Skip to content

Commit c6746e2

Browse files
authored
Remove custom "execute" method from SClass user node in Painless (#51776)
One of the goals moving forward is to make the user tree nodes and ir tree nodes generic (as in not have custom code for specific methods, etc.). Eventually, the user tree should not need to process anything from the ScriptContextInfo class as any customization should be added as generic nodes by whatever is creating the user and ir trees, respectively. This PR moves all code required for the customized "execute" method from the SClass/ClassNode into the SFunction/FunctionNode as a first step towards the aforementioned goals. In upcoming PRs the customized "execute" method code will be completely removed from the SFunction/FunctionNode classes as well. The SClass/ClassNode now consist of only functions and fields instead of also having logic to process statements for a singular custom "execute" method.
1 parent f4a4e41 commit c6746e2

File tree

12 files changed

+240
-231
lines changed

12 files changed

+240
-231
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ public void writeStatementOffset(Location location) {
125125
int offset = location.getOffset();
126126
// ensure we don't have duplicate stuff going in here. can catch bugs
127127
// (e.g. nodes get assigned wrong offsets by antlr walker)
128-
assert statements.get(offset) == false;
128+
// TODO: introduce a way to ignore internal statements so this assert is not triggered
129+
// TODO: https://github.com/elastic/elasticsearch/issues/51836
130+
//assert statements.get(offset) == false;
129131
statements.set(offset);
130132
}
131133

modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
import org.elasticsearch.painless.antlr.PainlessParser.VariableContext;
107107
import org.elasticsearch.painless.antlr.PainlessParser.WhileContext;
108108
import org.elasticsearch.painless.lookup.PainlessLookup;
109+
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
109110
import org.elasticsearch.painless.node.AExpression;
110111
import org.elasticsearch.painless.node.ANode;
111112
import org.elasticsearch.painless.node.AStatement;
@@ -245,13 +246,30 @@ public ANode visitSource(SourceContext ctx) {
245246
functions.add((SFunction)visit(function));
246247
}
247248

249+
// handle the code to generate the execute method here
250+
// because the statements come loose from the grammar as
251+
// part of the overall class
248252
List<AStatement> statements = new ArrayList<>();
249253

250254
for (StatementContext statement : ctx.statement()) {
251255
statements.add((AStatement)visit(statement));
252256
}
253257

254-
return new SClass(scriptClassInfo, sourceName, sourceText, debugStream, location(ctx), functions, statements);
258+
String returnCanonicalTypeName = PainlessLookupUtility.typeToCanonicalTypeName(scriptClassInfo.getExecuteMethodReturnType());
259+
List<String> paramTypes = new ArrayList<>();
260+
List<String> paramNames = new ArrayList<>();
261+
262+
for (ScriptClassInfo.MethodArgument argument : scriptClassInfo.getExecuteArguments()) {
263+
paramTypes.add(PainlessLookupUtility.typeToCanonicalTypeName(argument.getClazz()));
264+
paramNames.add(argument.getName());
265+
}
266+
267+
// generate the execute method from the collected statements and parameters
268+
SFunction execute = new SFunction(location(ctx), returnCanonicalTypeName, "execute", paramTypes, paramNames, new SBlock(
269+
location(ctx), statements), true, false, false, true);
270+
functions.add(execute);
271+
272+
return new SClass(scriptClassInfo, sourceName, sourceText, debugStream, location(ctx), functions);
255273
}
256274

257275
@Override
@@ -278,7 +296,8 @@ public ANode visitFunction(FunctionContext ctx) {
278296
statements.add((AStatement)visit(ctx.block().dstatement()));
279297
}
280298

281-
return new SFunction(location(ctx), rtnType, name, paramTypes, paramNames, new SBlock(location(ctx), statements), false);
299+
return new SFunction(
300+
location(ctx), rtnType, name, paramTypes, paramNames, new SBlock(location(ctx), statements), false, true, false, false);
282301
}
283302

284303
@Override

modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java

Lines changed: 0 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,10 @@
2727
import org.elasticsearch.painless.ScriptClassInfo;
2828
import org.elasticsearch.painless.WriterConstants;
2929
import org.elasticsearch.painless.symbol.ScopeTable;
30-
import org.elasticsearch.painless.symbol.ScopeTable.Variable;
3130
import org.elasticsearch.painless.symbol.ScriptRoot;
3231
import org.objectweb.asm.ClassVisitor;
33-
import org.objectweb.asm.Label;
3432
import org.objectweb.asm.Opcodes;
3533
import org.objectweb.asm.Type;
36-
import org.objectweb.asm.commons.Method;
3734
import org.objectweb.asm.util.Printer;
3835

3936
import java.lang.invoke.MethodType;
@@ -46,25 +43,15 @@
4643

