Skip to content

Commit 5bcae91

Browse files
authored
Make PainlessScript an interface (#24966)
Allows more flexibility for the specified script context interface if we want to allow script contexts to specify an abstract class instead.
1 parent ddbc468 commit 5bcae91

File tree

4 files changed

+89
-43
lines changed

4 files changed

+89
-43
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.security.CodeSource;
3030
import java.security.SecureClassLoader;
3131
import java.security.cert.Certificate;
32-
import java.util.BitSet;
3332
import java.util.concurrent.atomic.AtomicInteger;
3433

3534
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
@@ -68,7 +67,7 @@ final class Compiler {
6867
*/
6968
static final class Loader extends SecureClassLoader {
7069
private final AtomicInteger lambdaCounter = new AtomicInteger(0);
71-
70+
7271
/**
7372
* @param parent The parent ClassLoader.
7473
*/
@@ -95,7 +94,7 @@ Class<? extends PainlessScript> defineScript(String name, byte[] bytes) {
9594
Class<?> defineLambda(String name, byte[] bytes) {
9695
return defineClass(name, bytes, 0, bytes.length, CODESOURCE);
9796
}
98-
97+
9998
/**
10099
* A counter used to generate a unique name for each lambda
101100
* function/reference class in this classloader.
@@ -132,11 +131,13 @@ static <T> T compile(Loader loader, Class<T> iface, String name, String source,
132131

133132
try {
134133
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
134+
clazz.getField("$NAME").set(null, name);
135+
clazz.getField("$SOURCE").set(null, source);
136+
clazz.getField("$STATEMENTS").set(null, root.getStatements());
135137
clazz.getField("$DEFINITION").set(null, definition);
136-
java.lang.reflect.Constructor<? extends PainlessScript> constructor =
137-
clazz.getConstructor(String.class, String.class, BitSet.class);
138+
java.lang.reflect.Constructor<? extends PainlessScript> constructor = clazz.getConstructor();
138139

139-
return iface.cast(constructor.newInstance(name, source, root.getStatements()));
140+
return iface.cast(constructor.newInstance());
140141
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
141142
throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception);
142143
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScript.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,33 @@
2929
/**
3030
* Abstract superclass on top of which all Painless scripts are built.
3131
*/
32-
public abstract class PainlessScript {
32+
public interface PainlessScript {
33+
3334
/**
34-
* Name of the script set at compile time.
35+
* @return The name of the script retrieved from a static variable generated
36+
* during compilation of a Painless script.
3537
*/
36-
private final String name;
38+
String getName();
39+
3740
/**
38-
* Source of the script.
41+
* @return The source for a script retrieved from a static variable generated
42+
* during compilation of a Painless script.
3943
*/
40-
private final String source;
44+
String getSource();
45+
4146
/**
42-
* Character number of the start of each statement.
47+
* @return The {@link BitSet} tracking the boundaries for statements necessary
48+
* for good exception messages.
4349
*/
44-
private final BitSet statements;
45-
46-
protected PainlessScript(String name, String source, BitSet statements) {
47-
this.name = name;
48-
this.source = source;
49-
this.statements = statements;
50-
}
50+
BitSet getStatements();
5151

5252
/**
5353
* Adds stack trace and other useful information to exceptions thrown
5454
* from a Painless script.
5555
* @param t The throwable to build an exception around.
5656
* @return The generated ScriptException.
5757
*/
58-
protected final ScriptException convertToScriptException(Throwable t, Map<String, List<String>> extraMetadata) {
58+
default ScriptException convertToScriptException(Throwable t, Map<String, List<String>> extraMetadata) {
5959
// create a script stack: this is just the script portion
6060
List<String> scriptStack = new ArrayList<>();
6161
for (StackTraceElement element : t.getStackTrace()) {
@@ -73,10 +73,10 @@ protected final ScriptException convertToScriptException(Throwable t, Map<String
7373
}
7474
int endOffset = getNextStatement(startOffset);
7575
if (endOffset == -1) {
76-
endOffset = source.length();
76+
endOffset = getSource().length();
7777
}
7878
// TODO: if this is still too long, truncate and use ellipses
79-
String snippet = source.substring(startOffset, endOffset);
79+
String snippet = getSource().substring(startOffset, endOffset);
8080
scriptStack.add(snippet);
8181
StringBuilder pointer = new StringBuilder();
8282
for (int i = startOffset; i < offset; i++) {
@@ -93,10 +93,10 @@ protected final ScriptException convertToScriptException(Throwable t, Map<String
9393
}
9494
// build a name for the script:
9595
final String name;
96-
if (PainlessScriptEngine.INLINE_NAME.equals(this.name)) {
97-
name = source;
96+
if (PainlessScriptEngine.INLINE_NAME.equals(getName())) {
97+
name = getSource();
9898
} else {
99-
name = this.name;
99+
name = getName();
100100
}
101101
ScriptException scriptException = new ScriptException("runtime error", t, scriptStack, name, PainlessScriptEngine.NAME);
102102
for (Map.Entry<String, List<String>> entry : extraMetadata.entrySet()) {
@@ -106,7 +106,7 @@ protected final ScriptException convertToScriptException(Throwable t, Map<String
106106
}
107107

108108
/** returns true for methods that are part of the runtime */
109-
private static boolean shouldFilter(StackTraceElement element) {
109+
default boolean shouldFilter(StackTraceElement element) {
110110
return element.getClassName().startsWith("org.elasticsearch.painless.") ||
111111
element.getClassName().startsWith("java.lang.invoke.") ||
112112
element.getClassName().startsWith("sun.invoke.");
@@ -115,14 +115,14 @@ private static boolean shouldFilter(StackTraceElement element) {
115115
/**
116116
* Finds the start of the first statement boundary that is on or before {@code offset}. If one is not found, {@code -1} is returned.
117117
*/
118-
private int getPreviousStatement(int offset) {
119-
return statements.previousSetBit(offset);
118+
default int getPreviousStatement(int offset) {
119+
return getStatements().previousSetBit(offset);
120120
}
121121

122122
/**
123123
* Finds the start of the first statement boundary that is after {@code offset}. If one is not found, {@code -1} is returned.
124124
*/
125-
private int getNextStatement(int offset) {
126-
return statements.nextSetBit(offset + 1);
125+
default int getNextStatement(int offset) {
126+
return getStatements().nextSetBit(offset + 1);
127127
}
128128
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,28 @@ public final class WriterConstants {
4646

4747
public static final int CLASS_VERSION = Opcodes.V1_8;
4848
public static final int ASM_VERSION = Opcodes.ASM5;
49-
public static final String BASE_CLASS_NAME = PainlessScript.class.getName();
50-
public static final Type BASE_CLASS_TYPE = Type.getType(PainlessScript.class);
49+
public static final String BASE_INTERFACE_NAME = PainlessScript.class.getName();
50+
public static final Type BASE_INTERFACE_TYPE = Type.getType(PainlessScript.class);
5151
public static final Method CONVERT_TO_SCRIPT_EXCEPTION_METHOD = getAsmMethod(ScriptException.class, "convertToScriptException",
5252
Throwable.class, Map.class);
5353

54-
public static final String CLASS_NAME = BASE_CLASS_NAME + "$Script";
55-
public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/'));
56-
54+
public static final String CLASS_NAME = BASE_INTERFACE_NAME + "$Script";
55+
public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/'));
56+
5757
public static final String CTOR_METHOD_NAME = "<init>";
5858

59-
public static final Method CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME, String.class, String.class, BitSet.class);
59+
public static final Method CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME);
6060
public static final Method CLINIT = getAsmMethod(void.class, "<clinit>");
6161

62+
public static final String GET_NAME_NAME = "getName";
63+
public static final Method GET_NAME_METHOD = getAsmMethod(String.class, GET_NAME_NAME);
64+
65+
public static final String GET_SOURCE_NAME = "getSource";
66+
public static final Method GET_SOURCE_METHOD = getAsmMethod(String.class, GET_SOURCE_NAME);
67+
68+
public static final String GET_STATEMENTS_NAME = "getStatements";
69+
public static final Method GET_STATEMENTS_METHOD = getAsmMethod(BitSet.class, GET_STATEMENTS_NAME);
70+
6271
// All of these types are caught by the main method and rethrown as ScriptException
6372
public static final Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
6473
public static final Type BOOTSTRAP_METHOD_ERROR_TYPE = Type.getType(BootstrapMethodError.class);
@@ -68,6 +77,9 @@ public final class WriterConstants {
6877
public static final Type PAINLESS_EXPLAIN_ERROR_TYPE = Type.getType(PainlessExplainError.class);
6978
public static final Method PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD = getAsmMethod(Map.class, "getHeaders", Definition.class);
7079

80+
public static final Type OBJECT_TYPE = Type.getType(Object.class);
81+
public static final Type BITSET_TYPE = Type.getType(BitSet.class);
82+
7183
public static final Type DEFINITION_TYPE = Type.getType(Definition.class);
7284

7385
public static final Type COLLECTIONS_TYPE = Type.getType(Collections.class);

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353

5454
import static java.util.Collections.emptyList;
5555
import static java.util.Collections.unmodifiableSet;
56-
import static org.elasticsearch.painless.WriterConstants.BASE_CLASS_TYPE;
56+
import static org.elasticsearch.painless.WriterConstants.BASE_INTERFACE_TYPE;
57+
import static org.elasticsearch.painless.WriterConstants.BITSET_TYPE;
5758
import static org.elasticsearch.painless.WriterConstants.BOOTSTRAP_METHOD_ERROR_TYPE;
5859
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
5960
import static org.elasticsearch.painless.WriterConstants.COLLECTIONS_TYPE;
@@ -65,11 +66,16 @@
6566
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_METHOD;
6667
import static org.elasticsearch.painless.WriterConstants.EMPTY_MAP_METHOD;
6768
import static org.elasticsearch.painless.WriterConstants.EXCEPTION_TYPE;
69+
import static org.elasticsearch.painless.WriterConstants.GET_NAME_METHOD;
70+
import static org.elasticsearch.painless.WriterConstants.GET_SOURCE_METHOD;
71+
import static org.elasticsearch.painless.WriterConstants.GET_STATEMENTS_METHOD;
72+
import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE;
6873
import static org.elasticsearch.painless.WriterConstants.OUT_OF_MEMORY_ERROR_TYPE;
6974
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
7075
import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD;
7176
import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_TYPE;
7277
import static org.elasticsearch.painless.WriterConstants.STACK_OVERFLOW_ERROR_TYPE;
78+
import static org.elasticsearch.painless.WriterConstants.STRING_TYPE;
7379

7480
/**
7581
* The root of all Painless trees. Contains a series of statements.
@@ -203,9 +209,9 @@ public void write() {
203209

204210
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
205211
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
206-
String classBase = BASE_CLASS_TYPE.getInternalName();
212+
String interfaceBase = BASE_INTERFACE_TYPE.getInternalName();
207213
String className = CLASS_TYPE.getInternalName();
208-
String classInterfaces[] = new String[] { Type.getType(scriptInterface.getInterface()).getInternalName() };
214+
String classInterfaces[] = new String[] { interfaceBase, Type.getType(scriptInterface.getInterface()).getInternalName() };
209215

210216
ClassWriter writer = new ClassWriter(classFrames);
211217
ClassVisitor visitor = writer;
@@ -218,7 +224,7 @@ public void write() {
218224
if (debugStream != null) {
219225
visitor = new TraceClassVisitor(visitor, debugStream, null);
220226
}
221-
visitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, classBase, classInterfaces);
227+
visitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, OBJECT_TYPE.getInternalName(), classInterfaces);
222228
visitor.visitSource(Location.computeSourceName(name, source), null);
223229

224230
// Write the a method to bootstrap def calls
@@ -231,6 +237,11 @@ public void write() {
231237
bootstrapDef.returnValue();
232238
bootstrapDef.endMethod();
233239

240+
// Write static variables for name, source and statements used for writing exception messages
241+
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$NAME", STRING_TYPE.getDescriptor(), null, null).visitEnd();
242+
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$SOURCE", STRING_TYPE.getDescriptor(), null, null).visitEnd();
243+
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$STATEMENTS", BITSET_TYPE.getDescriptor(), null, null).visitEnd();
244+
234245
// Write the static variable used by the method to bootstrap def calls
235246
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$DEFINITION", DEFINITION_TYPE.getDescriptor(), null, null).visitEnd();
236247

@@ -239,10 +250,32 @@ public void write() {
239250
constructor.visitCode();
240251
constructor.loadThis();
241252
constructor.loadArgs();
242-
constructor.invokeConstructor(BASE_CLASS_TYPE, CONSTRUCTOR);
253+
constructor.invokeConstructor(OBJECT_TYPE, CONSTRUCTOR);
243254
constructor.returnValue();
244255
constructor.endMethod();
245256

257+
// Write a method to get static variable source
258+
MethodWriter nameMethod = new MethodWriter(Opcodes.ACC_PUBLIC, GET_NAME_METHOD, visitor, globals.getStatements(), settings);
259+
nameMethod.visitCode();
260+
nameMethod.getStatic(CLASS_TYPE, "$NAME", STRING_TYPE);
261+
nameMethod.returnValue();
262+
nameMethod.endMethod();
263+
264+
// Write a method to get static variable source
265+
MethodWriter sourceMethod = new MethodWriter(Opcodes.ACC_PUBLIC, GET_SOURCE_METHOD, visitor, globals.getStatements(), settings);
266+
sourceMethod.visitCode();
267+
sourceMethod.getStatic(CLASS_TYPE, "$SOURCE", STRING_TYPE);
268+
sourceMethod.returnValue();
269+
sourceMethod.endMethod();
270+
271+
// Write a method to get static variable statements
272+
MethodWriter statementsMethod =
273+
new MethodWriter(Opcodes.ACC_PUBLIC, GET_STATEMENTS_METHOD, visitor, globals.getStatements(), settings);
274+
statementsMethod.visitCode();
275+
statementsMethod.getStatic(CLASS_TYPE, "$STATEMENTS", BITSET_TYPE);
276+
statementsMethod.returnValue();
277+
statementsMethod.endMethod();
278+
246279
// Write the method defined in the interface:
247280
MethodWriter executeMethod = new MethodWriter(Opcodes.ACC_PUBLIC, scriptInterface.getExecuteMethod(), visitor,
248281
globals.getStatements(), settings);
@@ -357,7 +390,7 @@ void write(MethodWriter writer, Globals globals) {
357390
writer.dup();
358391
writer.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE);
359392
writer.invokeVirtual(PAINLESS_EXPLAIN_ERROR_TYPE, PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD);
360-
writer.invokeVirtual(BASE_CLASS_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
393+
writer.invokeInterface(BASE_INTERFACE_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
361394
writer.throwException();
362395
// This looks like:
363396
// } catch (PainlessError | BootstrapMethodError | OutOfMemoryError | StackOverflowError | Exception e) {
@@ -373,7 +406,7 @@ void write(MethodWriter writer, Globals globals) {
373406
writer.loadThis();
374407
writer.swap();
375408
writer.invokeStatic(COLLECTIONS_TYPE, EMPTY_MAP_METHOD);
376-
writer.invokeVirtual(BASE_CLASS_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
409+
writer.invokeInterface(BASE_INTERFACE_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
377410
writer.throwException();
378411
writer.mark(endCatch);
379412
}

0 commit comments

Comments
 (0)