diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index 664c567091c..b81a9dee24c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -109,7 +109,7 @@ final class Compiler { java.lang.reflect.Constructor constructor = clazz.getConstructor(String.class, String.class, BitSet.class); - return constructor.newInstance(name, source, root.getExpressions()); + return constructor.newInstance(name, source, root.getStatements()); } catch (Exception exception) { // Catch everything to let the user know this is something caused internally. throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Constant.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Constant.java new file mode 100644 index 00000000000..d42c267fc3f --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Constant.java @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless; + +import java.util.function.Consumer; + +/** + * A constant initializer to be added to the class file. + */ +public class Constant { + public final Location location; + public final String name; + public final org.objectweb.asm.Type type; + public final Consumer initializer; + + /** + * Create a new constant. + * + * @param location the location in the script that is creating it + * @param type the type of the constant + * @param name the name of the constant + * @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the + * value of the constant on the stack. Generating the load instruction is managed by the caller. + */ + public Constant(Location location, org.objectweb.asm.Type type, String name, Consumer initializer) { + this.location = location; + this.name = name; + this.type = type; + this.initializer = initializer; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Globals.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Globals.java new file mode 100644 index 00000000000..83eb74d827f --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Globals.java @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.painless.node.SFunction; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; + +/** + * Program-wide globals (initializers, synthetic methods, etc) + */ +public class Globals { + private final Map syntheticMethods = new HashMap<>(); + private final Map constantInitializers = new HashMap<>(); + private final BitSet statements; + + /** Create a new Globals from the set of statement boundaries */ + public Globals(BitSet statements) { + this.statements = statements; + } + + /** Adds a new synthetic method to be written. It must be analyzed! */ + public void addSyntheticMethod(SFunction function) { + if (!function.synthetic) { + throw new IllegalStateException("method: " + function.name + " is not synthetic"); + } + if (syntheticMethods.put(function.name, function) != null) { + throw new IllegalStateException("synthetic method: " + function.name + " already exists"); + } + } + + /** Adds a new constant initializer to be written */ + public void addConstantInitializer(Constant constant) { + if (constantInitializers.put(constant.name, constant) != null) { + throw new IllegalStateException("constant initializer: " + constant.name + " already exists"); + } + } + + /** Returns the current synthetic methods */ + public Map getSyntheticMethods() { + return syntheticMethods; + } + + /** Returns the current initializers */ + public Map getConstantInitializers() { + return constantInitializers; + } + + /** Returns the set of statement boundaries */ + public BitSet getStatements() { + return statements; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaLocals.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaLocals.java new file mode 100644 index 00000000000..9c01bc4c628 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaLocals.java @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.painless.Definition.Type; + +import java.util.List; +import java.util.Objects; + +/** Extension of locals for lambdas */ +// Note: this isn't functional yet, it throws UOE +// TODO: implement slot renumbering for captures. +class LambdaLocals extends Locals { + private List captures; + + LambdaLocals(Locals parent, List parameters, List captures) { + super(parent); + for (Parameter parameter : parameters) { + defineVariable(parameter.location, parameter.type, parameter.name, false); + } + this.captures = Objects.requireNonNull(captures); + } + + @Override + public Variable getVariable(Location location, String name) { + Variable variable = lookupVariable(location, name); + if (variable != null) { + return variable; + } + if (getParent() != null) { + variable = getParent().getVariable(location, name); + if (variable != null) { + assert captures != null; // unused right now + // make it read-only, and record that it was used. + throw new UnsupportedOperationException("lambda capture is not supported"); + } + } + throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined.")); + } + + @Override + public Type getReturnType() { + return Definition.DEF_TYPE; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java index 30f15c42dde..8cb99ed2404 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java @@ -23,141 +23,273 @@ import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.Type; -import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Deque; import java.util.HashMap; -import java.util.Iterator; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.function.Consumer; +import java.util.Set; /** * Tracks user defined methods and variables across compilation phases. */ -public final class Locals { +public class Locals { + + /** Reserved word: params map parameter */ + public static final String PARAMS = "params"; + /** Reserved word: Lucene scorer parameter */ + public static final String SCORER = "#scorer"; + /** Reserved word: _value variable for aggregations */ + public static final String VALUE = "_value"; + /** Reserved word: _score variable for search scripts */ + public static final String SCORE = "_score"; + /** Reserved word: ctx map for executable scripts */ + public static final String CTX = "ctx"; + /** Reserved word: loop counter */ + public static final String LOOP = "#loop"; + /** Reserved word: unused */ + public static final String THIS = "#this"; + /** Reserved word: unused */ + public static final String DOC = "doc"; + + /** Map of always reserved keywords */ + public static final Set KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + THIS,PARAMS,SCORER,DOC,VALUE,SCORE,CTX,LOOP + ))); + + /** Creates a new local variable scope (e.g. loop) inside the current scope */ + public static Locals newLocalScope(Locals currentScope) { + return new Locals(currentScope); + } + + /** Creates a new lambda scope inside the current scope */ + public static Locals newLambdaScope(Locals currentScope, List parameters, List captures) { + return new LambdaLocals(currentScope, parameters, captures); + } + + /** Creates a new function scope inside the current scope */ + public static Locals newFunctionScope(Locals programScope, Type returnType, List parameters, int maxLoopCounter) { + Locals locals = new Locals(programScope, returnType); + for (Parameter parameter : parameters) { + locals.defineVariable(parameter.location, parameter.type, parameter.name, false); + } + // Loop counter to catch infinite loops. Internal use only. + if (maxLoopCounter > 0) { + locals.defineVariable(null, Definition.INT_TYPE, LOOP, true); + } + return locals; + } + + /** Creates a new main method scope */ + public static Locals newMainMethodScope(Locals programScope, boolean usesScore, boolean usesCtx, int maxLoopCounter) { + Locals locals = new Locals(programScope, Definition.OBJECT_TYPE); + // This reference. Internal use only. + locals.defineVariable(null, Definition.getType("Object"), THIS, true); + + // Input map of variables passed to the script. + locals.defineVariable(null, Definition.getType("Map"), PARAMS, true); + + // Scorer parameter passed to the script. Internal use only. + locals.defineVariable(null, Definition.DEF_TYPE, SCORER, true); + + // Doc parameter passed to the script. TODO: Currently working as a Map, we can do better? + locals.defineVariable(null, Definition.getType("Map"), DOC, true); + + // Aggregation _value parameter passed to the script. + locals.defineVariable(null, Definition.DEF_TYPE, VALUE, true); + + // Shortcut variables. + + // Document's score as a read-only double. + if (usesScore) { + locals.defineVariable(null, Definition.DOUBLE_TYPE, SCORE, true); + } + + // The ctx map set by executable scripts as a read-only map. + if (usesCtx) { + locals.defineVariable(null, Definition.getType("Map"), CTX, true); + } + + // Loop counter to catch infinite loops. Internal use only. + if (maxLoopCounter > 0) { + locals.defineVariable(null, Definition.INT_TYPE, LOOP, true); + } + return locals; + } + + /** Creates a new program scope: the list of methods. It is the parent for all methods */ + public static Locals newProgramScope(Collection methods) { + Locals locals = new Locals(null, null); + for (Method method : methods) { + locals.addMethod(method); + } + return locals; + } + + /** Checks if a variable exists or not, in this scope or any parents. */ + public final boolean hasVariable(String name) { + Variable variable = lookupVariable(null, name); + if (variable != null) { + return true; + } + if (parent != null) { + return parent.hasVariable(name); + } + return false; + } + + /** Accesses a variable. This will throw IAE if the variable does not exist */ + public Variable getVariable(Location location, String name) { + Variable variable = lookupVariable(location, name); + if (variable != null) { + return variable; + } + if (parent != null) { + return parent.getVariable(location, name); + } + throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined.")); + } + + /** Looks up a method. Returns null if the method does not exist. */ + public final Method getMethod(MethodKey key) { + Method method = lookupMethod(key); + if (method != null) { + return method; + } + if (parent != null) { + return parent.getMethod(key); + } + return null; + } + + /** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */ + public final Variable addVariable(Location location, Type type, String name, boolean readonly) { + if (hasVariable(name)) { + throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined.")); + } + if (KEYWORDS.contains(name)) { + throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved.")); + } + return defineVariable(location, type, name, readonly); + } + + /** Return type of this scope (e.g. int, if inside a function that returns int) */ + public Type getReturnType() { + return returnType; + } + + /** Returns the top-level program scope. */ + public Locals getProgramScope() { + Locals locals = this; + while (locals.getParent() != null) { + locals = locals.getParent(); + } + return locals; + } + + ///// private impl + + // parent scope + private final Locals parent; + // return type of this scope + private final Type returnType; + // next slot number to assign + int nextSlotNumber; + // variable name -> variable + Map variables; + // method name+arity -> methods + Map methods; /** - * Tracks reserved variables. Must be given to any source of input - * prior to beginning the analysis phase so that reserved variables - * are known ahead of time to assign appropriate slots without - * being wasteful. + * Create a new Locals */ - public interface Reserved { - void markReserved(String name); - boolean isReserved(String name); - - void setMaxLoopCounter(int max); - int getMaxLoopCounter(); + Locals(Locals parent) { + this(parent, parent.getReturnType()); } - - public static final class ExecuteReserved implements Reserved { - public static final String THIS = "#this"; - public static final String PARAMS = "params"; - public static final String SCORER = "#scorer"; - public static final String DOC = "doc"; - public static final String VALUE = "_value"; - public static final String SCORE = "_score"; - public static final String CTX = "ctx"; - public static final String LOOP = "#loop"; - - private boolean score = false; - private boolean ctx = false; - private int maxLoopCounter = 0; - - @Override - public void markReserved(String name) { - if (SCORE.equals(name)) { - score = true; - } else if (CTX.equals(name)) { - ctx = true; - } - } - - @Override - public boolean isReserved(String name) { - return name.equals(THIS) || name.equals(PARAMS) || name.equals(SCORER) || name.equals(DOC) || - name.equals(VALUE) || name.equals(SCORE) || name.equals(CTX) || name.equals(LOOP); - } - - public boolean usesScore() { - return score; - } - - public boolean usesCtx() { - return ctx; - } - - @Override - public void setMaxLoopCounter(int max) { - maxLoopCounter = max; - } - - @Override - public int getMaxLoopCounter() { - return maxLoopCounter; + + /** + * Create a new Locals with specified return type + */ + Locals(Locals parent, Type returnType) { + this.parent = parent; + this.returnType = returnType; + if (parent == null) { + this.nextSlotNumber = 0; + } else { + this.nextSlotNumber = parent.getNextSlot(); } } - public static final class FunctionReserved implements Reserved { - public static final String THIS = "#this"; - public static final String LOOP = "#loop"; + /** Returns the parent scope */ + Locals getParent() { + return parent; + } - private int maxLoopCounter = 0; - - public void markReserved(String name) { - // Do nothing. + /** Looks up a variable at this scope only. Returns null if the variable does not exist. */ + Variable lookupVariable(Location location, String name) { + if (variables == null) { + return null; } + return variables.get(name); + } - public boolean isReserved(String name) { - return name.equals(THIS) || name.equals(LOOP); + /** Looks up a method at this scope only. Returns null if the method does not exist. */ + Method lookupMethod(MethodKey key) { + if (methods == null) { + return null; } + return methods.get(key); + } - @Override - public void setMaxLoopCounter(int max) { - maxLoopCounter = max; + + /** Defines a variable at this scope internally. */ + Variable defineVariable(Location location, Type type, String name, boolean readonly) { + if (variables == null) { + variables = new HashMap<>(); } + Variable variable = new Variable(location, name, type, readonly); + variable.slot = getNextSlot(); + variables.put(name, variable); // TODO: check result + nextSlotNumber += type.type.getSize(); + return variable; + } + + // TODO: make private, thats bogus + public void addMethod(Method method) { + if (methods == null) { + methods = new HashMap<>(); + } + methods.put(new MethodKey(method.name, method.arguments.size()), method); + // TODO: check result + } - @Override - public int getMaxLoopCounter() { - return maxLoopCounter; - } + + int getNextSlot() { + return nextSlotNumber; } public static final class Variable { public final Location location; public final String name; public final Type type; - public final int slot; + int slot = -1; public final boolean readonly; - - public boolean read = false; - - private Variable(Location location, String name, Type type, int slot, boolean readonly) { + + public Variable(Location location, String name, Type type, boolean readonly) { this.location = location; this.name = name; this.type = type; - this.slot = slot; this.readonly = readonly; } - } - - public static final class Constant { - public final Location location; - public final String name; - public final org.objectweb.asm.Type type; - public final Consumer initializer; - - private Constant(Location location, String name, org.objectweb.asm.Type type, Consumer initializer) { - this.location = location; - this.name = name; - this.type = type; - this.initializer = initializer; + + public int getSlot() { + return slot; } } - - public static final class Parameter { + + public static class Parameter { public final Location location; public final String name; public final Type type; @@ -168,197 +300,4 @@ public final class Locals { this.type = type; } } - - private final Reserved reserved; - private final Map methods; - private final Map constants; - private final Type rtnType; - - // TODO: this datastructure runs in linear time for nearly all operations. use linkedhashset instead? - private final Deque scopes = new ArrayDeque<>(); - private final Deque variables = new ArrayDeque<>(); - - public Locals(ExecuteReserved reserved, Map methods) { - this.reserved = reserved; - this.methods = Collections.unmodifiableMap(methods); - this.constants = new HashMap<>(); - this.rtnType = Definition.OBJECT_TYPE; - - incrementScope(); - - // Method variables. - - // This reference. Internal use only. - addVariable(null, Definition.getType("Object"), ExecuteReserved.THIS, true, true); - - // Input map of variables passed to the script. - addVariable(null, Definition.getType("Map"), ExecuteReserved.PARAMS, true, true); - - // Scorer parameter passed to the script. Internal use only. - addVariable(null, Definition.DEF_TYPE, ExecuteReserved.SCORER, true, true); - - // Doc parameter passed to the script. TODO: Currently working as a Map, we can do better? - addVariable(null, Definition.getType("Map"), ExecuteReserved.DOC, true, true); - - // Aggregation _value parameter passed to the script. - addVariable(null, Definition.DEF_TYPE, ExecuteReserved.VALUE, true, true); - - // Shortcut variables. - - // Document's score as a read-only double. - if (reserved.usesScore()) { - addVariable(null, Definition.DOUBLE_TYPE, ExecuteReserved.SCORE, true, true); - } - - // The ctx map set by executable scripts as a read-only map. - if (reserved.usesCtx()) { - addVariable(null, Definition.getType("Map"), ExecuteReserved.CTX, true, true); - } - - // Loop counter to catch infinite loops. Internal use only. - if (reserved.getMaxLoopCounter() > 0) { - addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true); - } - } - - public Locals(FunctionReserved reserved, Locals locals, Type rtnType, List parameters) { - this.reserved = reserved; - this.methods = locals.methods; - this.constants = locals.constants; - this.rtnType = rtnType; - - incrementScope(); - - for (Parameter parameter : parameters) { - addVariable(parameter.location, parameter.type, parameter.name, false, false); - } - - // Loop counter to catch infinite loops. Internal use only. - if (reserved.getMaxLoopCounter() > 0) { - addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true); - } - } - - public int getMaxLoopCounter() { - return reserved.getMaxLoopCounter(); - } - - public Method getMethod(MethodKey key) { - return methods.get(key); - } - - public Type getReturnType() { - return rtnType; - } - - public void incrementScope() { - scopes.push(0); - } - - public void decrementScope() { - int remove = scopes.pop(); - - while (remove > 0) { - Variable variable = variables.pop(); - - // This checks whether or not a variable is used when exiting a local scope. - if (variable.read) { - throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] is never used.")); - } - - --remove; - } - } - - public Variable getVariable(Location location, String name) { - Iterator itr = variables.iterator(); - - while (itr.hasNext()) { - Variable variable = itr.next(); - - if (variable.name.equals(name)) { - return variable; - } - } - - throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined.")); - } - - public boolean isVariable(String name) { - Iterator itr = variables.iterator(); - - while (itr.hasNext()) { - Variable variable = itr.next(); - - if (variable.name.equals(name)) { - return true; - } - } - - return false; - } - - public Variable addVariable(Location location, Type type, String name, boolean readonly, boolean reserved) { - if (!reserved && this.reserved.isReserved(name)) { - throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved.")); - } - - if (isVariable(name)) { - throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined.")); - } - - Variable previous = variables.peekFirst(); - int slot = 0; - - if (previous != null) { - slot = previous.slot + previous.type.type.getSize(); - } - - Variable variable = new Variable(location, name, type, slot, readonly); - variables.push(variable); - - int update = scopes.pop() + 1; - scopes.push(update); - - return variable; - } - - /** - * Create a new constant. - * - * @param location the location in the script that is creating it - * @param type the type of the constant - * @param name the name of the constant - * @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the - * value of the constant on the stack. Generating the load instruction is managed by the caller. - * @return the constant - */ - public Constant addConstant(Location location, org.objectweb.asm.Type type, String name, Consumer initializer) { - if (constants.containsKey(name)) { - throw location.createError(new IllegalArgumentException("Constant [" + name + "] is already defined.")); - } - - Constant constant = new Constant(location, name, type, initializer); - constants.put(name, constant); - return constant; - } - - /** - * Create a new constant. - * - * @param location the location in the script that is creating it - * @param type the type of the constant - * @param name the name of the constant - * @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the - * value of the constant on the stack. Generating the load instruction is managed by the caller. - * @return the constant - */ - public Constant addConstant(Location location, Type type, String name, Consumer initializer) { - return addConstant(location, type.type, name, initializer); - } - - public Collection getConstants() { - return constants.values(); - } } - diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java index f3751892b97..b92bd6733c9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java @@ -29,11 +29,12 @@ import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.tree.TerminalNode; import org.elasticsearch.painless.CompilerSettings; -import org.elasticsearch.painless.Locals.ExecuteReserved; -import org.elasticsearch.painless.Locals.FunctionReserved; +import org.elasticsearch.painless.Globals; +import org.elasticsearch.painless.node.SFunction.Reserved; +import org.elasticsearch.painless.node.SSource.MainMethodReserved; +import org.elasticsearch.painless.node.SFunction.FunctionReserved; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Operation; -import org.elasticsearch.painless.Locals.Reserved; import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext; @@ -114,6 +115,7 @@ import org.elasticsearch.painless.node.EConditional; import org.elasticsearch.painless.node.EDecimal; import org.elasticsearch.painless.node.EExplicit; import org.elasticsearch.painless.node.EFunctionRef; +import org.elasticsearch.painless.node.ELambda; import org.elasticsearch.painless.node.ENull; import org.elasticsearch.painless.node.ENumeric; import org.elasticsearch.painless.node.EUnary; @@ -151,6 +153,7 @@ import org.objectweb.asm.util.Printer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Deque; import java.util.List; @@ -170,7 +173,7 @@ public final class Walker extends PainlessParserBaseVisitor { private final String sourceText; private final Deque reserved = new ArrayDeque<>(); - private final List synthetic = new ArrayList<>(); + private final Globals globals; private int syntheticCounter = 0; private Walker(String sourceName, String sourceText, CompilerSettings settings, Printer debugStream) { @@ -178,6 +181,7 @@ public final class Walker extends PainlessParserBaseVisitor { this.settings = settings; this.sourceName = Location.computeSourceName(sourceName, sourceText); this.sourceText = sourceText; + this.globals = new Globals(new BitSet(sourceText.length())); this.source = (SSource)visit(buildAntlrTree(sourceText)); } @@ -223,7 +227,7 @@ public final class Walker extends PainlessParserBaseVisitor { @Override public Object visitSource(SourceContext ctx) { - reserved.push(new ExecuteReserved()); + reserved.push(new MainMethodReserved()); List functions = new ArrayList<>(); @@ -236,11 +240,9 @@ public final class Walker extends PainlessParserBaseVisitor { for (StatementContext statement : ctx.statement()) { statements.add((AStatement)visit(statement)); } - - functions.addAll(synthetic); - return new SSource(sourceName, sourceText, debugStream, - (ExecuteReserved)reserved.pop(), location(ctx), functions, statements); + return new SSource(sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(), + location(ctx), functions, globals, statements); } @Override @@ -265,7 +267,8 @@ public final class Walker extends PainlessParserBaseVisitor { statements.add((AStatement)visit(statement)); } - return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, paramTypes, paramNames, statements, false); + return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, + paramTypes, paramNames, statements, false); } @Override @@ -977,10 +980,8 @@ public final class Walker extends PainlessParserBaseVisitor { } String name = nextLambda(); - synthetic.add(new SFunction((FunctionReserved)reserved.pop(), location(ctx), "def", name, - paramTypes, paramNames, statements, true)); - return new EFunctionRef(location(ctx), "this", name); - // TODO: use a real node for captures and shit + return new ELambda(name, (FunctionReserved)reserved.pop(), location(ctx), + paramTypes, paramNames, statements); } @Override @@ -1021,8 +1022,8 @@ public final class Walker extends PainlessParserBaseVisitor { new EChain(location, new LVariable(location, "size")))))); String name = nextLambda(); - synthetic.add(new SFunction(new FunctionReserved(), location, arrayType, name, - Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true)); + globals.addSyntheticMethod(new SFunction(new FunctionReserved(), location, arrayType, name, + Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true)); return new EFunctionRef(location(ctx), "this", name); } return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java index fbaaa83a6ad..eb5f73334db 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Type; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.Locals; @@ -111,7 +112,7 @@ public abstract class AExpression extends ANode { /** * Writes ASM based on the data collected during the analysis phase. */ - abstract void write(MethodWriter writer); + abstract void write(MethodWriter writer, Globals globals); /** * Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java index 6d52bfe0de1..f8081d0020b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition.Type; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -91,17 +92,17 @@ public abstract class ALink extends ANode { /** * Write values before a load/store occurs such as an array index. */ - abstract void write(MethodWriter writer); + abstract void write(MethodWriter writer, Globals globals); /** * Write a load for the specific link type. */ - abstract void load(MethodWriter writer); + abstract void load(MethodWriter writer, Globals globals); /** * Write a store for the specific link type. */ - abstract void store(MethodWriter writer); + abstract void store(MethodWriter writer, Globals globals); /** * Used to copy link data from one to another during analysis in the case of replacement. diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java index e34d174ac43..23210472b70 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java @@ -19,7 +19,9 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; +import org.elasticsearch.painless.Locals.Variable; import org.objectweb.asm.Label; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -88,7 +90,7 @@ public abstract class AStatement extends ANode { * Set to the loop counter variable slot as a shortcut if loop statements * are being counted. */ - int loopCounterSlot = -1; + Variable loopCounter = null; /** * Set to the approximate number of statements in a loop block to prevent @@ -120,5 +122,5 @@ public abstract class AStatement extends ANode { /** * Writes ASM based on the data collected during the analysis phase. */ - abstract void write(MethodWriter writer); + abstract void write(MethodWriter writer, Globals globals); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java index d3b92d7eb08..11b77b0441f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Location; @@ -604,7 +605,7 @@ public final class EBinary extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (promote.sort == Sort.STRING && operation == Operation.ADD) { @@ -612,13 +613,13 @@ public final class EBinary extends AExpression { writer.writeNewStrings(); } - left.write(writer); + left.write(writer, globals); if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) { writer.writeAppendStrings(left.actual); } - right.write(writer); + right.write(writer, globals); if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) { writer.writeAppendStrings(right.actual); @@ -628,14 +629,14 @@ public final class EBinary extends AExpression { writer.writeToStrings(); } } else if (operation == Operation.FIND) { - writeBuildMatcher(writer); + writeBuildMatcher(writer, globals); writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND); } else if (operation == Operation.MATCH) { - writeBuildMatcher(writer); + writeBuildMatcher(writer, globals); writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES); } else { - left.write(writer); - right.write(writer); + left.write(writer, globals); + right.write(writer, globals); if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) { writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false); @@ -647,9 +648,9 @@ public final class EBinary extends AExpression { writer.writeBranch(tru, fals); } - private void writeBuildMatcher(MethodWriter writer) { - right.write(writer); - left.write(writer); + private void writeBuildMatcher(MethodWriter writer, Globals globals) { + right.write(writer, globals); + left.write(writer, globals); writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java index 5aa2daeeb34..9e39ca712f5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Locals; @@ -67,7 +68,7 @@ public final class EBool extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { if (tru != null || fals != null) { if (operation == Operation.AND) { Label localfals = fals == null ? new Label() : fals; @@ -76,8 +77,8 @@ public final class EBool extends AExpression { right.tru = tru; right.fals = fals; - left.write(writer); - right.write(writer); + left.write(writer, globals); + right.write(writer, globals); if (fals == null) { writer.mark(localfals); @@ -89,8 +90,8 @@ public final class EBool extends AExpression { right.tru = tru; right.fals = fals; - left.write(writer); - right.write(writer); + left.write(writer, globals); + right.write(writer, globals); if (tru == null) { writer.mark(localtru); @@ -106,8 +107,8 @@ public final class EBool extends AExpression { left.fals = localfals; right.fals = localfals; - left.write(writer); - right.write(writer); + left.write(writer, globals); + right.write(writer, globals); writer.push(true); writer.goTo(end); @@ -122,8 +123,8 @@ public final class EBool extends AExpression { left.tru = localtru; right.fals = localfals; - left.write(writer); - right.write(writer); + left.write(writer, globals); + right.write(writer, globals); writer.mark(localtru); writer.push(true); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java index 877e79549af..a152422fac4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -41,7 +42,7 @@ public final class EBoolean extends AExpression { } @Override - void write(MethodWriter adapter) { + void write(MethodWriter adapter, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index af4e3779b65..333787db962 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -22,6 +22,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.FunctionRef; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Locals; @@ -37,7 +38,7 @@ import java.lang.invoke.LambdaMetafactory; /** * Represents a capturing function reference. */ -public class ECapturingFunctionRef extends AExpression { +public class ECapturingFunctionRef extends AExpression implements ILambda { public final String type; public final String call; @@ -79,21 +80,21 @@ public class ECapturingFunctionRef extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (defPointer != null) { // dynamic interface: push captured parameter on stack // TODO: don't do this: its just to cutover :) writer.push((String)null); - writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); + writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot()); } else if (ref == null) { // typed interface, dynamic implementation - writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); + writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot()); String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor(); writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name); } else { // typed interface, typed implementation - writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); + writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot()); // convert MethodTypes to asm Type for the constant pool. String invokedType = ref.invokedType.toMethodDescriptorString(); Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString()); @@ -119,4 +120,14 @@ public class ECapturingFunctionRef extends AExpression { } } } + + @Override + public String getPointer() { + return defPointer; + } + + @Override + public Type[] getCaptures() { + return new Type[] { captured.type.type }; + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java index 7892b918ed1..1073125c637 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Location; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -50,8 +51,8 @@ final class ECast extends AExpression { } @Override - void write(MethodWriter writer) { - child.write(writer); + void write(MethodWriter writer, Globals globals) { + child.write(writer, globals); writer.writeDebugInfo(location); writer.writeCast(cast); writer.writeBranch(tru, fals); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java index 3be7f068261..b249cf4511e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; @@ -286,7 +287,7 @@ public final class EChain extends AExpression { * 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...] */ @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); // For the case where the chain represents a String concatenation @@ -306,7 +307,7 @@ public final class EChain extends AExpression { // See individual links for more information on what each of the // write, load, and store methods do. for (ALink link : links) { - link.write(writer); // call the write method on the link to prepare for a load/store operation + link.write(writer, globals); // call the write method on the link to prepare for a load/store operation if (link == last && link.store) { if (cat) { @@ -314,10 +315,10 @@ public final class EChain extends AExpression { // representing a String concatenation. writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack - link.load(writer); // read the current link's value + link.load(writer, globals); // read the current link's value writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder - expression.write(writer); // write the bytecode for the rhs expression + expression.write(writer, globals); // write the bytecode for the rhs expression if (!(expression instanceof EBinary) || ((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) { @@ -331,13 +332,13 @@ public final class EChain extends AExpression { writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack } - link.store(writer); // store the link's value from the stack in its respective variable/field/array + link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array } else if (operation != null) { // Handle the case where we are doing a compound assignment that // does not represent a String concatenation. writer.writeDup(link.size, 0); // if necessary, dup the previous link's value to be both loaded from and stored to - link.load(writer); // load the current link's value + link.load(writer, globals); // load the current link's value if (link.load && post) { writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also @@ -346,7 +347,7 @@ public final class EChain extends AExpression { writer.writeCast(there); // if necessary cast the current link's value // to the promotion type between the lhs and rhs types - expression.write(writer); // write the bytecode for the rhs expression + expression.write(writer, globals); // write the bytecode for the rhs expression // XXX: fix these types, but first we need def compound assignment tests. // its tricky here as there are possibly explicit casts, too. // write the operation instruction for compound assignment @@ -364,22 +365,22 @@ public final class EChain extends AExpression { // read from and is not a post increment } - link.store(writer); // store the link's value from the stack in its respective variable/field/array + link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array } else { // Handle the case for a simple write. - expression.write(writer); // write the bytecode for the rhs expression + expression.write(writer, globals); // write the bytecode for the rhs expression if (link.load) { writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from } - link.store(writer); // store the link's value from the stack in its respective variable/field/array + link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array } } else { // Handle the case for a simple read. - link.load(writer); // read the link's value onto the stack + link.load(writer, globals); // read the link's value onto the stack } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java index 03c6351e95c..53684a19005 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Location; @@ -436,15 +437,15 @@ public final class EComp extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); boolean branch = tru != null || fals != null; - left.write(writer); + left.write(writer, globals); if (!right.isNull) { - right.write(writer); + right.write(writer, globals); } Label jump = tru != null ? tru : fals != null ? fals : new Label(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java index 024e0c800fc..600626348dd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.AnalyzerCaster; @@ -78,7 +79,7 @@ public final class EConditional extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); Label localfals = new Label(); @@ -88,11 +89,11 @@ public final class EConditional extends AExpression { left.tru = right.tru = tru; left.fals = right.fals = fals; - condition.write(writer); - left.write(writer); + condition.write(writer, globals); + left.write(writer, globals); writer.goTo(end); writer.mark(localfals); - right.write(writer); + right.write(writer, globals); writer.mark(end); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java index 267d32983b9..b037d25c2d6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; @@ -63,7 +64,7 @@ final class EConstant extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { Sort sort = actual.sort; switch (sort) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java index 0fd7fe46b51..efa453605cb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -57,7 +58,7 @@ public final class EDecimal extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java index 007a5d59e59..1c186faaeeb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -54,7 +55,7 @@ public final class EExplicit extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java index 7ab7703d02e..856a876550c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.FunctionRef; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Definition.Method; @@ -35,7 +36,7 @@ import java.lang.invoke.LambdaMetafactory; /** * Represents a function reference. */ -public class EFunctionRef extends AExpression { +public class EFunctionRef extends AExpression implements ILambda { public final String type; public final String call; @@ -83,7 +84,7 @@ public class EFunctionRef extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { if (ref != null) { writer.writeDebugInfo(location); // convert MethodTypes to asm Type for the constant pool. @@ -114,4 +115,14 @@ public class EFunctionRef extends AExpression { writer.push((String)null); } } + + @Override + public String getPointer() { + return defPointer; + } + + @Override + public Type[] getCaptures() { + return new Type[0]; // no captures + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index b513e3813c4..d0b531c74d3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -20,23 +20,33 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Locals; -import org.elasticsearch.painless.Locals.FunctionReserved; +import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; +import org.elasticsearch.painless.node.SFunction.FunctionReserved; +import org.elasticsearch.painless.Globals; +import org.objectweb.asm.Type; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class ELambda extends AExpression { +public class ELambda extends AExpression implements ILambda { + final String name; final FunctionReserved reserved; final List paramTypeStrs; final List paramNameStrs; final List statements; + // desugared synthetic method (lambda body) + SFunction desugared; + // method ref (impl detail) + ILambda impl; - public ELambda(FunctionReserved reserved, Location location, - List paramTypes, List paramNames, List statements) { + public ELambda(String name, FunctionReserved reserved, + Location location, List paramTypes, List paramNames, + List statements) { super(location); - + this.name = name; this.reserved = reserved; this.paramTypeStrs = Collections.unmodifiableList(paramTypes); this.paramNameStrs = Collections.unmodifiableList(paramNames); @@ -45,11 +55,40 @@ public class ELambda extends AExpression { @Override void analyze(Locals locals) { - throw createError(new UnsupportedOperationException("Lambda functions are not supported.")); + // desugar lambda body into a synthetic method + desugared = new SFunction(reserved, location, "def", name, + paramTypeStrs, paramNameStrs, statements, true); + desugared.generate(); + List captures = new ArrayList<>(); + desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters, captures)); + + // setup reference + EFunctionRef ref = new EFunctionRef(location, "this", name); + ref.expected = expected; + // hack, create a new scope, with our method, so the ref can see it (impl detail) + locals = Locals.newLocalScope(locals); + locals.addMethod(desugared.method); + ref.analyze(locals); + actual = ref.actual; + impl = ref; } @Override - void write(MethodWriter writer) { - throw createError(new IllegalStateException("Illegal tree structure.")); + void write(MethodWriter writer, Globals globals) { + AExpression expr = (AExpression) impl; + expr.write(writer, globals); + // add synthetic method to the queue to be written + globals.addSyntheticMethod(desugared); } + + @Override + public String getPointer() { + return impl.getPointer(); + } + + @Override + public Type[] getCaptures() { + return impl.getCaptures(); + } + } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java index eefc09c5946..0a1242e6507 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Opcodes; @@ -50,7 +51,7 @@ public final class ENull extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.visitInsn(Opcodes.ACONST_NULL); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java index 9abdc8f6a12..e2314880448 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Locals; @@ -96,7 +97,7 @@ public final class ENumeric extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java index f4f73e6101b..f74b91dd5bc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; @@ -177,7 +178,7 @@ public final class EUnary extends AExpression { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (operation == Operation.NOT) { @@ -186,7 +187,7 @@ public final class EUnary extends AExpression { Label end = new Label(); child.fals = localfals; - child.write(writer); + child.write(writer, globals); writer.push(false); writer.goTo(end); @@ -196,11 +197,11 @@ public final class EUnary extends AExpression { } else { child.tru = fals; child.fals = tru; - child.write(writer); + child.write(writer, globals); } } else { Sort sort = promote.sort; - child.write(writer); + child.write(writer, globals); if (operation == Operation.BWNOT) { if (sort == Sort.DEF) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java new file mode 100644 index 00000000000..5a279b3a162 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.node; + +/** + * Interface for lambda/method reference nodes. They need special handling by LDefCall. + *

+ * This is because they know nothing about the target interface, and can only push + * all their captures onto the stack and defer everything until link-time. + */ +interface ILambda { + + /** Returns reference to resolve at link-time */ + String getPointer(); + + /** Returns the types of captured parameters. Can be empty */ + org.objectweb.asm.Type[] getCaptures(); + + /** Returns the number of captured parameters */ + default int getCaptureCount() { + return getCaptures().length; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java index 38c1ae43907..1a240747d73 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -55,18 +56,18 @@ public final class LArrayLength extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.arrayLength(); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java index 91346b7a208..e8c23bf3857 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Locals; @@ -69,18 +70,18 @@ public final class LBrace extends ALink { } @Override - void write(MethodWriter writer) { - index.write(writer); + void write(MethodWriter writer, Globals globals) { + index.write(writer, globals); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.arrayLoad(after.type); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.arrayStore(after.type); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java index 1f6e899b1dd..34ad0343aa9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java @@ -24,6 +24,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Struct; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -86,16 +87,16 @@ public final class LCallInvoke extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); for (AExpression argument : arguments) { - argument.write(writer); + argument.write(writer, globals); } if (java.lang.reflect.Modifier.isStatic(method.modifiers)) { @@ -108,7 +109,7 @@ public final class LCallInvoke extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java index 31085171bf6..4503692b48d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -77,23 +78,23 @@ public class LCallLocal extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); for (AExpression argument : arguments) { - argument.write(writer); + argument.write(writer, globals); } writer.invokeStatic(CLASS_TYPE, method.method); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java index d2b4f83a823..bf900cedafc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.AnalyzerCaster; @@ -61,18 +62,18 @@ public final class LCast extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.writeCast(cast); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java index 5a5333aabe8..02fbb2000f2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Locals; @@ -53,12 +54,12 @@ final class LDefArray extends ALink implements IDefLink { } @Override - void write(MethodWriter writer) { - index.write(writer); + void write(MethodWriter writer, Globals globals) { + index.write(writer, globals); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type); @@ -66,7 +67,7 @@ final class LDefArray extends ALink implements IDefLink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java index a2c2f150840..716b26d6687 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java @@ -20,10 +20,12 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; +import org.objectweb.asm.Type; import java.util.ArrayList; import java.util.List; @@ -64,13 +66,11 @@ final class LDefCall extends ALink implements IDefLink { expression.internal = true; expression.analyze(locals); - if (expression instanceof EFunctionRef) { - pointers.add(((EFunctionRef)expression).defPointer); + if (expression instanceof ILambda) { + ILambda lambda = (ILambda) expression; + pointers.add(lambda.getPointer()); recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference - } else if (expression instanceof ECapturingFunctionRef) { - pointers.add(((ECapturingFunctionRef)expression).defPointer); - recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference - totalCaptures++; + totalCaptures += lambda.getCaptureCount(); } expression.expected = expression.actual; @@ -84,12 +84,12 @@ final class LDefCall extends ALink implements IDefLink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); StringBuilder signature = new StringBuilder(); @@ -100,11 +100,13 @@ final class LDefCall extends ALink implements IDefLink { for (AExpression argument : arguments) { signature.append(argument.actual.type.getDescriptor()); - if (argument instanceof ECapturingFunctionRef) { - ECapturingFunctionRef capturingRef = (ECapturingFunctionRef) argument; - signature.append(capturingRef.captured.type.type.getDescriptor()); + if (argument instanceof ILambda) { + ILambda lambda = (ILambda) argument; + for (Type capture : lambda.getCaptures()) { + signature.append(capture.getDescriptor()); + } } - argument.write(writer); + argument.write(writer, globals); } signature.append(')'); @@ -119,7 +121,7 @@ final class LDefCall extends ALink implements IDefLink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java index 09b48a94f86..125b2bee6fc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Locals; @@ -50,12 +51,12 @@ final class LDefField extends ALink implements IDefLink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type); @@ -63,7 +64,7 @@ final class LDefField extends ALink implements IDefLink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java index 049dd2d8524..9fc48c009a2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Field; import org.elasticsearch.painless.Definition.Sort; @@ -100,12 +101,12 @@ public final class LField extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { @@ -116,7 +117,7 @@ public final class LField extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java index 7b0feb094e1..1cd5620cc17 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Sort; @@ -74,12 +75,12 @@ final class LListShortcut extends ALink { } @Override - void write(MethodWriter writer) { - index.write(writer); + void write(MethodWriter writer, Globals globals) { + index.write(writer, globals); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { @@ -94,7 +95,7 @@ final class LListShortcut extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java index 5d4c5a9e50a..05c2192b870 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Sort; @@ -73,12 +74,12 @@ final class LMapShortcut extends ALink { } @Override - void write(MethodWriter writer) { - index.write(writer); + void write(MethodWriter writer, Globals globals) { + index.write(writer, globals); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { @@ -93,7 +94,7 @@ final class LMapShortcut extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java index 15aa01c1d26..312cbf925e1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Locals; @@ -74,16 +75,16 @@ public final class LNewArray extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); for (AExpression argument : arguments) { - argument.write(writer); + argument.write(writer, globals); } if (arguments.size() > 1) { @@ -94,7 +95,7 @@ public final class LNewArray extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java index aeb6f64f9db..0b28a308e19 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Struct; @@ -93,12 +94,12 @@ public final class LNewObj extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.newInstance(after.type); @@ -107,14 +108,14 @@ public final class LNewObj extends ALink { } for (AExpression argument : arguments) { - argument.write(writer); + argument.write(writer, globals); } writer.invokeConstructor(constructor.owner.type, constructor.method); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LRegex.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LRegex.java index 324b6d72cf1..8727f992242 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LRegex.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LRegex.java @@ -19,9 +19,10 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; -import org.elasticsearch.painless.Locals.Constant; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -64,25 +65,26 @@ public final class LRegex extends ALink { throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "].")); } - constant = locals.addConstant(location, Definition.PATTERN_TYPE, "regexAt$" + location.getOffset(), this::initializeConstant); + constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant); after = Definition.PATTERN_TYPE; return this; } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type); + globals.addConstantInitializer(constant); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java index 73cbc201db4..efaf02bb601 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Sort; @@ -79,12 +80,12 @@ final class LShortcut extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { @@ -99,7 +100,7 @@ final class LShortcut extends ALink { } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java index 6f04e9a22c4..3a7c9a35fe5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Locals; @@ -54,17 +55,17 @@ public final class LStatic extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java index 41eb027e3ab..fac9d413ad7 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -51,17 +52,17 @@ public final class LString extends ALink { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { + void load(MethodWriter writer, Globals globals) { writer.push(string); } @Override - void store(MethodWriter writer) { + void store(MethodWriter writer, Globals globals) { throw createError(new IllegalStateException("Illegal tree structure.")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java index 64dc852117d..ad9ba9dfbf6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java @@ -21,6 +21,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals.Variable; import org.objectweb.asm.Opcodes; @@ -32,7 +33,7 @@ public final class LVariable extends ALink { final String name; - int slot; + Variable variable; public LVariable(Location location, String name) { super(location, 0); @@ -46,30 +47,29 @@ public final class LVariable extends ALink { throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined.")); } - Variable variable = locals.getVariable(location, name); + variable = locals.getVariable(location, name); if (store && variable.readonly) { throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only.")); } - slot = variable.slot; after = variable.type; return this; } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { // Do nothing. } @Override - void load(MethodWriter writer) { - writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), slot); + void load(MethodWriter writer, Globals globals) { + writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), variable.getSlot()); } @Override - void store(MethodWriter writer) { - writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), slot); + void store(MethodWriter writer, Globals globals) { + writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), variable.getSlot()); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java index 5fec362ec17..7d459f27504 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -70,11 +71,11 @@ public final class SBlock extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { for (AStatement statement : statements) { statement.continu = continu; statement.brake = brake; - statement.write(writer); + statement.write(writer, globals); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java index d4f8bfff4e4..c791faeadd6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -45,7 +46,7 @@ public final class SBreak extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.goTo(brake); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java index 42de28e48f5..8632c0eea6d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Locals; @@ -65,7 +66,7 @@ public final class SCatch extends AStatement { throw createError(new ClassCastException("Not an exception type [" + this.type + "].")); } - variable = locals.addVariable(location, type, name, true, false); + variable = locals.addVariable(location, type, name, true); if (block != null) { block.lastSource = lastSource; @@ -84,18 +85,18 @@ public final class SCatch extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label jump = new Label(); writer.mark(jump); - writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); + writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot()); if (block != null) { block.continu = continu; block.brake = brake; - block.write(writer); + block.write(writer, globals); } writer.visitTryCatchBlock(begin, end, jump, variable.type.type.getInternalName()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java index 802cf90087b..e70f625bb3d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -48,7 +49,7 @@ public final class SContinue extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.goTo(continu); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java index 5365e72888a..58ff09e45d8 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -49,9 +50,9 @@ public final class SDeclBlock extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { for (AStatement declaration : declarations) { - declaration.write(writer); + declaration.write(writer, globals); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java index a62a287df9c..c19adfd699e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Locals; @@ -62,11 +63,11 @@ public final class SDeclaration extends AStatement { expression = expression.cast(locals); } - variable = locals.addVariable(location, type, name, false, false); + variable = locals.addVariable(location, type, name, false); } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); if (expression == null) { @@ -83,9 +84,9 @@ public final class SDeclaration extends AStatement { default: writer.visitInsn(Opcodes.ACONST_NULL); } } else { - expression.write(writer); + expression.write(writer, globals); } - writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); + writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot()); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java index b25d903dc41..3b94f5799ea 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; @@ -42,7 +43,7 @@ public final class SDo extends AStatement { @Override void analyze(Locals locals) { - locals.incrementScope(); + locals = Locals.newLocalScope(locals); if (block == null) { throw createError(new IllegalArgumentException("Extraneous do while loop.")); @@ -76,15 +77,13 @@ public final class SDo extends AStatement { statementCount = 1; - if (locals.getMaxLoopCounter() > 0) { - loopCounterSlot = locals.getVariable(location, "#loop").slot; + if (locals.hasVariable(Locals.LOOP)) { + loopCounter = locals.getVariable(location, Locals.LOOP); } - - locals.decrementScope(); } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label start = new Label(); @@ -95,14 +94,14 @@ public final class SDo extends AStatement { block.continu = begin; block.brake = end; - block.write(writer); + block.write(writer, globals); writer.mark(begin); condition.fals = end; - condition.write(writer); + condition.write(writer, globals); - writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location); + writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location); writer.goTo(start); writer.mark(end); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index 48a3f6e3eae..403b377ea9c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -22,6 +22,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; @@ -85,9 +86,9 @@ public class SEach extends AStatement { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } - locals.incrementScope(); + locals = Locals.newLocalScope(locals); - variable = locals.addVariable(location, type, name, true, false); + variable = locals.addVariable(location, type, name, true); if (expression.actual.sort == Sort.ARRAY) { analyzeArray(locals, type); @@ -112,18 +113,16 @@ public class SEach extends AStatement { statementCount = 1; - if (locals.getMaxLoopCounter() > 0) { - loopCounterSlot = locals.getVariable(location, "#loop").slot; + if (locals.hasVariable(Locals.LOOP)) { + loopCounter = locals.getVariable(location, Locals.LOOP); } - - locals.decrementScope(); } void analyzeArray(Locals variables, Type type) { // We must store the array and index as variables for securing slots on the stack, and // also add the location offset to make the names unique in case of nested for each loops. - array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true, false); - index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true, false); + array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true); + index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true); indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1); cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true); } @@ -131,7 +130,7 @@ public class SEach extends AStatement { void analyzeIterable(Locals variables, Type type) { // We must store the iterator as a variable for securing a slot on the stack, and // also add the location offset to make the name unique in case of nested for each loops. - iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true, false); + iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true); if (expression.actual.sort == Sort.DEF) { method = null; @@ -148,49 +147,49 @@ public class SEach extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); if (array != null) { - writeArray(writer); + writeArray(writer, globals); } else if (iterator != null) { - writeIterable(writer); + writeIterable(writer, globals); } else { throw createError(new IllegalStateException("Illegal tree structure.")); } } - void writeArray(MethodWriter writer) { - expression.write(writer); - writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.slot); + void writeArray(MethodWriter writer, Globals globals) { + expression.write(writer, globals); + writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot()); writer.push(-1); - writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.slot); + writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot()); Label begin = new Label(); Label end = new Label(); writer.mark(begin); - writer.visitIincInsn(index.slot, 1); - writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot); - writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot); + writer.visitIincInsn(index.getSlot(), 1); + writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot()); + writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot()); writer.arrayLength(); writer.ifICmp(MethodWriter.GE, end); - writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot); - writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot); + writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot()); + writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot()); writer.arrayLoad(indexed.type); writer.writeCast(cast); - writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); + writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot()); - block.write(writer); + block.write(writer, globals); writer.goTo(begin); writer.mark(end); } - void writeIterable(MethodWriter writer) { - expression.write(writer); + void writeIterable(MethodWriter writer, Globals globals) { + expression.write(writer, globals); if (method == null) { Type itr = Definition.getType("Iterator"); @@ -202,23 +201,23 @@ public class SEach extends AStatement { writer.invokeVirtual(method.owner.type, method.method); } - writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.slot); + writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot()); Label begin = new Label(); Label end = new Label(); writer.mark(begin); - writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot); + writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot()); writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT); writer.ifZCmp(MethodWriter.EQ, end); - writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot); + writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot()); writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT); writer.writeCast(cast); - writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); + writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot()); - block.write(writer); + block.write(writer, globals); writer.goTo(begin); writer.mark(end); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java index 8817db95be6..c6cf0a47c8f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java @@ -22,6 +22,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Definition.Sort; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -63,9 +64,9 @@ public final class SExpression extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); - expression.write(writer); + expression.write(writer, globals); if (methodEscape) { writer.returnValue(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java index cc9dee20138..b2cf1da2594 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; @@ -46,7 +47,7 @@ public final class SFor extends AStatement { @Override void analyze(Locals locals) { - locals.incrementScope(); + locals = Locals.newLocalScope(locals); boolean continuous = false; @@ -116,15 +117,13 @@ public final class SFor extends AStatement { statementCount = 1; - if (locals.getMaxLoopCounter() > 0) { - loopCounterSlot = locals.getVariable(location, "#loop").slot; + if (locals.hasVariable(Locals.LOOP)) { + loopCounter = locals.getVariable(location, Locals.LOOP); } - - locals.decrementScope(); } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label start = new Label(); @@ -132,11 +131,11 @@ public final class SFor extends AStatement { Label end = new Label(); if (initializer instanceof SDeclBlock) { - ((SDeclBlock)initializer).write(writer); + ((SDeclBlock)initializer).write(writer, globals); } else if (initializer instanceof AExpression) { AExpression initializer = (AExpression)this.initializer; - initializer.write(writer); + initializer.write(writer, globals); writer.writePop(initializer.expected.sort.size); } @@ -144,7 +143,7 @@ public final class SFor extends AStatement { if (condition != null) { condition.fals = end; - condition.write(writer); + condition.write(writer, globals); } boolean allEscape = false; @@ -158,15 +157,15 @@ public final class SFor extends AStatement { ++statementCount; } - writer.writeLoopCounter(loopCounterSlot, statementCount, location); - block.write(writer); + writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location); + block.write(writer, globals); } else { - writer.writeLoopCounter(loopCounterSlot, 1, location); + writer.writeLoopCounter(loopCounter.getSlot(), 1, location); } if (afterthought != null) { writer.mark(begin); - afterthought.write(writer); + afterthought.write(writer, globals); } if (afterthought != null || !allEscape) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 60015844a96..8d3db7f811a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -19,27 +19,26 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Def; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals.Parameter; -import org.elasticsearch.painless.Locals.FunctionReserved; import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.WriterConstants; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import java.lang.invoke.MethodType; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collections; import java.util.List; @@ -51,17 +50,17 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE; public class SFunction extends AStatement { final FunctionReserved reserved; final String rtnTypeStr; - final String name; + public final String name; final List paramTypeStrs; final List paramNameStrs; final List statements; - final boolean synthetic; + public final boolean synthetic; Type rtnType = null; List parameters = new ArrayList<>(); Method method = null; - Locals locals = null; + Variable loop = null; public SFunction(FunctionReserved reserved, Location location, String rtnType, String name, List paramTypes, @@ -115,10 +114,7 @@ public class SFunction extends AStatement { throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "].")); } - this.locals = new Locals(reserved, locals, rtnType, parameters); - locals = this.locals; - - locals.incrementScope(); + locals = Locals.newLocalScope(locals); AStatement last = statements.get(statements.size() - 1); @@ -141,37 +137,33 @@ public class SFunction extends AStatement { throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "].")); } - locals.decrementScope(); - - String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size()); - locals.addConstant(location, WriterConstants.METHOD_HANDLE_TYPE, staticHandleFieldName, this::initializeConstant); + if (reserved.getMaxLoopCounter() > 0) { + loop = locals.getVariable(null, FunctionReserved.LOOP); + } } /** Writes the function to given ClassVisitor. */ - void write (ClassVisitor writer, BitSet statements) { + void write (ClassVisitor writer, Globals globals) { int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; if (synthetic) { access |= Opcodes.ACC_SYNTHETIC; } - final MethodWriter function = new MethodWriter(access, method.method, writer, statements); - write(function); + final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements()); + write(function, globals); function.endMethod(); } @Override - void write(MethodWriter function) { + void write(MethodWriter function, Globals globals) { if (reserved.getMaxLoopCounter() > 0) { // if there is infinite loop protection, we do this once: // int #loop = settings.getMaxLoopCounter() - - Variable loop = locals.getVariable(null, FunctionReserved.LOOP); - function.push(reserved.getMaxLoopCounter()); - function.visitVarInsn(Opcodes.ISTORE, loop.slot); + function.visitVarInsn(Opcodes.ISTORE, loop.getSlot()); } for (AStatement statement : statements) { - statement.write(function); + statement.write(function, globals); } if (!methodEscape) { @@ -181,6 +173,10 @@ public class SFunction extends AStatement { throw createError(new IllegalStateException("Illegal tree structure.")); } } + + String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size()); + globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE, + staticHandleFieldName, this::initializeConstant)); } private void initializeConstant(MethodWriter writer) { @@ -191,4 +187,43 @@ public class SFunction extends AStatement { false); writer.push(handle); } + + /** + * Tracks reserved variables. Must be given to any source of input + * prior to beginning the analysis phase so that reserved variables + * are known ahead of time to assign appropriate slots without + * being wasteful. + */ + public interface Reserved { + void markReserved(String name); + boolean isReserved(String name); + + void setMaxLoopCounter(int max); + int getMaxLoopCounter(); + } + + public static final class FunctionReserved implements Reserved { + public static final String THIS = "#this"; + public static final String LOOP = "#loop"; + + private int maxLoopCounter = 0; + + public void markReserved(String name) { + // Do nothing. + } + + public boolean isReserved(String name) { + return name.equals(THIS) || name.equals(LOOP); + } + + @Override + public void setMaxLoopCounter(int max) { + maxLoopCounter = max; + } + + @Override + public int getMaxLoopCounter() { + return maxLoopCounter; + } + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java index a46075af9a1..1b0c0ffb348 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; @@ -58,9 +59,7 @@ public final class SIf extends AStatement { ifblock.inLoop = inLoop; ifblock.lastLoop = lastLoop; - locals.incrementScope(); - ifblock.analyze(locals); - locals.decrementScope(); + ifblock.analyze(Locals.newLocalScope(locals)); anyContinue = ifblock.anyContinue; anyBreak = ifblock.anyBreak; @@ -68,17 +67,17 @@ public final class SIf extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label fals = new Label(); condition.fals = fals; - condition.write(writer); + condition.write(writer, globals); ifblock.continu = continu; ifblock.brake = brake; - ifblock.write(writer); + ifblock.write(writer, globals); writer.mark(fals); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java index 22cbfe25614..612969acfff 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; @@ -60,9 +61,7 @@ public final class SIfElse extends AStatement { ifblock.inLoop = inLoop; ifblock.lastLoop = lastLoop; - locals.incrementScope(); - ifblock.analyze(locals); - locals.decrementScope(); + ifblock.analyze(Locals.newLocalScope(locals)); anyContinue = ifblock.anyContinue; anyBreak = ifblock.anyBreak; @@ -76,9 +75,7 @@ public final class SIfElse extends AStatement { elseblock.inLoop = inLoop; elseblock.lastLoop = lastLoop; - locals.incrementScope(); - elseblock.analyze(locals); - locals.decrementScope(); + elseblock.analyze(Locals.newLocalScope(locals)); methodEscape = ifblock.methodEscape && elseblock.methodEscape; loopEscape = ifblock.loopEscape && elseblock.loopEscape; @@ -89,18 +86,18 @@ public final class SIfElse extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label end = new Label(); Label fals = elseblock != null ? new Label() : end; condition.fals = fals; - condition.write(writer); + condition.write(writer, globals); ifblock.continu = continu; ifblock.brake = brake; - ifblock.write(writer); + ifblock.write(writer, globals); if (!ifblock.allEscape) { writer.goTo(end); @@ -110,7 +107,7 @@ public final class SIfElse extends AStatement { elseblock.continu = continu; elseblock.brake = brake; - elseblock.write(writer); + elseblock.write(writer, globals); writer.mark(end); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java index e789f3372e3..2c46b3ecfaf 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Location; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -51,9 +52,9 @@ public final class SReturn extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); - expression.write(writer); + expression.write(writer, globals); writer.returnValue(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java index 1d3bb74bc31..5e932072ce1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java @@ -19,13 +19,14 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Executable; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; -import org.elasticsearch.painless.Locals.Constant; -import org.elasticsearch.painless.Locals.ExecuteReserved; import org.elasticsearch.painless.Locals.Variable; +import org.elasticsearch.painless.node.SFunction.Reserved; import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -35,7 +36,9 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.TraceClassVisitor; +import java.util.ArrayList; import java.util.BitSet; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -56,24 +59,28 @@ public final class SSource extends AStatement { final String name; final String source; final Printer debugStream; - final ExecuteReserved reserved; + final MainMethodReserved reserved; final List functions; + final Globals globals; final List statements; - private Locals locals; - private BitSet expressions; + private Locals mainMethod; private byte[] bytes; - public SSource(String name, String source, Printer debugStream, ExecuteReserved reserved, Location location, - List functions, List statements) { + public SSource(String name, String source, Printer debugStream, MainMethodReserved reserved, Location location, + List functions, Globals globals, List statements) { super(location); this.name = name; this.source = source; this.debugStream = debugStream; this.reserved = reserved; + // process any synthetic functions generated by walker (because right now, thats still easy) + functions.addAll(globals.getSyntheticMethods().values()); + globals.getSyntheticMethods().clear(); this.functions = Collections.unmodifiableList(functions); this.statements = Collections.unmodifiableList(statements); + this.globals = globals; } public void analyze() { @@ -89,21 +96,22 @@ public final class SSource extends AStatement { } } - locals = new Locals(reserved, methods); - analyze(locals); + analyze(Locals.newProgramScope(methods.values())); } @Override - void analyze(Locals locals) { + void analyze(Locals program) { for (SFunction function : functions) { - function.analyze(locals); + Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters, + function.reserved.getMaxLoopCounter()); + function.analyze(functionLocals); } if (statements == null || statements.isEmpty()) { throw createError(new IllegalArgumentException("Cannot generate an empty script.")); } - locals.incrementScope(); + mainMethod = Locals.newMainMethodScope(program, reserved.usesScore(), reserved.usesCtx(), reserved.getMaxLoopCounter()); AStatement last = statements.get(statements.size() - 1); @@ -116,13 +124,11 @@ public final class SSource extends AStatement { statement.lastSource = statement == last; - statement.analyze(locals); + statement.analyze(mainMethod); methodEscape = statement.methodEscape; allEscape = statement.allEscape; } - - locals.decrementScope(); } public void write() { @@ -144,10 +150,8 @@ public final class SSource extends AStatement { visitor.visit(classVersion, classAccess, className, null, classBase, classInterfaces); visitor.visitSource(Location.computeSourceName(name, source), null); - expressions = new BitSet(source.length()); - // Write the constructor: - MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, expressions); + MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements()); constructor.visitCode(); constructor.loadThis(); constructor.loadArgs(); @@ -156,20 +160,31 @@ public final class SSource extends AStatement { constructor.endMethod(); // Write the execute method: - MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, expressions); + MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements()); execute.visitCode(); - write(execute); + write(execute, globals); execute.endMethod(); // Write all functions: for (SFunction function : functions) { - function.write(visitor, expressions); + function.write(visitor, globals); + } + + // Write all synthetic functions. Note that this process may add more :) + while (!globals.getSyntheticMethods().isEmpty()) { + List current = new ArrayList<>(globals.getSyntheticMethods().values()); + globals.getSyntheticMethods().clear(); + for (SFunction function : current) { + function.write(visitor, globals); + } } // Write the constants - if (false == locals.getConstants().isEmpty()) { + if (false == globals.getConstantInitializers().isEmpty()) { + Collection inits = globals.getConstantInitializers().values(); + // Fields - for (Constant constant : locals.getConstants()) { + for (Constant constant : inits) { visitor.visitField( Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, constant.name, @@ -180,8 +195,8 @@ public final class SSource extends AStatement { // Initialize the constants in a static initializer final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC, - WriterConstants.CLINIT, visitor, expressions); - for (Constant constant : locals.getConstants()) { + WriterConstants.CLINIT, visitor, globals.getStatements()); + for (Constant constant : inits) { constant.initializer.accept(clinit); clinit.putStatic(CLASS_TYPE, constant.name, constant.type); } @@ -196,44 +211,44 @@ public final class SSource extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { if (reserved.usesScore()) { // if the _score value is used, we do this once: // final double _score = scorer.score(); - Variable scorer = locals.getVariable(null, ExecuteReserved.SCORER); - Variable score = locals.getVariable(null, ExecuteReserved.SCORE); + Variable scorer = mainMethod.getVariable(null, Locals.SCORER); + Variable score = mainMethod.getVariable(null, Locals.SCORE); - writer.visitVarInsn(Opcodes.ALOAD, scorer.slot); + writer.visitVarInsn(Opcodes.ALOAD, scorer.getSlot()); writer.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE); writer.visitInsn(Opcodes.F2D); - writer.visitVarInsn(Opcodes.DSTORE, score.slot); + writer.visitVarInsn(Opcodes.DSTORE, score.getSlot()); } if (reserved.usesCtx()) { // if the _ctx value is used, we do this once: // final Map ctx = input.get("ctx"); - Variable input = locals.getVariable(null, ExecuteReserved.PARAMS); - Variable ctx = locals.getVariable(null, ExecuteReserved.CTX); + Variable input = mainMethod.getVariable(null, Locals.PARAMS); + Variable ctx = mainMethod.getVariable(null, Locals.CTX); - writer.visitVarInsn(Opcodes.ALOAD, input.slot); - writer.push(ExecuteReserved.CTX); + writer.visitVarInsn(Opcodes.ALOAD, input.getSlot()); + writer.push(Locals.CTX); writer.invokeInterface(MAP_TYPE, MAP_GET); - writer.visitVarInsn(Opcodes.ASTORE, ctx.slot); + writer.visitVarInsn(Opcodes.ASTORE, ctx.getSlot()); } if (reserved.getMaxLoopCounter() > 0) { // if there is infinite loop protection, we do this once: // int #loop = settings.getMaxLoopCounter() - Variable loop = locals.getVariable(null, ExecuteReserved.LOOP); + Variable loop = mainMethod.getVariable(null, Locals.LOOP); writer.push(reserved.getMaxLoopCounter()); - writer.visitVarInsn(Opcodes.ISTORE, loop.slot); + writer.visitVarInsn(Opcodes.ISTORE, loop.getSlot()); } for (AStatement statement : statements) { - statement.write(writer); + statement.write(writer, globals); } if (!methodEscape) { @@ -242,11 +257,50 @@ public final class SSource extends AStatement { } } - public BitSet getExpressions() { - return expressions; + public BitSet getStatements() { + return globals.getStatements(); } public byte[] getBytes() { return bytes; } + + + public static final class MainMethodReserved implements Reserved { + private boolean score = false; + private boolean ctx = false; + private int maxLoopCounter = 0; + + @Override + public void markReserved(String name) { + if (Locals.SCORE.equals(name)) { + score = true; + } else if (Locals.CTX.equals(name)) { + ctx = true; + } + } + + @Override + public boolean isReserved(String name) { + return Locals.KEYWORDS.contains(name); + } + + public boolean usesScore() { + return score; + } + + public boolean usesCtx() { + return ctx; + } + + @Override + public void setMaxLoopCounter(int max) { + maxLoopCounter = max; + } + + @Override + public int getMaxLoopCounter() { + return maxLoopCounter; + } + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java index db04d622839..ffa1b89dfe2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.MethodWriter; @@ -50,9 +51,9 @@ public final class SThrow extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); - expression.write(writer); + expression.write(writer, globals); writer.throwException(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java index 42fffc759ce..0295d32ae35 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java @@ -19,6 +19,7 @@ package org.elasticsearch.painless.node; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; import org.elasticsearch.painless.Location; @@ -52,9 +53,7 @@ public final class STry extends AStatement { block.inLoop = inLoop; block.lastLoop = lastLoop; - locals.incrementScope(); - block.analyze(locals); - locals.decrementScope(); + block.analyze(Locals.newLocalScope(locals)); methodEscape = block.methodEscape; loopEscape = block.loopEscape; @@ -69,9 +68,7 @@ public final class STry extends AStatement { catc.inLoop = inLoop; catc.lastLoop = lastLoop; - locals.incrementScope(); - catc.analyze(locals); - locals.decrementScope(); + catc.analyze(Locals.newLocalScope(locals)); methodEscape &= catc.methodEscape; loopEscape &= catc.loopEscape; @@ -86,7 +83,7 @@ public final class STry extends AStatement { } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label begin = new Label(); @@ -97,7 +94,7 @@ public final class STry extends AStatement { block.continu = continu; block.brake = brake; - block.write(writer); + block.write(writer, globals); if (!block.allEscape) { writer.goTo(exception); @@ -109,7 +106,7 @@ public final class STry extends AStatement { catc.begin = begin; catc.end = end; catc.exception = catches.size() > 1 ? exception : null; - catc.write(writer); + catc.write(writer, globals); } if (!block.allEscape || catches.size() > 1) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java index 20478a55aa0..39b48781b46 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Definition; +import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; import org.objectweb.asm.Label; @@ -42,7 +43,7 @@ public final class SWhile extends AStatement { @Override void analyze(Locals locals) { - locals.incrementScope(); + locals = Locals.newLocalScope(locals); condition.expected = Definition.BOOLEAN_TYPE; condition.analyze(locals); @@ -82,15 +83,13 @@ public final class SWhile extends AStatement { statementCount = 1; - if (locals.getMaxLoopCounter() > 0) { - loopCounterSlot = locals.getVariable(location, "#loop").slot; + if (locals.hasVariable(Locals.LOOP)) { + loopCounter = locals.getVariable(location, Locals.LOOP); } - - locals.decrementScope(); } @Override - void write(MethodWriter writer) { + void write(MethodWriter writer, Globals globals) { writer.writeStatementOffset(location); Label begin = new Label(); @@ -99,16 +98,16 @@ public final class SWhile extends AStatement { writer.mark(begin); condition.fals = end; - condition.write(writer); + condition.write(writer, globals); if (block != null) { - writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location); + writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location); block.continu = begin; block.brake = end; - block.write(writer); + block.write(writer, globals); } else { - writer.writeLoopCounter(loopCounterSlot, 1, location); + writer.writeLoopCounter(loopCounter.getSlot(), 1, location); } if (block == null || !block.allEscape) { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java index e1dbe9db0f7..fdc4fba4313 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java @@ -30,7 +30,7 @@ public class ReservedWordTests extends ScriptTestCase { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("int _score = 5; return _score;"); }); - assertTrue(expected.getMessage().contains("Variable [_score] is reserved")); + assertTrue(expected.getMessage().contains("Variable [_score] is already defined")); } /** check that we can't write to _score, its read-only! */ @@ -46,7 +46,7 @@ public class ReservedWordTests extends ScriptTestCase { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("int doc = 5; return doc;"); }); - assertTrue(expected.getMessage().contains("Variable [doc] is reserved")); + assertTrue(expected.getMessage().contains("Variable [doc] is already defined")); } /** check that we can't write to doc, its read-only! */ @@ -62,7 +62,7 @@ public class ReservedWordTests extends ScriptTestCase { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("int ctx = 5; return ctx;"); }); - assertTrue(expected.getMessage().contains("Variable [ctx] is reserved")); + assertTrue(expected.getMessage().contains("Variable [ctx] is already defined")); } /** check that we can't write to ctx, its read-only! */ @@ -83,7 +83,7 @@ public class ReservedWordTests extends ScriptTestCase { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("int _value = 5; return _value;"); }); - assertTrue(expected.getMessage().contains("Variable [_value] is reserved")); + assertTrue(expected.getMessage().contains("Variable [_value] is already defined")); } /** check that we can't write to _value, its read-only! */