4744
import static org.elasticsearch.painless.WriterConstants.BASE_INTERFACE_TYPE;
4845
import static org.elasticsearch.painless.WriterConstants.BITSET_TYPE;
49-
import static org.elasticsearch.painless.WriterConstants.BOOTSTRAP_METHOD_ERROR_TYPE;
5046
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
51-
import static org.elasticsearch.painless.WriterConstants.COLLECTIONS_TYPE;
52-
import static org.elasticsearch.painless.WriterConstants.CONVERT_TO_SCRIPT_EXCEPTION_METHOD;
5347
import static org.elasticsearch.painless.WriterConstants.DEFINITION_TYPE;
5448
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_METHOD;
5549
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_TYPE;
5650
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_METHOD;
57-
import static org.elasticsearch.painless.WriterConstants.EMPTY_MAP_METHOD;
58-
import static org.elasticsearch.painless.WriterConstants.EXCEPTION_TYPE;
5951
import static org.elasticsearch.painless.WriterConstants.FUNCTION_TABLE_TYPE;
6052
import static org.elasticsearch.painless.WriterConstants.GET_NAME_METHOD;
6153
import static org.elasticsearch.painless.WriterConstants.GET_SOURCE_METHOD;
6254
import static org.elasticsearch.painless.WriterConstants.GET_STATEMENTS_METHOD;
63-
import static org.elasticsearch.painless.WriterConstants.OUT_OF_MEMORY_ERROR_TYPE;
64-
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
65-
import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD;
66-
import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_TYPE;
67-
import static org.elasticsearch.painless.WriterConstants.STACK_OVERFLOW_ERROR_TYPE;
6855
import static org.elasticsearch.painless.WriterConstants.STRING_TYPE;
6956

