Add functions to Painless.

Closes #18810
This commit is contained in:
Jack Conradson 2016-06-10 13:59:59 -07:00
parent 86f1bedaab
commit f98be6fa06
67 changed files with 1723 additions and 1055 deletions

View File

@ -22,7 +22,15 @@ parser grammar PainlessParser;
options { tokenVocab=PainlessLexer; }
source
: statement* EOF
: function* statement* EOF
;
function
: decltype ID parameters block
;
parameters
: LP ( decltype ID ( COMMA decltype ID )* )? RP
;
// Note we use a predicate on the if/else case here to prevent the
@ -143,6 +151,7 @@ primary[boolean c] returns [boolean s = true]
| { $c }? LP unary[true] RP # chainprec
| STRING # string
| ID # variable
| ID arguments # calllocal
| NEW TYPE arguments # newobject
;

View File

@ -1,37 +0,0 @@
/*
* 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.Variables.Reserved;
import org.elasticsearch.painless.node.SSource;
/**
* Runs the analysis phase of compilation using the Painless AST.
*/
final class Analyzer {
static Variables analyze(Reserved shortcut, SSource root) {
Variables variables = new Variables(shortcut);
root.analyze(variables);
return variables;
}
private Analyzer() {}
}

View File

@ -20,7 +20,6 @@
package org.elasticsearch.painless;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.painless.Variables.Reserved;
import org.elasticsearch.painless.antlr.Walker;
import org.elasticsearch.painless.node.SSource;
@ -36,8 +35,7 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
/**
* The Compiler is the entry point for generating a Painless script. The compiler will receive a Painless
* tree based on the type of input passed in (currently only ANTLR). Two passes will then be run over the tree,
* one for analysis using the {@link Analyzer} and another to generate the actual byte code using ASM in
* the {@link Writer}.
* one for analysis and another to generate the actual byte code using ASM using the root of the tree {@link SSource}.
*/
final class Compiler {
@ -100,18 +98,17 @@ final class Compiler {
" plugin if a script longer than this length is a requirement.");
}
Reserved reserved = new Reserved();
SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
Variables variables = Analyzer.analyze(reserved, root);
BitSet expressions = new BitSet(source.length());
byte[] bytes = Writer.write(settings, name, source, variables, root, expressions);
SSource root = Walker.buildPainlessTree(name, source, settings);
root.analyze();
root.write();
try {
Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
Class<? extends Executable> clazz = loader.define(CLASS_NAME, root.getBytes());
java.lang.reflect.Constructor<? extends Executable> constructor =
clazz.getConstructor(String.class, String.class, BitSet.class);
return constructor.newInstance(name, source, expressions);
return constructor.newInstance(name, source, root.getExpressions());
} 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);
}
@ -130,11 +127,12 @@ final class Compiler {
" plugin if a script longer than this length is a requirement.");
}
Reserved reserved = new Reserved();
SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
Variables variables = Analyzer.analyze(reserved, root);
SSource root = Walker.buildPainlessTree(name, source, settings);
return Writer.write(settings, name, source, variables, root, new BitSet(source.length()));
root.analyze();
root.write();
return root.getBytes();
}
/**

View File

@ -188,7 +188,7 @@ public final class Definition {
public final int modifiers;
public final MethodHandle handle;
private Method(String name, Struct owner, Type rtn, List<Type> arguments,
public Method(String name, Struct owner, Type rtn, List<Type> arguments,
org.objectweb.asm.commons.Method method, int modifiers, MethodHandle handle) {
this.name = name;
this.owner = owner;
@ -301,7 +301,7 @@ public final class Definition {
staticMembers = new HashMap<>();
members = new HashMap<>();
functionalMethod = new SetOnce<Method>();
functionalMethod = new SetOnce<>();
}
private Struct(final Struct struct) {

View File

@ -19,16 +19,21 @@
package org.elasticsearch.painless;
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.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Tracks variables across compilation phases.
* Tracks user defined methods and variables across compilation phases.
*/
public final class Variables {
public final class Locals {
/**
* Tracks reserved variables. Must be given to any source of input
@ -36,7 +41,15 @@ public final class Variables {
* are known ahead of time to assign appropriate slots without
* being wasteful.
*/
public static final class Reserved {
public interface Reserved {
void markReserved(String name);
boolean isReserved(String name);
void setMaxLoopCounter(int max);
int getMaxLoopCounter();
}
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";
@ -46,10 +59,11 @@ public final class Variables {
public static final String CTX = "ctx";
public static final String LOOP = "#loop";
boolean score = false;
boolean ctx = false;
boolean loop = false;
private boolean score = false;
private boolean ctx = false;
private int maxLoopCounter = 0;
@Override
public void markReserved(String name) {
if (SCORE.equals(name)) {
score = true;
@ -58,13 +72,53 @@ public final class Variables {
}
}
@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 void usesLoop() {
loop = true;
public boolean usesScore() {
return score;
}
public boolean usesCtx() {
return ctx;
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
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;
}
}
@ -86,52 +140,97 @@ public final class Variables {
}
}
final Reserved reserved;
public static final class Parameter {
public final Location location;
public final String name;
public final Type type;
public Parameter(Location location, String name, Type type) {
this.location = location;
this.name = name;
this.type = type;
}
}
private final Reserved reserved;
private final Map<MethodKey, Method> methods;
private final Type rtnType;
// TODO: this datastructure runs in linear time for nearly all operations. use linkedhashset instead?
private final Deque<Integer> scopes = new ArrayDeque<>();
private final Deque<Variable> variables = new ArrayDeque<>();
public Variables(Reserved reserved) {
public Locals(ExecuteReserved reserved, Map<MethodKey, Method> methods) {
this.reserved = reserved;
this.methods = Collections.unmodifiableMap(methods);
this.rtnType = Definition.OBJECT_TYPE;
incrementScope();
// Method variables.
// This reference. Internal use only.
addVariable(null, Definition.getType("Object"), Reserved.THIS, true, true);
addVariable(null, Definition.getType("Object"), ExecuteReserved.THIS, true, true);
// Input map of variables passed to the script.
addVariable(null, Definition.getType("Map"), Reserved.PARAMS, true, true);
addVariable(null, Definition.getType("Map"), ExecuteReserved.PARAMS, true, true);
// Scorer parameter passed to the script. Internal use only.
addVariable(null, Definition.DEF_TYPE, Reserved.SCORER, true, true);
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"), Reserved.DOC, true, true);
addVariable(null, Definition.getType("Map"), ExecuteReserved.DOC, true, true);
// Aggregation _value parameter passed to the script.
addVariable(null, Definition.DEF_TYPE, Reserved.VALUE, true, true);
addVariable(null, Definition.DEF_TYPE, ExecuteReserved.VALUE, true, true);
// Shortcut variables.
// Document's score as a read-only double.
if (reserved.score) {
addVariable(null, Definition.DOUBLE_TYPE, Reserved.SCORE, true, true);
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.ctx) {
addVariable(null, Definition.getType("Map"), Reserved.CTX, true, true);
if (reserved.usesCtx()) {
addVariable(null, Definition.getType("Map"), ExecuteReserved.CTX, true, true);
}
// Loop counter to catch infinite loops. Internal use only.
if (reserved.loop) {
addVariable(null, Definition.INT_TYPE, Reserved.LOOP, true, true);
if (reserved.getMaxLoopCounter() > 0) {
addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true);
}
}
public Locals(FunctionReserved reserved, Locals locals, Type rtnType, List<Parameter> parameters) {
this.reserved = reserved;
this.methods = locals.methods;
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);
}
@ -142,9 +241,9 @@ public final class Variables {
while (remove > 0) {
Variable variable = variables.pop();
// TODO: is this working? the code reads backwards...
// 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 + "] never used."));
throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] is never used."));
}
--remove;
@ -162,26 +261,30 @@ public final class Variables {
}
}
throw location.createError(new IllegalArgumentException("Variable [" + name + "] not defined."));
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
}
private boolean variableExists(String name) {
return variables.contains(name);
public boolean isVariable(String name) {
Iterator<Variable> 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 [" + name + "] is reserved."));
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
}
if (variableExists(name)) {
throw new IllegalArgumentException("Variable name [" + name + "] already defined.");
}
try {
Definition.getType(name);
} catch (IllegalArgumentException exception) {
// Do nothing.
if (isVariable(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
}
Variable previous = variables.peekFirst();

View File

@ -91,10 +91,10 @@ public final class MethodWriter extends GeneratorAdapter {
private final ClassWriter parent;
private final BitSet statements;
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs = (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ?
null : new ArrayDeque<>();
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs =
(INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>();
MethodWriter(int access, Method method, ClassWriter cw, BitSet statements) {
public MethodWriter(int access, Method method, ClassWriter cw, BitSet statements) {
super(Opcodes.ASM5, cw.visitMethod(access, method.getName(), method.getDescriptor(), null, null),
access, method.getName(), method.getDescriptor());
@ -105,7 +105,7 @@ public final class MethodWriter extends GeneratorAdapter {
/**
* @return A new {@link MethodWriter} with the specified access and signature.
*/
MethodWriter newMethodWriter(int access, Method method) {
public MethodWriter newMethodWriter(int access, Method method) {
return new MethodWriter(access, method, parent, statements);
}

View File

@ -1,144 +0,0 @@
/*
* 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.Variables.Reserved;
import org.elasticsearch.painless.Variables.Variable;
import org.elasticsearch.painless.node.SSource;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
import static org.elasticsearch.painless.WriterConstants.BASE_CLASS_TYPE;
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
import static org.elasticsearch.painless.WriterConstants.CONSTRUCTOR;
import static org.elasticsearch.painless.WriterConstants.EXECUTE;
import static org.elasticsearch.painless.WriterConstants.MAP_GET;
import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
import java.util.BitSet;
/**
* Runs the writing phase of compilation using the Painless AST.
*/
final class Writer {
static byte[] write(CompilerSettings settings, String name, String source, Variables variables, SSource root, BitSet expressions) {
return new Writer(settings, name, source, variables, root, expressions).getBytes();
}
private final CompilerSettings settings;
private final String scriptName;
private final String source;
private final Variables variables;
private final SSource root;
private final ClassWriter writer;
private final MethodWriter adapter;
private Writer(CompilerSettings settings, String name, String source, Variables variables, SSource root, BitSet expressions) {
this.settings = settings;
this.scriptName = name;
this.source = source;
this.variables = variables;
this.root = root;
writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writeBegin();
writeConstructor();
adapter = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, writer, expressions);
writeExecute();
writeEnd();
}
private void writeBegin() {
final int version = Opcodes.V1_8;
final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
final String base = BASE_CLASS_TYPE.getInternalName();
final String name = CLASS_TYPE.getInternalName();
// apply marker interface NeedsScore if we use the score!
final String interfaces[] = variables.reserved.score ?
new String[] { WriterConstants.NEEDS_SCORE_TYPE.getInternalName() } : null;
writer.visit(version, access, name, null, base, interfaces);
writer.visitSource(Location.computeSourceName(scriptName,source), null);
}
private void writeConstructor() {
final GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, null, null, writer);
constructor.loadThis();
constructor.loadArgs();
constructor.invokeConstructor(org.objectweb.asm.Type.getType(Executable.class), CONSTRUCTOR);
constructor.returnValue();
constructor.endMethod();
}
private void writeExecute() {
if (variables.reserved.score) {
// if the _score value is used, we do this once:
// final double _score = scorer.score();
final Variable scorer = variables.getVariable(null, Reserved.SCORER);
final Variable score = variables.getVariable(null, Reserved.SCORE);
adapter.visitVarInsn(Opcodes.ALOAD, scorer.slot);
adapter.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
adapter.visitInsn(Opcodes.F2D);
adapter.visitVarInsn(Opcodes.DSTORE, score.slot);
}
if (variables.reserved.ctx) {
// if the _ctx value is used, we do this once:
// final Map<String,Object> ctx = input.get("ctx");
final Variable input = variables.getVariable(null, Reserved.PARAMS);
final Variable ctx = variables.getVariable(null, Reserved.CTX);
adapter.visitVarInsn(Opcodes.ALOAD, input.slot);
adapter.push(Reserved.CTX);
adapter.invokeInterface(MAP_TYPE, MAP_GET);
adapter.visitVarInsn(Opcodes.ASTORE, ctx.slot);
}
if (variables.reserved.loop) {
// if there is infinite loop protection, we do this once:
// int #loop = settings.getMaxLoopCounter()
final Variable loop = variables.getVariable(null, Reserved.LOOP);
adapter.push(settings.getMaxLoopCounter());
adapter.visitVarInsn(Opcodes.ISTORE, loop.slot);
}
root.write(adapter);
adapter.endMethod();
}
private void writeEnd() {
writer.visitEnd();
}
private byte[] getBytes() {
return writer.toByteArray();
}
}

View File

@ -152,7 +152,7 @@ public final class WriterConstants {
public final static Method CHECKEQUALS = getAsmMethod(boolean.class, "checkEquals", Object.class, Object.class);
public static Method getAsmMethod(final Class<?> rtype, final String name, final Class<?>... ptypes) {
private static Method getAsmMethod(final Class<?> rtype, final String name, final Class<?>... ptypes) {
return new Method(name, MethodType.methodType(rtype, ptypes).toMethodDescriptorString());
}

View File

@ -18,6 +18,20 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitSource(PainlessParser.SourceContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitFunction(PainlessParser.FunctionContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitParameters(PainlessParser.ParametersContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -333,6 +347,13 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitVariable(PainlessParser.VariableContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitCalllocal(PainlessParser.CalllocalContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*

View File

@ -16,6 +16,18 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitSource(PainlessParser.SourceContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#function}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitFunction(PainlessParser.FunctionContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#parameters}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitParameters(PainlessParser.ParametersContext ctx);
/**
* Visit a parse tree produced by the {@code if}
* labeled alternative in {@link PainlessParser#statement}.
@ -320,6 +332,13 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitVariable(PainlessParser.VariableContext ctx);
/**
* Visit a parse tree produced by the {@code calllocal}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitCalllocal(PainlessParser.CalllocalContext ctx);
/**
* Visit a parse tree produced by the {@code newobject}
* labeled alternative in {@link PainlessParser#primary}.

View File

@ -27,10 +27,13 @@ import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
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.Location;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables.Reserved;
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;
@ -41,6 +44,7 @@ import org.elasticsearch.painless.antlr.PainlessParser.BoolContext;
import org.elasticsearch.painless.antlr.PainlessParser.BraceaccessContext;
import org.elasticsearch.painless.antlr.PainlessParser.BreakContext;
import org.elasticsearch.painless.antlr.PainlessParser.CallinvokeContext;
import org.elasticsearch.painless.antlr.PainlessParser.CalllocalContext;
import org.elasticsearch.painless.antlr.PainlessParser.CastContext;
import org.elasticsearch.painless.antlr.PainlessParser.ChainprecContext;
import org.elasticsearch.painless.antlr.PainlessParser.CompContext;
@ -62,6 +66,7 @@ import org.elasticsearch.painless.antlr.PainlessParser.FalseContext;
import org.elasticsearch.painless.antlr.PainlessParser.FieldaccessContext;
import org.elasticsearch.painless.antlr.PainlessParser.ForContext;
import org.elasticsearch.painless.antlr.PainlessParser.FuncrefContext;
import org.elasticsearch.painless.antlr.PainlessParser.FunctionContext;
import org.elasticsearch.painless.antlr.PainlessParser.IfContext;
import org.elasticsearch.painless.antlr.PainlessParser.InitializerContext;
import org.elasticsearch.painless.antlr.PainlessParser.NewarrayContext;
@ -69,6 +74,7 @@ import org.elasticsearch.painless.antlr.PainlessParser.NewobjectContext;
import org.elasticsearch.painless.antlr.PainlessParser.NullContext;
import org.elasticsearch.painless.antlr.PainlessParser.NumericContext;
import org.elasticsearch.painless.antlr.PainlessParser.OperatorContext;
import org.elasticsearch.painless.antlr.PainlessParser.ParametersContext;
import org.elasticsearch.painless.antlr.PainlessParser.PostContext;
import org.elasticsearch.painless.antlr.PainlessParser.PreContext;
import org.elasticsearch.painless.antlr.PainlessParser.ReadContext;
@ -104,7 +110,8 @@ import org.elasticsearch.painless.node.ENull;
import org.elasticsearch.painless.node.ENumeric;
import org.elasticsearch.painless.node.EUnary;
import org.elasticsearch.painless.node.LBrace;
import org.elasticsearch.painless.node.LCall;
import org.elasticsearch.painless.node.LCallInvoke;
import org.elasticsearch.painless.node.LCallLocal;
import org.elasticsearch.painless.node.LCast;
import org.elasticsearch.painless.node.LField;
import org.elasticsearch.painless.node.LNewArray;
@ -122,6 +129,7 @@ import org.elasticsearch.painless.node.SDo;
import org.elasticsearch.painless.node.SEach;
import org.elasticsearch.painless.node.SExpression;
import org.elasticsearch.painless.node.SFor;
import org.elasticsearch.painless.node.SFunction;
import org.elasticsearch.painless.node.SIf;
import org.elasticsearch.painless.node.SIfElse;
import org.elasticsearch.painless.node.SReturn;
@ -130,7 +138,9 @@ import org.elasticsearch.painless.node.SThrow;
import org.elasticsearch.painless.node.STry;
import org.elasticsearch.painless.node.SWhile;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
@ -138,19 +148,21 @@ import java.util.List;
*/
public final class Walker extends PainlessParserBaseVisitor<Object> {
public static SSource buildPainlessTree(String name, String sourceText, Reserved reserved, CompilerSettings settings) {
return new Walker(name, sourceText, reserved, settings).source;
public static SSource buildPainlessTree(String sourceName, String sourceText, CompilerSettings settings) {
return new Walker(sourceName, sourceText, settings).source;
}
private final Reserved reserved;
private final SSource source;
private final CompilerSettings settings;
private final String sourceName;
private final String sourceText;
private Walker(String name, String sourceText, Reserved reserved, CompilerSettings settings) {
this.reserved = reserved;
private final Deque<Reserved> reserved = new ArrayDeque<>();
private Walker(String sourceName, String sourceText, CompilerSettings settings) {
this.settings = settings;
this.sourceName = Location.computeSourceName(name, sourceText);
this.sourceName = Location.computeSourceName(sourceName, sourceText);
this.sourceText = sourceText;
this.source = (SSource)visit(buildAntlrTree(sourceText));
}
@ -196,13 +208,51 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@Override
public Object visitSource(SourceContext ctx) {
reserved.push(new ExecuteReserved());
List<SFunction> functions = new ArrayList<>();
for (FunctionContext function : ctx.function()) {
functions.add((SFunction)visit(function));
}
List<AStatement> statements = new ArrayList<>();
for (StatementContext statement : ctx.statement()) {
statements.add((AStatement)visit(statement));
}
return new SSource(location(ctx), statements);
return new SSource(sourceName, sourceText, (ExecuteReserved)reserved.pop(), location(ctx), functions, statements);
}
@Override
public Object visitFunction(FunctionContext ctx) {
reserved.push(new FunctionReserved());
String rtnType = ctx.decltype().getText();
String name = ctx.ID().getText();
List<String> paramTypes = new ArrayList<>();
List<String> paramNames = new ArrayList<>();
List<AStatement> statements = new ArrayList<>();
for (DecltypeContext decltype : ctx.parameters().decltype()) {
paramTypes.add(decltype.getText());
}
for (TerminalNode id : ctx.parameters().ID()) {
paramNames.add(id.getText());
}
for (StatementContext statement : ctx.block().statement()) {
statements.add((AStatement)visit(statement));
}
return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, paramTypes, paramNames, statements);
}
@Override
public Object visitParameters(ParametersContext ctx) {
throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
}
@Override
@ -221,18 +271,16 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@Override
public Object visitWhile(WhileContext ctx) {
if (settings.getMaxLoopCounter() > 0) {
reserved.usesLoop();
}
reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter());
AExpression expression = (AExpression)visitExpression(ctx.expression());
if (ctx.trailer() != null) {
SBlock block = (SBlock)visit(ctx.trailer());
return new SWhile(location(ctx), settings.getMaxLoopCounter(), expression, block);
return new SWhile(location(ctx), expression, block);
} else if (ctx.empty() != null) {
return new SWhile(location(ctx), settings.getMaxLoopCounter(), expression, null);
return new SWhile(location(ctx), expression, null);
} else {
throw location(ctx).createError(new IllegalStateException(" Illegal tree structure."));
}
@ -240,21 +288,17 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@Override
public Object visitDo(DoContext ctx) {
if (settings.getMaxLoopCounter() > 0) {
reserved.usesLoop();
}
reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter());
AExpression expression = (AExpression)visitExpression(ctx.expression());
SBlock block = (SBlock)visit(ctx.block());
return new SDo(location(ctx), settings.getMaxLoopCounter(), block, expression);
return new SDo(location(ctx), block, expression);
}
@Override
public Object visitFor(ForContext ctx) {
if (settings.getMaxLoopCounter() > 0) {
reserved.usesLoop();
}
reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter());
ANode initializer = ctx.initializer() == null ? null : (ANode)visit(ctx.initializer());
AExpression expression = ctx.expression() == null ? null : (AExpression)visitExpression(ctx.expression());
@ -263,9 +307,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
if (ctx.trailer() != null) {
SBlock block = (SBlock)visit(ctx.trailer());
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
return new SFor(location(ctx), initializer, expression, afterthought, block);
} else if (ctx.empty() != null) {
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
return new SFor(location(ctx), initializer, expression, afterthought, null);
} else {
throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
}
@ -273,16 +317,14 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@Override
public Object visitEach(EachContext ctx) {
if (settings.getMaxLoopCounter() > 0) {
reserved.usesLoop();
}
reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter());
String type = ctx.decltype().getText();
String name = ctx.ID().getText();
AExpression expression = (AExpression)visitExpression(ctx.expression());
SBlock block = (SBlock)visit(ctx.trailer());
return new SEach(location(ctx), settings.getMaxLoopCounter(), type, name, expression, block);
return new SEach(location(ctx), type, name, expression, block);
}
@Override
@ -791,7 +833,18 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
List<ALink> links = new ArrayList<>();
links.add(new LVariable(location(ctx), name));
reserved.markReserved(name);
reserved.peek().markReserved(name);
return links;
}
@Override
public Object visitCalllocal(CalllocalContext ctx) {
String name = ctx.ID().getText();
@SuppressWarnings("unchecked")
List<AExpression> arguments = (List<AExpression>)visit(ctx.arguments());
List<ALink> links = new ArrayList<>();
links.add(new LCallLocal(location(ctx), name, arguments));
return links;
}
@ -825,7 +878,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@SuppressWarnings("unchecked")
List<AExpression> arguments = (List<AExpression>)visit(ctx.arguments());
return new LCall(location(ctx), name, arguments);
return new LCallInvoke(location(ctx), name, arguments);
}
@Override

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -106,7 +106,7 @@ public abstract class AExpression extends ANode {
/**
* Checks for errors and collects data for the writing phase.
*/
abstract void analyze(Variables variables);
abstract void analyze(Locals locals);
/**
* Writes ASM based on the data collected during the analysis phase.
@ -118,7 +118,7 @@ public abstract class AExpression extends ANode {
* nodes with the constant variable set to a non-null value with {@link EConstant}.
* @return The new child node for the parent node calling this method.
*/
AExpression cast(Variables variables) {
AExpression cast(Locals locals) {
final Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
if (cast == null) {
@ -136,7 +136,7 @@ public abstract class AExpression extends ANode {
// will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
econstant.analyze(locals);
if (!expected.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
@ -170,7 +170,7 @@ public abstract class AExpression extends ANode {
constant = AnalyzerCaster.constCast(location, constant, cast);
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
econstant.analyze(locals);
if (!expected.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
@ -201,7 +201,7 @@ public abstract class AExpression extends ANode {
// the EConstant will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
econstant.analyze(locals);
if (!actual.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -86,7 +86,7 @@ public abstract class ALink extends ANode {
* def or a shortcut is used. Otherwise, returns itself. This will be
* updated into the {@link EChain} node's list of links.
*/
abstract ALink analyze(Variables variables);
abstract ALink analyze(Locals locals);
/**
* Write values before a load/store occurs such as an array index.

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -115,7 +115,7 @@ public abstract class AStatement extends ANode {
/**
* Checks for errors and collects data for the writing phase.
*/
abstract void analyze(Variables variables);
abstract void analyze(Locals locals);
/**
* Writes ASM based on the data collected during the analysis phase.

View File

@ -26,7 +26,7 @@ import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
/**
* Represents a binary math expression.
@ -48,35 +48,35 @@ public final class EBinary extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (operation == Operation.MUL) {
analyzeMul(variables);
analyzeMul(locals);
} else if (operation == Operation.DIV) {
analyzeDiv(variables);
analyzeDiv(locals);
} else if (operation == Operation.REM) {
analyzeRem(variables);
analyzeRem(locals);
} else if (operation == Operation.ADD) {
analyzeAdd(variables);
analyzeAdd(locals);
} else if (operation == Operation.SUB) {
analyzeSub(variables);
analyzeSub(locals);
} else if (operation == Operation.LSH) {
analyzeLSH(variables);
analyzeLSH(locals);
} else if (operation == Operation.RSH) {
analyzeRSH(variables);
analyzeRSH(locals);
} else if (operation == Operation.USH) {
analyzeUSH(variables);
analyzeUSH(locals);
} else if (operation == Operation.BWAND) {
analyzeBWAnd(variables);
analyzeBWAnd(locals);
} else if (operation == Operation.XOR) {
analyzeXor(variables);
analyzeXor(locals);
} else if (operation == Operation.BWOR) {
analyzeBWOr(variables);
analyzeBWOr(locals);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
private void analyzeMul(Variables variables) {
private void analyzeMul(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -112,7 +112,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeDiv(Variables variables) {
private void analyzeDiv(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -152,7 +152,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeRem(Variables variables) {
private void analyzeRem(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -192,7 +192,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeAdd(Variables variables) {
private void analyzeAdd(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -244,7 +244,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeSub(Variables variables) {
private void analyzeSub(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -280,7 +280,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeLSH(Variables variables) {
private void analyzeLSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -313,7 +313,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeRSH(Variables variables) {
private void analyzeRSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -346,7 +346,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeUSH(Variables variables) {
private void analyzeUSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -379,7 +379,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeBWAnd(Variables variables) {
private void analyzeBWAnd(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -411,7 +411,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeXor(Variables variables) {
private void analyzeXor(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -445,7 +445,7 @@ public final class EBinary extends AExpression {
actual = promote;
}
private void analyzeBWOr(Variables variables) {
private void analyzeBWOr(Locals variables) {
left.analyze(variables);
right.analyze(variables);

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -44,14 +44,14 @@ public final class EBool extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
left.expected = Definition.BOOLEAN_TYPE;
left.analyze(variables);
left = left.cast(variables);
left.analyze(locals);
left = left.cast(locals);
right.expected = Definition.BOOLEAN_TYPE;
right.analyze(variables);
right = right.cast(variables);
right.analyze(locals);
right = right.cast(locals);
if (left.constant != null && right.constant != null) {
if (operation == Operation.AND) {

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -36,7 +36,7 @@ public final class EBoolean extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
actual = Definition.BOOLEAN_TYPE;
}

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -45,7 +45,7 @@ final class ECast extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}

View File

@ -26,7 +26,7 @@ import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -59,20 +59,20 @@ public final class EChain extends AExpression {
}
@Override
void analyze(Variables variables) {
analyzeLinks(variables);
void analyze(Locals locals) {
analyzeLinks(locals);
analyzeIncrDecr();
if (operation != null) {
analyzeCompound(variables);
analyzeCompound(locals);
} else if (expression != null) {
analyzeWrite(variables);
analyzeWrite(locals);
} else {
analyzeRead();
}
}
private void analyzeLinks(Variables variables) {
private void analyzeLinks(Locals variables) {
ALink previous = null;
int index = 0;
@ -153,7 +153,7 @@ public final class EChain extends AExpression {
}
}
private void analyzeCompound(Variables variables) {
private void analyzeCompound(Locals variables) {
ALink last = links.get(links.size() - 1);
expression.analyze(variables);
@ -214,7 +214,7 @@ public final class EChain extends AExpression {
this.actual = read ? last.after : Definition.VOID_TYPE;
}
private void analyzeWrite(Variables variables) {
private void analyzeWrite(Locals variables) {
ALink last = links.get(links.size() - 1);
// If the store node is a def node, we remove the cast to def from the expression

View File

@ -25,7 +25,7 @@ import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -56,29 +56,29 @@ public final class EComp extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (operation == Operation.EQ) {
analyzeEq(variables);
analyzeEq(locals);
} else if (operation == Operation.EQR) {
analyzeEqR(variables);
analyzeEqR(locals);
} else if (operation == Operation.NE) {
analyzeNE(variables);
analyzeNE(locals);
} else if (operation == Operation.NER) {
analyzeNER(variables);
analyzeNER(locals);
} else if (operation == Operation.GTE) {
analyzeGTE(variables);
analyzeGTE(locals);
} else if (operation == Operation.GT) {
analyzeGT(variables);
analyzeGT(locals);
} else if (operation == Operation.LTE) {
analyzeLTE(variables);
analyzeLTE(locals);
} else if (operation == Operation.LT) {
analyzeLT(variables);
analyzeLT(locals);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
private void analyzeEq(Variables variables) {
private void analyzeEq(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -124,7 +124,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeEqR(Variables variables) {
private void analyzeEqR(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -166,7 +166,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeNE(Variables variables) {
private void analyzeNE(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -212,7 +212,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeNER(Variables variables) {
private void analyzeNER(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -254,7 +254,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeGTE(Variables variables) {
private void analyzeGTE(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -290,7 +290,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeGT(Variables variables) {
private void analyzeGT(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -326,7 +326,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeLTE(Variables variables) {
private void analyzeLTE(Locals variables) {
left.analyze(variables);
right.analyze(variables);
@ -362,7 +362,7 @@ public final class EComp extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeLT(Variables variables) {
private void analyzeLT(Locals variables) {
left.analyze(variables);
right.analyze(variables);

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -45,10 +45,10 @@ public final class EConditional extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
if (condition.constant != null) {
throw createError(new IllegalArgumentException("Extraneous conditional statement."));
@ -62,8 +62,8 @@ public final class EConditional extends AExpression {
right.internal = internal;
actual = expected;
left.analyze(variables);
right.analyze(variables);
left.analyze(locals);
right.analyze(locals);
if (expected == null) {
final Type promote = AnalyzerCaster.promoteConditional(left.actual, right.actual, left.constant, right.constant);
@ -73,8 +73,8 @@ public final class EConditional extends AExpression {
actual = promote;
}
left = left.cast(variables);
right = right.cast(variables);
left = left.cast(locals);
right = right.cast(locals);
}
@Override

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -38,7 +38,7 @@ final class EConstant extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (constant instanceof String) {
actual = Definition.STRING_TYPE;
} else if (constant instanceof Double) {

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -38,7 +38,7 @@ public final class EDecimal extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (value.endsWith("f") || value.endsWith("F")) {
try {
constant = Float.parseFloat(value.substring(0, value.length() - 1));

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -40,7 +40,7 @@ public final class EExplicit extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
try {
actual = Definition.getType(this.type);
} catch (IllegalArgumentException exception) {
@ -49,8 +49,8 @@ public final class EExplicit extends AExpression {
child.expected = actual;
child.explicit = true;
child.analyze(variables);
child = child.cast(variables);
child.analyze(locals);
child = child.cast(locals);
}
@Override
@ -58,11 +58,11 @@ public final class EExplicit extends AExpression {
throw createError(new IllegalStateException("Illegal tree structure."));
}
AExpression cast(Variables variables) {
AExpression cast(Locals locals) {
child.expected = expected;
child.explicit = explicit;
child.internal = internal;
return child.cast(variables);
return child.cast(locals);
}
}

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
@ -47,7 +47,7 @@ public class EFunctionRef extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (expected == null) {
ref = null;
actual = Definition.getType("String");

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Opcodes;
import org.elasticsearch.painless.MethodWriter;
@ -35,7 +35,7 @@ public final class ENull extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
isNull = true;
if (expected != null) {

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -41,7 +41,7 @@ public final class ENumeric extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (value.endsWith("d") || value.endsWith("D")) {
if (radix != 10) {
throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -25,7 +25,7 @@ import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -49,21 +49,21 @@ public final class EUnary extends AExpression {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (operation == Operation.NOT) {
analyzeNot(variables);
analyzeNot(locals);
} else if (operation == Operation.BWNOT) {
analyzeBWNot(variables);
analyzeBWNot(locals);
} else if (operation == Operation.ADD) {
analyzerAdd(variables);
analyzerAdd(locals);
} else if (operation == Operation.SUB) {
analyzerSub(variables);
analyzerSub(locals);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
void analyzeNot(Variables variables) {
void analyzeNot(Locals variables) {
child.expected = Definition.BOOLEAN_TYPE;
child.analyze(variables);
child = child.cast(variables);
@ -75,7 +75,7 @@ public final class EUnary extends AExpression {
actual = Definition.BOOLEAN_TYPE;
}
void analyzeBWNot(Variables variables) {
void analyzeBWNot(Locals variables) {
child.analyze(variables);
Type promote = AnalyzerCaster.promoteNumeric(child.actual, false);
@ -102,7 +102,7 @@ public final class EUnary extends AExpression {
actual = promote;
}
void analyzerAdd(Variables variables) {
void analyzerAdd(Locals variables) {
child.analyze(variables);
Type promote = AnalyzerCaster.promoteNumeric(child.actual, true);
@ -133,7 +133,7 @@ public final class EUnary extends AExpression {
actual = promote;
}
void analyzerSub(Variables variables) {
void analyzerSub(Locals variables) {
child.analyze(variables);
Type promote = AnalyzerCaster.promoteNumeric(child.actual, true);

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -38,7 +38,7 @@ public final class LArrayLength extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if ("length".equals(value)) {
if (!load) {
throw createError(new IllegalArgumentException("Must read array field [length]."));

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -42,7 +42,7 @@ public final class LBrace extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal array access made without target."));
}
@ -51,18 +51,18 @@ public final class LBrace extends ALink {
if (sort == Sort.ARRAY) {
index.expected = Definition.INT_TYPE;
index.analyze(variables);
index = index.cast(variables);
index.analyze(locals);
index = index.cast(locals);
after = Definition.getType(before.struct, before.dimensions - 1);
return this;
} else if (sort == Sort.DEF) {
return new LDefArray(location, index).copy(this).analyze(variables);
return new LDefArray(location, index).copy(this).analyze(locals);
} else if (Map.class.isAssignableFrom(before.clazz)) {
return new LMapShortcut(location, index).copy(this).analyze(variables);
return new LMapShortcut(location, index).copy(this).analyze(locals);
} else if (List.class.isAssignableFrom(before.clazz)) {
return new LListShortcut(location, index).copy(this).analyze(variables);
return new LListShortcut(location, index).copy(this).analyze(locals);
}
throw createError(new IllegalArgumentException("Illegal array access on type [" + before.name + "]."));

View File

@ -19,12 +19,12 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.MethodKey;
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.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -32,14 +32,14 @@ import java.util.List;
/**
* Represents a method call or deferes to a def call.
*/
public final class LCall extends ALink {
public final class LCallInvoke extends ALink {
final String name;
final List<AExpression> arguments;
Method method = null;
public LCall(Location location, String name, List<AExpression> arguments) {
public LCallInvoke(Location location, String name, List<AExpression> arguments) {
super(location, -1);
this.name = name;
@ -47,7 +47,7 @@ public final class LCall extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] made without target."));
} else if (before.sort == Sort.ARRAY) {
@ -56,7 +56,7 @@ public final class LCall extends ALink {
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
}
Definition.MethodKey methodKey = new Definition.MethodKey(name, arguments.size());
MethodKey methodKey = new MethodKey(name, arguments.size());
Struct struct = before.struct;
method = statik ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey);
@ -66,8 +66,8 @@ public final class LCall extends ALink {
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(variables);
arguments.set(argument, expression.cast(variables));
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;
@ -78,11 +78,11 @@ public final class LCall extends ALink {
ALink link = new LDefCall(location, name, arguments);
link.copy(this);
return link.analyze(variables);
return link.analyze(locals);
}
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() +
"] arguments on type [" + struct.name + "]."));
throw createError(new IllegalArgumentException(
"Unknown call [" + name + "] with [" + arguments.size() + "] arguments on type [" + struct.name + "]."));
}
@Override
@ -105,10 +105,6 @@ public final class LCall extends ALink {
} else {
writer.invokeVirtual(method.owner.type, method.method);
}
if (!method.rtn.clazz.equals(method.handle.type().returnType())) {
writer.checkCast(method.rtn.type);
}
}
@Override

View File

@ -0,0 +1,99 @@
/*
* 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;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
/**
* Represents a user-defined call.
*/
public class LCallLocal extends ALink {
final String name;
final List<AExpression> arguments;
Method method = null;
public LCallLocal(Location location, String name, List<AExpression> arguments) {
super(location, -1);
this.name = name;
this.arguments = arguments;
}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] against an existing target."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
}
MethodKey methodKey = new MethodKey(name, arguments.size());
method = locals.getMethod(methodKey);
if (method != null) {
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;
after = method.rtn;
return this;
}
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
}
@Override
void write(MethodWriter writer) {
// Do nothing.
}
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
for (AExpression argument : arguments) {
argument.write(writer);
}
writer.invokeStatic(CLASS_TYPE, method.method);
}
@Override
void store(MethodWriter writer) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -42,7 +42,7 @@ public final class LCast extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalStateException("Illegal cast without a target."));
} else if (store) {

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.MethodWriter;
@ -42,10 +42,10 @@ final class LDefArray extends ALink implements IDefLink {
}
@Override
ALink analyze(Variables variables) {
index.analyze(variables);
ALink analyze(Locals locals) {
index.analyze(locals);
index.expected = index.actual;
index = index.cast(variables);
index = index.cast(locals);
after = Definition.DEF_TYPE;

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -46,7 +46,7 @@ final class LDefCall extends ALink implements IDefLink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (arguments.size() > 63) {
// technically, the limitation is just methods with > 63 params, containing method references.
// this is because we are lazy and use a long as a bitset. we can always change to a "string" if need be.
@ -62,9 +62,9 @@ final class LDefCall extends ALink implements IDefLink {
recipe |= (1L << argument); // mark argument as deferred reference
}
expression.internal = true;
expression.analyze(variables);
expression.analyze(locals);
expression.expected = expression.actual;
arguments.set(argument, expression.cast(variables));
arguments.set(argument, expression.cast(locals));
}
statement = true;

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.MethodWriter;
@ -43,7 +43,7 @@ final class LDefField extends ALink implements IDefLink {
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
after = Definition.DEF_TYPE;
return this;

View File

@ -24,7 +24,7 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -46,7 +46,7 @@ public final class LField extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal field [" + value + "] access made without target."));
}
@ -54,9 +54,9 @@ public final class LField extends ALink {
Sort sort = before.sort;
if (sort == Sort.ARRAY) {
return new LArrayLength(location, value).copy(this).analyze(variables);
return new LArrayLength(location, value).copy(this).analyze(locals);
} else if (sort == Sort.DEF) {
return new LDefField(location, value).copy(this).analyze(variables);
return new LDefField(location, value).copy(this).analyze(locals);
}
Struct struct = before.struct;
@ -81,17 +81,17 @@ public final class LField extends ALink {
Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
if (shortcut) {
return new LShortcut(location, value).copy(this).analyze(variables);
return new LShortcut(location, value).copy(this).analyze(locals);
} else {
EConstant index = new EConstant(location, value);
index.analyze(variables);
index.analyze(locals);
if (Map.class.isAssignableFrom(before.clazz)) {
return new LMapShortcut(location, index).copy(this).analyze(variables);
return new LMapShortcut(location, index).copy(this).analyze(locals);
}
if (List.class.isAssignableFrom(before.clazz)) {
return new LListShortcut(location, index).copy(this).analyze(variables);
return new LListShortcut(location, index).copy(this).analyze(locals);
}
}
}

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -42,7 +42,7 @@ final class LListShortcut extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
setter = before.struct.methods.get(new Definition.MethodKey("set", 2));
@ -62,8 +62,8 @@ final class LListShortcut extends ALink {
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
index.expected = Definition.INT_TYPE;
index.analyze(variables);
index = index.cast(variables);
index.analyze(locals);
index = index.cast(locals);
after = setter != null ? setter.arguments.get(1) : getter.rtn;
} else {

View File

@ -23,7 +23,7 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -42,7 +42,7 @@ final class LMapShortcut extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
setter = before.struct.methods.get(new Definition.MethodKey("put", 2));
@ -61,8 +61,8 @@ final class LMapShortcut extends ALink {
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
index.expected = setter != null ? setter.arguments.get(0) : getter.arguments.get(0);
index.analyze(variables);
index = index.cast(variables);
index.analyze(locals);
index = index.cast(locals);
after = setter != null ? setter.arguments.get(1) : getter.rtn;
} else {

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -43,7 +43,7 @@ public final class LNewArray extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Cannot create a new array with a target already defined."));
} else if (store) {
@ -64,8 +64,8 @@ public final class LNewArray extends ALink {
AExpression expression = arguments.get(argument);
expression.expected = Definition.INT_TYPE;
expression.analyze(variables);
arguments.set(argument, expression.cast(variables));
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
after = Definition.getType(type.struct, arguments.size());

View File

@ -24,7 +24,7 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
@ -47,7 +47,7 @@ public final class LNewObj extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal new call with a target already defined."));
} else if (store) {
@ -79,8 +79,8 @@ public final class LNewObj extends ALink {
expression.expected = types[argument];
expression.internal = true;
expression.analyze(variables);
arguments.set(argument, expression.cast(variables));
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;

View File

@ -24,7 +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.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -44,7 +44,7 @@ final class LShortcut extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
Struct struct = before.struct;
getter = struct.methods.get(new Definition.MethodKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));

View File

@ -22,7 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
/**
* Represents a static type target.
@ -38,7 +38,7 @@ public final class LStatic extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal static type [" + type + "] after target already defined."));
}

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -36,7 +36,7 @@ public final class LString extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal String constant [" + string + "]."));
} else if (store) {

View File

@ -21,8 +21,8 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Variables.Variable;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Opcodes;
/**
@ -41,12 +41,12 @@ public final class LVariable extends ALink {
}
@Override
ALink analyze(Variables variables) {
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
}
Variable variable = variables.getVariable(location, name);
Variable variable = locals.getVariable(location, name);
if (store && variable.readonly) {
throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -40,7 +40,7 @@ public final class SBlock extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (statements == null || statements.isEmpty()) {
throw createError(new IllegalArgumentException("A block must contain at least one statement."));
}
@ -58,7 +58,7 @@ public final class SBlock extends AStatement {
statement.lastSource = lastSource && statement == last;
statement.lastLoop = (beginLoop || lastLoop) && statement == last;
statement.analyze(variables);
statement.analyze(locals);
methodEscape = statement.methodEscape;
loopEscape = statement.loopEscape;

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -33,7 +33,7 @@ public final class SBreak extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (!inLoop) {
throw createError(new IllegalArgumentException("Break statement outside of a loop."));
}

View File

@ -22,8 +22,8 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Variables.Variable;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.elasticsearch.painless.MethodWriter;
@ -52,7 +52,7 @@ public final class SCatch extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
final Type type;
try {
@ -65,14 +65,14 @@ public final class SCatch extends AStatement {
throw createError(new ClassCastException("Not an exception type [" + this.type + "]."));
}
variable = variables.addVariable(location, type, name, true, false);
variable = locals.addVariable(location, type, name, true, false);
if (block != null) {
block.lastSource = lastSource;
block.inLoop = inLoop;
block.lastLoop = lastLoop;
block.analyze(variables);
block.analyze(locals);
methodEscape = block.methodEscape;
loopEscape = block.loopEscape;

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -33,7 +33,7 @@ public final class SContinue extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (!inLoop) {
throw createError(new IllegalArgumentException("Continue statement outside of a loop."));
}

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -40,9 +40,9 @@ public final class SDeclBlock extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
for (SDeclaration declaration : declarations) {
declaration.analyze(variables);
declaration.analyze(locals);
}
statementCount = declarations.size();

View File

@ -22,8 +22,8 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Variables.Variable;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Opcodes;
import org.elasticsearch.painless.MethodWriter;
@ -47,7 +47,7 @@ public final class SDeclaration extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
final Type type;
try {
@ -58,11 +58,11 @@ public final class SDeclaration extends AStatement {
if (expression != null) {
expression.expected = type;
expression.analyze(variables);
expression = expression.cast(variables);
expression.analyze(locals);
expression = expression.cast(locals);
}
variable = variables.addVariable(location, type, name, false, false);
variable = locals.addVariable(location, type, name, false, false);
}
@Override

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -30,21 +30,19 @@ import org.elasticsearch.painless.MethodWriter;
*/
public final class SDo extends AStatement {
final int maxLoopCounter;
final SBlock block;
AExpression condition;
public SDo(Location location, int maxLoopCounter, SBlock block, AExpression condition) {
public SDo(Location location, SBlock block, AExpression condition) {
super(location);
this.condition = condition;
this.block = block;
this.maxLoopCounter = maxLoopCounter;
}
@Override
void analyze(Variables variables) {
variables.incrementScope();
void analyze(Locals locals) {
locals.incrementScope();
if (block == null) {
throw createError(new IllegalArgumentException("Extraneous do while loop."));
@ -53,15 +51,15 @@ public final class SDo extends AStatement {
block.beginLoop = true;
block.inLoop = true;
block.analyze(variables);
block.analyze(locals);
if (block.loopEscape && !block.anyContinue) {
throw createError(new IllegalArgumentException("Extraneous do while loop."));
}
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
if (condition.constant != null) {
final boolean continuous = (boolean)condition.constant;
@ -78,11 +76,11 @@ public final class SDo extends AStatement {
statementCount = 1;
if (maxLoopCounter > 0) {
loopCounterSlot = variables.getVariable(location, "#loop").slot;
if (locals.getMaxLoopCounter() > 0) {
loopCounterSlot = locals.getVariable(location, "#loop").slot;
}
variables.decrementScope();
locals.decrementScope();
}
@Override

View File

@ -29,8 +29,8 @@ import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Variables.Variable;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
@ -44,7 +44,6 @@ import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
*/
public class SEach extends AStatement {
final int maxLoopCounter;
final String type;
final String name;
AExpression expression;
@ -63,10 +62,9 @@ public class SEach extends AStatement {
Variable iterator = null;
Method method = null;
public SEach(Location location, int maxLoopCounter, String type, String name, AExpression expression, SBlock block) {
public SEach(Location location, String type, String name, AExpression expression, SBlock block) {
super(location);
this.maxLoopCounter = maxLoopCounter;
this.type = type;
this.name = name;
this.expression = expression;
@ -74,10 +72,10 @@ public class SEach extends AStatement {
}
@Override
void analyze(Variables variables) {
expression.analyze(variables);
void analyze(Locals locals) {
expression.analyze(locals);
expression.expected = expression.actual;
expression = expression.cast(variables);
expression = expression.cast(locals);
final Type type;
@ -87,25 +85,25 @@ public class SEach extends AStatement {
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
}
variables.incrementScope();
locals.incrementScope();
variable = variables.addVariable(location, type, name, true, false);
variable = locals.addVariable(location, type, name, true, false);
if (expression.actual.sort == Sort.ARRAY) {
analyzeArray(variables, type);
analyzeArray(locals, type);
} else if (expression.actual.sort == Sort.DEF || Iterable.class.isAssignableFrom(expression.actual.clazz)) {
analyzeIterable(variables, type);
analyzeIterable(locals, type);
} else {
throw location.createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
throw createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
}
if (block == null) {
throw location.createError(new IllegalArgumentException("Extraneous for each loop."));
throw createError(new IllegalArgumentException("Extraneous for each loop."));
}
block.beginLoop = true;
block.inLoop = true;
block.analyze(variables);
block.analyze(locals);
block.statementCount = Math.max(1, block.statementCount);
if (block.loopEscape && !block.anyContinue) {
@ -114,14 +112,14 @@ public class SEach extends AStatement {
statementCount = 1;
if (maxLoopCounter > 0) {
loopCounterSlot = variables.getVariable(location, "#loop").slot;
if (locals.getMaxLoopCounter() > 0) {
loopCounterSlot = locals.getVariable(location, "#loop").slot;
}
variables.decrementScope();
locals.decrementScope();
}
void analyzeArray(Variables variables, Type type) {
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);
@ -130,7 +128,7 @@ public class SEach extends AStatement {
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
}
void analyzeIterable(Variables variables, Type type) {
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);
@ -141,7 +139,7 @@ public class SEach extends AStatement {
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
if (method == null) {
throw location.createError(new IllegalArgumentException(
throw createError(new IllegalArgumentException(
"Unable to create iterator for the type [" + expression.actual.name + "]."));
}
}
@ -158,7 +156,7 @@ public class SEach extends AStatement {
} else if (iterator != null) {
writeIterable(writer);
} else {
throw location.createError(new IllegalStateException("Illegal tree structure."));
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
@ -197,7 +195,7 @@ public class SEach extends AStatement {
if (method == null) {
Type itr = Definition.getType("Iterator");
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type);
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ITERATOR, 0);
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ITERATOR, (Object)0);
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
writer.invokeInterface(method.owner.type, method.method);
} else {

View File

@ -20,9 +20,10 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -39,19 +40,22 @@ public final class SExpression extends AStatement {
}
@Override
void analyze(Variables variables) {
expression.read = lastSource;
expression.analyze(variables);
void analyze(Locals locals) {
Type rtnType = locals.getReturnType();
boolean isVoid = rtnType.sort == Sort.VOID;
expression.read = lastSource && !isVoid;
expression.analyze(locals);
if (!lastSource && !expression.statement) {
throw createError(new IllegalArgumentException("Not a statement."));
}
final boolean rtn = lastSource && expression.actual.sort != Sort.VOID;
boolean rtn = lastSource && !isVoid && expression.actual.sort != Sort.VOID;
expression.expected = rtn ? Definition.OBJECT_TYPE : expression.actual;
expression.expected = rtn ? rtnType : expression.actual;
expression.internal = rtn;
expression = expression.cast(variables);
expression = expression.cast(locals);
methodEscape = rtn;
loopEscape = rtn;

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -30,17 +30,14 @@ import org.elasticsearch.painless.MethodWriter;
*/
public final class SFor extends AStatement {
final int maxLoopCounter;
ANode initializer;
AExpression condition;
AExpression afterthought;
final SBlock block;
public SFor(Location location, int maxLoopCounter,
ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
public SFor(Location location, ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
super(location);
this.maxLoopCounter = maxLoopCounter;
this.initializer = initializer;
this.condition = condition;
this.afterthought = afterthought;
@ -48,19 +45,19 @@ public final class SFor extends AStatement {
}
@Override
void analyze(Variables variables) {
variables.incrementScope();
void analyze(Locals locals) {
locals.incrementScope();
boolean continuous = false;
if (initializer != null) {
if (initializer instanceof AStatement) {
((AStatement)initializer).analyze(variables);
((AStatement)initializer).analyze(locals);
} else if (initializer instanceof AExpression) {
AExpression initializer = (AExpression)this.initializer;
initializer.read = false;
initializer.analyze(variables);
initializer.analyze(locals);
if (!initializer.statement) {
throw createError(new IllegalArgumentException("Not a statement."));
@ -72,8 +69,8 @@ public final class SFor extends AStatement {
if (condition != null) {
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
if (condition.constant != null) {
continuous = (boolean)condition.constant;
@ -92,7 +89,7 @@ public final class SFor extends AStatement {
if (afterthought != null) {
afterthought.read = false;
afterthought.analyze(variables);
afterthought.analyze(locals);
if (!afterthought.statement) {
throw createError(new IllegalArgumentException("Not a statement."));
@ -103,7 +100,7 @@ public final class SFor extends AStatement {
block.beginLoop = true;
block.inLoop = true;
block.analyze(variables);
block.analyze(locals);
if (block.loopEscape && !block.anyContinue) {
throw createError(new IllegalArgumentException("Extraneous for loop."));
@ -119,11 +116,11 @@ public final class SFor extends AStatement {
statementCount = 1;
if (maxLoopCounter > 0) {
loopCounterSlot = variables.getVariable(location, "#loop").slot;
if (locals.getMaxLoopCounter() > 0) {
loopCounterSlot = locals.getVariable(location, "#loop").slot;
}
variables.decrementScope();
locals.decrementScope();
}
@Override

View File

@ -0,0 +1,163 @@
/*
* 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;
import org.elasticsearch.painless.Definition;
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.objectweb.asm.Opcodes;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents a user-defined function.
*/
public class SFunction extends AStatement {
final FunctionReserved reserved;
final String rtnTypeStr;
final String name;
final List<String> paramTypeStrs;
final List<String> paramNameStrs;
final List<AStatement> statements;
Type rtnType = null;
List<Parameter> parameters = new ArrayList<>();
Method method = null;
Locals locals = null;
public SFunction(FunctionReserved reserved, Location location,
String rtnType, String name, List<String> paramTypes, List<String> paramNames, List<AStatement> statements) {
super(location);
this.reserved = reserved;
this.rtnTypeStr = rtnType;
this.name = name;
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
this.paramNameStrs = Collections.unmodifiableList(paramNames);
this.statements = Collections.unmodifiableList(statements);
}
void generate() {
try {
rtnType = Definition.getType(rtnTypeStr);
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException("Illegal return type [" + rtnTypeStr + "] for function [" + name + "]."));
}
if (paramTypeStrs.size() != paramNameStrs.size()) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
Class<?>[] paramClasses = new Class<?>[this.paramTypeStrs.size()];
List<Type> paramTypes = new ArrayList<>();
for (int param = 0; param < this.paramTypeStrs.size(); ++param) {
try {
Type paramType = Definition.getType(this.paramTypeStrs.get(param));
paramClasses[param] = paramType.clazz;
paramTypes.add(paramType);
parameters.add(new Parameter(location, paramNameStrs.get(param), paramType));
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException(
"Illegal parameter type [" + this.paramTypeStrs.get(param) + "] for function [" + name + "]."));
}
}
org.objectweb.asm.commons.Method method =
new org.objectweb.asm.commons.Method(name, MethodType.methodType(rtnType.clazz, paramClasses).toMethodDescriptorString());
this.method = new Method(name, null, rtnType, paramTypes, method, 0, null);
}
@Override
void analyze(Locals locals) {
if (statements == null || statements.isEmpty()) {
throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "]."));
}
this.locals = new Locals(reserved, locals, rtnType, parameters);
locals = this.locals;
locals.incrementScope();
AStatement last = statements.get(statements.size() - 1);
for (AStatement statement : statements) {
// Note that we do not need to check after the last statement because
// there is no statement that can be unreachable after the last.
if (allEscape) {
throw createError(new IllegalArgumentException("Unreachable statement."));
}
statement.lastSource = statement == last;
statement.analyze(locals);
methodEscape = statement.methodEscape;
allEscape = statement.allEscape;
}
if (!methodEscape && rtnType.sort != Sort.VOID) {
throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "]."));
}
locals.decrementScope();
}
@Override
void write(MethodWriter writer) {
MethodWriter function = writer.newMethodWriter(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, method.method);
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);
}
for (AStatement statement : statements) {
statement.write(function);
}
if (!methodEscape) {
if (rtnType.sort == Sort.VOID) {
function.returnValue();
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
function.endMethod();
}
}

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -41,10 +41,10 @@ public final class SIf extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
if (condition.constant != null) {
throw createError(new IllegalArgumentException("Extraneous if statement."));
@ -58,9 +58,9 @@ public final class SIf extends AStatement {
ifblock.inLoop = inLoop;
ifblock.lastLoop = lastLoop;
variables.incrementScope();
ifblock.analyze(variables);
variables.decrementScope();
locals.incrementScope();
ifblock.analyze(locals);
locals.decrementScope();
anyContinue = ifblock.anyContinue;
anyBreak = ifblock.anyBreak;

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -43,10 +43,10 @@ public final class SIfElse extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
if (condition.constant != null) {
throw createError(new IllegalArgumentException("Extraneous if statement."));
@ -60,9 +60,9 @@ public final class SIfElse extends AStatement {
ifblock.inLoop = inLoop;
ifblock.lastLoop = lastLoop;
variables.incrementScope();
ifblock.analyze(variables);
variables.decrementScope();
locals.incrementScope();
ifblock.analyze(locals);
locals.decrementScope();
anyContinue = ifblock.anyContinue;
anyBreak = ifblock.anyBreak;
@ -76,9 +76,9 @@ public final class SIfElse extends AStatement {
elseblock.inLoop = inLoop;
elseblock.lastLoop = lastLoop;
variables.incrementScope();
elseblock.analyze(variables);
variables.decrementScope();
locals.incrementScope();
elseblock.analyze(locals);
locals.decrementScope();
methodEscape = ifblock.methodEscape && elseblock.methodEscape;
loopEscape = ifblock.loopEscape && elseblock.loopEscape;

View File

@ -19,9 +19,8 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -38,11 +37,11 @@ public final class SReturn extends AStatement {
}
@Override
void analyze(Variables variables) {
expression.expected = Definition.OBJECT_TYPE;
void analyze(Locals locals) {
expression.expected = locals.getReturnType();
expression.internal = true;
expression.analyze(variables);
expression = expression.cast(variables);
expression.analyze(locals);
expression = expression.cast(locals);
methodEscape = true;
loopEscape = true;

View File

@ -19,34 +19,85 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Executable;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.ExecuteReserved;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.WriterConstants;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.painless.WriterConstants.BASE_CLASS_TYPE;
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
import static org.elasticsearch.painless.WriterConstants.CONSTRUCTOR;
import static org.elasticsearch.painless.WriterConstants.EXECUTE;
import static org.elasticsearch.painless.WriterConstants.MAP_GET;
import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
/**
* The root of all Painless trees. Contains a series of statements.
*/
public final class SSource extends AStatement {
final String name;
final String source;
final ExecuteReserved reserved;
final List<SFunction> functions;
final List<AStatement> statements;
public SSource(Location location, List<AStatement> statements) {
private Locals locals;
private BitSet expressions;
private byte[] bytes;
public SSource(String name, String source, ExecuteReserved reserved, Location location,
List<SFunction> functions, List<AStatement> statements) {
super(location);
this.name = name;
this.source = source;
this.reserved = reserved;
this.functions = Collections.unmodifiableList(functions);
this.statements = Collections.unmodifiableList(statements);
}
public void analyze() {
Map<MethodKey, Method> methods = new HashMap<>();
for (SFunction function : functions) {
function.generate();
MethodKey key = new MethodKey(function.name, function.parameters.size());
if (methods.put(key, function.method) != null) {
throw createError(new IllegalArgumentException("Duplicate functions with name [" + function.name + "]."));
}
}
locals = new Locals(reserved, methods);
analyze(locals);
}
@Override
public void analyze(Variables variables) {
void analyze(Locals locals) {
for (SFunction function : functions) {
function.analyze(locals);
}
if (statements == null || statements.isEmpty()) {
throw createError(new IllegalArgumentException("Cannot generate an empty script."));
}
variables.incrementScope();
locals.incrementScope();
AStatement last = statements.get(statements.size() - 1);
@ -59,17 +110,95 @@ public final class SSource extends AStatement {
statement.lastSource = statement == last;
statement.analyze(variables);
statement.analyze(locals);
methodEscape = statement.methodEscape;
allEscape = statement.allEscape;
}
variables.decrementScope();
locals.decrementScope();
}
public void write() {
// Create the ClassWriter.
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
int classVersion = Opcodes.V1_8;
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
String classBase = BASE_CLASS_TYPE.getInternalName();
String className = CLASS_TYPE.getInternalName();
String classInterfaces[] = reserved.usesScore() ? new String[] { WriterConstants.NEEDS_SCORE_TYPE.getInternalName() } : null;
ClassWriter writer = new ClassWriter(classFrames);
writer.visit(classVersion, classAccess, className, null, classBase, classInterfaces);
writer.visitSource(Location.computeSourceName(name, source), null);
// Create the execute MethodWriter.
expressions = new BitSet(source.length());
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, writer, expressions);
// Write the constructor.
MethodWriter constructor = execute.newMethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR);
constructor.loadThis();
constructor.loadArgs();
constructor.invokeConstructor(org.objectweb.asm.Type.getType(Executable.class), CONSTRUCTOR);
constructor.returnValue();
constructor.endMethod();
// Write the execute method.
write(execute);
execute.endMethod();
// End writing the class and store the generated bytes.
writer.visitEnd();
bytes = writer.toByteArray();
}
@Override
public void write(MethodWriter writer) {
void write(MethodWriter writer) {
for (SFunction function : functions) {
function.write(writer);
}
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);
writer.visitVarInsn(Opcodes.ALOAD, scorer.slot);
writer.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
writer.visitInsn(Opcodes.F2D);
writer.visitVarInsn(Opcodes.DSTORE, score.slot);
}
if (reserved.usesCtx()) {
// if the _ctx value is used, we do this once:
// final Map<String,Object> ctx = input.get("ctx");
Variable input = locals.getVariable(null, ExecuteReserved.PARAMS);
Variable ctx = locals.getVariable(null, ExecuteReserved.CTX);
writer.visitVarInsn(Opcodes.ALOAD, input.slot);
writer.push(ExecuteReserved.CTX);
writer.invokeInterface(MAP_TYPE, MAP_GET);
writer.visitVarInsn(Opcodes.ASTORE, ctx.slot);
}
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);
writer.push(reserved.getMaxLoopCounter());
writer.visitVarInsn(Opcodes.ISTORE, loop.slot);
}
for (AStatement statement : statements) {
statement.write(writer);
}
@ -79,4 +208,12 @@ public final class SSource extends AStatement {
writer.returnValue();
}
}
public BitSet getExpressions() {
return expressions;
}
public byte[] getBytes() {
return bytes;
}
}

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
@ -38,10 +38,10 @@ public final class SThrow extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
expression.expected = Definition.EXCEPTION_TYPE;
expression.analyze(variables);
expression = expression.cast(variables);
expression.analyze(locals);
expression = expression.cast(locals);
methodEscape = true;
loopEscape = true;

View File

@ -19,7 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -43,7 +43,7 @@ public final class STry extends AStatement {
}
@Override
void analyze(Variables variables) {
void analyze(Locals locals) {
if (block == null) {
throw createError(new IllegalArgumentException("Extraneous try statement."));
}
@ -52,9 +52,9 @@ public final class STry extends AStatement {
block.inLoop = inLoop;
block.lastLoop = lastLoop;
variables.incrementScope();
block.analyze(variables);
variables.decrementScope();
locals.incrementScope();
block.analyze(locals);
locals.decrementScope();
methodEscape = block.methodEscape;
loopEscape = block.loopEscape;
@ -69,9 +69,9 @@ public final class STry extends AStatement {
catc.inLoop = inLoop;
catc.lastLoop = lastLoop;
variables.incrementScope();
catc.analyze(variables);
variables.decrementScope();
locals.incrementScope();
catc.analyze(locals);
locals.decrementScope();
methodEscape &= catc.methodEscape;
loopEscape &= catc.loopEscape;

View File

@ -21,7 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
@ -30,25 +30,23 @@ import org.elasticsearch.painless.MethodWriter;
*/
public final class SWhile extends AStatement {
final int maxLoopCounter;
AExpression condition;
final SBlock block;
public SWhile(Location location, int maxLoopCounter, AExpression condition, SBlock block) {
public SWhile(Location location, AExpression condition, SBlock block) {
super(location);
this.maxLoopCounter = maxLoopCounter;
this.condition = condition;
this.block = block;
}
@Override
void analyze(Variables variables) {
variables.incrementScope();
void analyze(Locals locals) {
locals.incrementScope();
condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(variables);
condition = condition.cast(variables);
condition.analyze(locals);
condition = condition.cast(locals);
boolean continuous = false;
@ -68,7 +66,7 @@ public final class SWhile extends AStatement {
block.beginLoop = true;
block.inLoop = true;
block.analyze(variables);
block.analyze(locals);
if (block.loopEscape && !block.anyContinue) {
throw createError(new IllegalArgumentException("Extraneous while loop."));
@ -84,11 +82,11 @@ public final class SWhile extends AStatement {
statementCount = 1;
if (maxLoopCounter > 0) {
loopCounterSlot = variables.getVariable(location, "#loop").slot;
if (locals.getMaxLoopCounter() > 0) {
loopCounterSlot = locals.getVariable(location, "#loop").slot;
}
variables.decrementScope();
locals.decrementScope();
}
@Override

View File

@ -49,7 +49,8 @@
* {@link org.elasticsearch.painless.node.IDefLink} - A marker interface for all LDef* (link) nodes.
* {@link org.elasticsearch.painless.node.LArrayLength} - Represents an array length field load.
* {@link org.elasticsearch.painless.node.LBrace} - Represents an array load/store or defers to possible shortcuts.
* {@link org.elasticsearch.painless.node.LCall} - Represents a method call or defers to a def call.
* {@link org.elasticsearch.painless.node.LCallInvoke} - Represents a method call or defers to a def call.
* {@link org.elasticsearch.painless.node.LCallLocal} - Represents a user-defined call.
* {@link org.elasticsearch.painless.node.LCast} - Represents a cast made in a variable/method chain.
* {@link org.elasticsearch.painless.node.LDefArray} - Represents an array load/store or shortcut on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.LDefCall} - Represents a method call made on a def type. (Internal only.)
@ -73,6 +74,7 @@
* {@link org.elasticsearch.painless.node.SEach} - Represents a for each loop shortcut for iterables.
* {@link org.elasticsearch.painless.node.SExpression} - Represents the top-level node for an expression as a statement.
* {@link org.elasticsearch.painless.node.SFor} - Represents a for loop.
* {@link org.elasticsearch.painless.node.SFunction} - Represents a user-defined function.
* {@link org.elasticsearch.painless.node.SIf} - Represents an if block.
* {@link org.elasticsearch.painless.node.SIfElse} - Represents an if/else block.
* {@link org.elasticsearch.painless.node.SReturn} - Represents a return statement.

View File

@ -0,0 +1,69 @@
/*
* 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;
public class FunctionTests extends ScriptTestCase {
public void testBasic() {
assertEquals(5, exec("int get() {5;} get()"));
}
public void testReference() {
assertEquals(5, exec("void get(int[] x) {x[0] = 5;} int[] y = new int[1]; y[0] = 1; get(y); y[0]"));
}
public void testConcat() {
assertEquals("xyxy", exec("String catcat(String single) {single + single;} catcat('xy')"));
}
public void testMultiArgs() {
assertEquals(5, exec("int add(int x, int y) {return x + y;} int x = 1, y = 2; add(add(x, x), add(x, y))"));
}
public void testMultiFuncs() {
assertEquals(1, exec("int add(int x, int y) {return x + y;} int sub(int x, int y) {return x - y;} add(2, sub(3, 4))"));
assertEquals(3, exec("int sub2(int x, int y) {sub(x, y) - y;} int sub(int x, int y) {return x - y;} sub2(5, 1)"));
}
public void testRecursion() {
assertEquals(55, exec("int fib(int n) {if (n <= 1) return n; else return fib(n-1) + fib(n-2);} fib(10)"));
}
public void testEmpty() {
Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("void test(int x) {} test()");
});
assertTrue(expected.getMessage().contains("Cannot generate an empty function"));
}
public void testDuplicates() {
Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("void test(int x) {x = 2;} void test(def y) {y = 3;} test()");
});
assertTrue(expected.getMessage().contains("Duplicate functions"));
}
public void testInfiniteLoop() {
Error expected = expectScriptThrows(PainlessError.class, () -> {
exec("void test() {boolean x = true; while (x) {}} test()");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
}

View File

@ -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 name [_score] is reserved"));
assertTrue(expected.getMessage().contains("Variable [_score] is reserved"));
}
/** 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 name [doc] is reserved"));
assertTrue(expected.getMessage().contains("Variable [doc] is reserved"));
}
/** 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 name [ctx] is reserved"));
assertTrue(expected.getMessage().contains("Variable [ctx] is reserved"));
}
/** 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 name [_value] is reserved"));
assertTrue(expected.getMessage().contains("Variable [_value] is reserved"));
}
/** check that we can't write to _value, its read-only! */