7057
public class ClassNode extends IRNode {
@@ -73,7 +60,6 @@ public class ClassNode extends IRNode {
7360

7461
private final List<FieldNode> fieldNodes = new ArrayList<>();
7562
private final List<FunctionNode> functionNodes = new ArrayList<>();
76-
private final List<StatementNode> statementNodes = new ArrayList<>();
7763

7864
public void addFieldNode(FieldNode fieldNode) {
7965
fieldNodes.add(fieldNode);
@@ -90,14 +76,6 @@ public void addFunctionNode(FunctionNode functionNode) {
9076
public List<FunctionNode> getFunctionsNodes() {
9177
return functionNodes;
9278
}
93-
94-
public void addStatementNode(StatementNode statementNode) {
95-
statementNodes.add(statementNode);
96-
}
97-
98-
public List<StatementNode> getStatementsNodes() {
99-
return statementNodes;
100-
}
10179

10280
/* ---- end tree structure, begin node data ---- */
10381

@@ -106,7 +84,6 @@ public List<StatementNode> getStatementsNodes() {
10684
private String sourceText;
10785
private Printer debugStream;
10886
private ScriptRoot scriptRoot;
109-
private boolean doesMethodEscape;
11087

11188
public void setScriptClassInfo(ScriptClassInfo scriptClassInfo) {
11289
this.scriptClassInfo = scriptClassInfo;
@@ -148,14 +125,6 @@ public ScriptRoot getScriptRoot() {
148125
return scriptRoot;
149126
}
150127

151-
public void setMethodEscape(boolean doesMethodEscape) {
152-
this.doesMethodEscape = doesMethodEscape;
153-
}
154-
155-
public boolean doesMethodEscape() {
156-
return doesMethodEscape;
157-
}
158-
159128
/* ---- end node data ---- */
160129

161130
protected Globals globals;
@@ -245,12 +214,6 @@ public Map<String, Object> write() {
245214
statementsMethod.returnValue();
246215
statementsMethod.endMethod();
247216

248-
// Write the method defined in the interface:
249-
MethodWriter executeMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, scriptClassInfo.getExecuteMethod());
250-
executeMethod.visitCode();
251-
write(classWriter, executeMethod, globals, new ScopeTable());
252-
executeMethod.endMethod();
253-
254217
// Write all fields:
255218
for (FieldNode fieldNode : fieldNodes) {
256219
fieldNode.write(classWriter, null, null, null);
@@ -305,116 +268,4 @@ public Map<String, Object> write() {
305268

306269
return statics;
307270
}
308-
309-
@Override
310-
protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
311-
// We wrap the whole method in a few try/catches to handle and/or convert other exceptions to ScriptException
312-
Label startTry = new Label();
313-
Label endTry = new Label();
314-
Label startExplainCatch = new Label();
315-
Label startOtherCatch = new Label();
316-
Label endCatch = new Label();
317-
methodWriter.mark(startTry);
318-
319-
scopeTable.defineInternalVariable(Object.class, "this");
320-
321-
// Method arguments
322-
for (ScriptClassInfo.MethodArgument arg : scriptClassInfo.getExecuteArguments()) {
323-
scopeTable.defineVariable(arg.getClazz(), arg.getName());
324-
}
325-
326-
if (scriptRoot.getCompilerSettings().getMaxLoopCounter() > 0) {
327-
// if there is infinite loop protection, we do this once:
328-
// int #loop = settings.getMaxLoopCounter()
329-
330-
Variable loop = scopeTable.defineInternalVariable(int.class, "loop");
331-
332-
methodWriter.push(scriptRoot.getCompilerSettings().getMaxLoopCounter());
333-
methodWriter.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
334-
}
335-
336-
for (int getMethodIndex = 0; getMethodIndex < scriptClassInfo.getGetMethods().size(); ++getMethodIndex) {
337-
Method method = scriptClassInfo.getGetMethods().get(getMethodIndex);
338-
Class<?> returnType = scriptClassInfo.getGetReturns().get(getMethodIndex);
339-
340-
String name = method.getName().substring(3);
341-
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
342-
343-
if (scriptRoot.getUsedVariables().contains(name)) {
344-
Variable variable = scopeTable.defineVariable(returnType, name);
345-
346-
methodWriter.loadThis();
347-
methodWriter.invokeVirtual(Type.getType(scriptClassInfo.getBaseClass()), method);
348-
methodWriter.visitVarInsn(method.getReturnType().getOpcode(Opcodes.ISTORE), variable.getSlot());
349-
}
350-
}
351-
352-
for (StatementNode statementNode : statementNodes) {
353-
statementNode.write(classWriter, methodWriter, globals, scopeTable);
354-
}
355-
356-
if (doesMethodEscape == false) {
357-
switch (scriptClassInfo.getExecuteMethod().getReturnType().getSort()) {
358-
case org.objectweb.asm.Type.VOID:
359-
break;
360-
case org.objectweb.asm.Type.BOOLEAN:
361-
methodWriter.push(false);
362-
break;
363-
case org.objectweb.asm.Type.BYTE:
364-
methodWriter.push(0);
365-
break;
366-
case org.objectweb.asm.Type.SHORT:
367-
methodWriter.push(0);
368-
break;
369-
case org.objectweb.asm.Type.INT:
370-
methodWriter.push(0);
371-
break;
372-
case org.objectweb.asm.Type.LONG:
373-
methodWriter.push(0L);
374-
break;
375-
case org.objectweb.asm.Type.FLOAT:
376-
methodWriter.push(0f);
377-
break;
378-
case org.objectweb.asm.Type.DOUBLE:
379-
methodWriter.push(0d);
380-
break;
381-
default:
382-
methodWriter.visitInsn(Opcodes.ACONST_NULL);
383-
}
384-
methodWriter.returnValue();
385-
}
386-
387-
methodWriter.mark(endTry);
388-
methodWriter.goTo(endCatch);
389-
// This looks like:
390-
// } catch (PainlessExplainError e) {
391-
// throw this.convertToScriptException(e, e.getHeaders($DEFINITION))
392-
// }
393-
methodWriter.visitTryCatchBlock(startTry, endTry, startExplainCatch, PAINLESS_EXPLAIN_ERROR_TYPE.getInternalName());
394-
methodWriter.mark(startExplainCatch);
395-
methodWriter.loadThis();
396-
methodWriter.swap();
397-
methodWriter.dup();
398-
methodWriter.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE);
399-
methodWriter.invokeVirtual(PAINLESS_EXPLAIN_ERROR_TYPE, PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD);
400-
methodWriter.invokeInterface(BASE_INTERFACE_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
401-
methodWriter.throwException();
402-
// This looks like:
403-
// } catch (PainlessError | BootstrapMethodError | OutOfMemoryError | StackOverflowError | Exception e) {
404-
// throw this.convertToScriptException(e, e.getHeaders())
405-
// }
406-
// We *think* it is ok to catch OutOfMemoryError and StackOverflowError because Painless is stateless
407-
methodWriter.visitTryCatchBlock(startTry, endTry, startOtherCatch, PAINLESS_ERROR_TYPE.getInternalName());
408-
methodWriter.visitTryCatchBlock(startTry, endTry, startOtherCatch, BOOTSTRAP_METHOD_ERROR_TYPE.getInternalName());
409-
methodWriter.visitTryCatchBlock(startTry, endTry, startOtherCatch, OUT_OF_MEMORY_ERROR_TYPE.getInternalName());
410-
methodWriter.visitTryCatchBlock(startTry, endTry, startOtherCatch, STACK_OVERFLOW_ERROR_TYPE.getInternalName());
411-
methodWriter.visitTryCatchBlock(startTry, endTry, startOtherCatch, EXCEPTION_TYPE.getInternalName());
412-
methodWriter.mark(startOtherCatch);
413-
methodWriter.loadThis();
414-
methodWriter.swap();
415-
methodWriter.invokeStatic(COLLECTIONS_TYPE, EMPTY_MAP_METHOD);
416-
methodWriter.invokeInterface(BASE_INTERFACE_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD);
417-
methodWriter.throwException();
418-
methodWriter.mark(endCatch);
419-
}
420271
}

0 commit comments

Comments
 (0)