Merge pull request #18954 from rmuir/lambda_captures
Painless: add lambda captures
This commit is contained in:
commit
09305a0f98
|
@ -28,6 +28,7 @@ import java.lang.invoke.MethodHandle;
|
|||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -222,19 +223,25 @@ public final class Def {
|
|||
*/
|
||||
static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType,
|
||||
Class<?> receiverClass, String name, Object args[]) throws Throwable {
|
||||
long recipe = (Long) args[0];
|
||||
String recipeString = (String) args[0];
|
||||
int numArguments = callSiteType.parameterCount();
|
||||
// simple case: no lambdas
|
||||
if (recipe == 0) {
|
||||
if (recipeString.isEmpty()) {
|
||||
return lookupMethodInternal(receiverClass, name, numArguments - 1).handle;
|
||||
}
|
||||
|
||||
// convert recipe string to a bitset for convenience (the code below should be refactored...)
|
||||
BitSet lambdaArgs = new BitSet();
|
||||
for (int i = 0; i < recipeString.length(); i++) {
|
||||
lambdaArgs.set(recipeString.charAt(i));
|
||||
}
|
||||
|
||||
// otherwise: first we have to compute the "real" arity. This is because we have extra arguments:
|
||||
// e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i).
|
||||
int arity = callSiteType.parameterCount() - 1;
|
||||
int upTo = 1;
|
||||
for (int i = 0; i < numArguments; i++) {
|
||||
if ((recipe & (1L << (i - 1))) != 0) {
|
||||
for (int i = 1; i < numArguments; i++) {
|
||||
if (lambdaArgs.get(i - 1)) {
|
||||
String signature = (String) args[upTo++];
|
||||
int numCaptures = Integer.parseInt(signature.substring(signature.indexOf(',')+1));
|
||||
arity -= numCaptures;
|
||||
|
@ -250,7 +257,7 @@ public final class Def {
|
|||
upTo = 1;
|
||||
for (int i = 1; i < numArguments; i++) {
|
||||
// its a functional reference, replace the argument with an impl
|
||||
if ((recipe & (1L << (i - 1))) != 0) {
|
||||
if (lambdaArgs.get(i - 1)) {
|
||||
// decode signature of form 'type.call,2'
|
||||
String signature = (String) args[upTo++];
|
||||
int separator = signature.indexOf('.');
|
||||
|
@ -334,6 +341,12 @@ public final class Def {
|
|||
MethodHandle.class);
|
||||
handle = (MethodHandle) accessor.invokeExact();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
|
||||
// because the arity does not match the expected interface type.
|
||||
if (call.contains("$")) {
|
||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
|
||||
"] in [" + clazz.clazz + "]");
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
|
||||
}
|
||||
ref = new FunctionRef(clazz, interfaceMethod, handle, captures);
|
||||
|
|
|
@ -397,11 +397,11 @@ public final class DefBootstrap {
|
|||
if (args.length == 0) {
|
||||
throw new BootstrapMethodError("Invalid number of parameters for method call");
|
||||
}
|
||||
if (args[0] instanceof Long == false) {
|
||||
if (args[0] instanceof String == false) {
|
||||
throw new BootstrapMethodError("Illegal parameter for method call: " + args[0]);
|
||||
}
|
||||
long recipe = (Long) args[0];
|
||||
int numLambdas = Long.bitCount(recipe);
|
||||
String recipe = (String) args[0];
|
||||
int numLambdas = recipe.length();
|
||||
if (numLambdas > type.parameterCount()) {
|
||||
throw new BootstrapMethodError("Illegal recipe for method call: too many bits");
|
||||
}
|
||||
|
|
|
@ -176,7 +176,12 @@ public class FunctionRef {
|
|||
* If the interface expects a primitive type to be returned, we can't return Object,
|
||||
* But we can set SAM to the wrapper version, and a cast will take place
|
||||
*/
|
||||
private static MethodType adapt(MethodType expected, MethodType actual) {
|
||||
private MethodType adapt(MethodType expected, MethodType actual) {
|
||||
// add some checks, now that we've set everything up, to deliver exceptions as early as possible.
|
||||
if (expected.parameterCount() != actual.parameterCount()) {
|
||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + invokedName +
|
||||
"] in [" + invokedType.returnType() + "]");
|
||||
}
|
||||
if (expected.returnType().isPrimitive() && actual.returnType() == Object.class) {
|
||||
actual = actual.changeReturnType(MethodType.methodType(expected.returnType()).wrap().returnType());
|
||||
}
|
||||
|
|
|
@ -1,62 +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.Definition.Type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Extension of locals for lambdas */
|
||||
// Note: this isn't functional yet, it throws UOE
|
||||
// TODO: implement slot renumbering for captures.
|
||||
class LambdaLocals extends Locals {
|
||||
private List<Variable> captures;
|
||||
|
||||
LambdaLocals(Locals parent, List<Parameter> parameters, List<Variable> captures) {
|
||||
super(parent);
|
||||
for (Parameter parameter : parameters) {
|
||||
defineVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
}
|
||||
this.captures = Objects.requireNonNull(captures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variable getVariable(Location location, String name) {
|
||||
Variable variable = lookupVariable(location, name);
|
||||
if (variable != null) {
|
||||
return variable;
|
||||
}
|
||||
if (getParent() != null) {
|
||||
variable = getParent().getVariable(location, name);
|
||||
if (variable != null) {
|
||||
assert captures != null; // unused right now
|
||||
// make it read-only, and record that it was used.
|
||||
throw new UnsupportedOperationException("lambda capture is not supported");
|
||||
}
|
||||
}
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getReturnType() {
|
||||
return Definition.DEF_TYPE;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import java.util.Set;
|
|||
/**
|
||||
* Tracks user defined methods and variables across compilation phases.
|
||||
*/
|
||||
public class Locals {
|
||||
public final class Locals {
|
||||
|
||||
/** Reserved word: params map parameter */
|
||||
public static final String PARAMS = "params";
|
||||
|
@ -64,16 +64,30 @@ public class Locals {
|
|||
return new Locals(currentScope);
|
||||
}
|
||||
|
||||
/** Creates a new lambda scope inside the current scope */
|
||||
public static Locals newLambdaScope(Locals currentScope, List<Parameter> parameters, List<Variable> captures) {
|
||||
return new LambdaLocals(currentScope, parameters, captures);
|
||||
/**
|
||||
* Creates a new lambda scope inside the current scope
|
||||
* <p>
|
||||
* This is just like {@link #newFunctionScope}, except the captured parameters are made read-only.
|
||||
*/
|
||||
public static Locals newLambdaScope(Locals programScope, List<Parameter> parameters, int captureCount, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, Definition.DEF_TYPE);
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
Parameter parameter = parameters.get(i);
|
||||
boolean isCapture = i < captureCount;
|
||||
locals.addVariable(parameter.location, parameter.type, parameter.name, isCapture);
|
||||
}
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (maxLoopCounter > 0) {
|
||||
locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
/** Creates a new function scope inside the current scope */
|
||||
public static Locals newFunctionScope(Locals programScope, Type returnType, List<Parameter> parameters, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, returnType);
|
||||
for (Parameter parameter : parameters) {
|
||||
locals.defineVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
locals.addVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
}
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (maxLoopCounter > 0) {
|
||||
|
@ -129,7 +143,7 @@ public class Locals {
|
|||
}
|
||||
|
||||
/** Checks if a variable exists or not, in this scope or any parents. */
|
||||
public final boolean hasVariable(String name) {
|
||||
public boolean hasVariable(String name) {
|
||||
Variable variable = lookupVariable(null, name);
|
||||
if (variable != null) {
|
||||
return true;
|
||||
|
@ -153,7 +167,7 @@ public class Locals {
|
|||
}
|
||||
|
||||
/** Looks up a method. Returns null if the method does not exist. */
|
||||
public final Method getMethod(MethodKey key) {
|
||||
public Method getMethod(MethodKey key) {
|
||||
Method method = lookupMethod(key);
|
||||
if (method != null) {
|
||||
return method;
|
||||
|
@ -165,7 +179,7 @@ public class Locals {
|
|||
}
|
||||
|
||||
/** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */
|
||||
public final Variable addVariable(Location location, Type type, String name, boolean readonly) {
|
||||
public Variable addVariable(Location location, Type type, String name, boolean readonly) {
|
||||
if (hasVariable(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
|
||||
}
|
||||
|
@ -196,23 +210,23 @@ public class Locals {
|
|||
// return type of this scope
|
||||
private final Type returnType;
|
||||
// next slot number to assign
|
||||
int nextSlotNumber;
|
||||
private int nextSlotNumber;
|
||||
// variable name -> variable
|
||||
Map<String,Variable> variables;
|
||||
private Map<String,Variable> variables;
|
||||
// method name+arity -> methods
|
||||
Map<MethodKey,Method> methods;
|
||||
private Map<MethodKey,Method> methods;
|
||||
|
||||
/**
|
||||
* Create a new Locals
|
||||
*/
|
||||
Locals(Locals parent) {
|
||||
private Locals(Locals parent) {
|
||||
this(parent, parent.getReturnType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Locals with specified return type
|
||||
*/
|
||||
Locals(Locals parent, Type returnType) {
|
||||
private Locals(Locals parent, Type returnType) {
|
||||
this.parent = parent;
|
||||
this.returnType = returnType;
|
||||
if (parent == null) {
|
||||
|
@ -223,12 +237,12 @@ public class Locals {
|
|||
}
|
||||
|
||||
/** Returns the parent scope */
|
||||
Locals getParent() {
|
||||
private Locals getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/** Looks up a variable at this scope only. Returns null if the variable does not exist. */
|
||||
Variable lookupVariable(Location location, String name) {
|
||||
private Variable lookupVariable(Location location, String name) {
|
||||
if (variables == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -236,7 +250,7 @@ public class Locals {
|
|||
}
|
||||
|
||||
/** Looks up a method at this scope only. Returns null if the method does not exist. */
|
||||
Method lookupMethod(MethodKey key) {
|
||||
private Method lookupMethod(MethodKey key) {
|
||||
if (methods == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -245,19 +259,17 @@ public class Locals {
|
|||
|
||||
|
||||
/** Defines a variable at this scope internally. */
|
||||
Variable defineVariable(Location location, Type type, String name, boolean readonly) {
|
||||
private Variable defineVariable(Location location, Type type, String name, boolean readonly) {
|
||||
if (variables == null) {
|
||||
variables = new HashMap<>();
|
||||
}
|
||||
Variable variable = new Variable(location, name, type, readonly);
|
||||
variable.slot = getNextSlot();
|
||||
Variable variable = new Variable(location, name, type, getNextSlot(), readonly);
|
||||
variables.put(name, variable); // TODO: check result
|
||||
nextSlotNumber += type.type.getSize();
|
||||
return variable;
|
||||
}
|
||||
|
||||
// TODO: make private, thats bogus
|
||||
public void addMethod(Method method) {
|
||||
private void addMethod(Method method) {
|
||||
if (methods == null) {
|
||||
methods = new HashMap<>();
|
||||
}
|
||||
|
@ -266,7 +278,7 @@ public class Locals {
|
|||
}
|
||||
|
||||
|
||||
int getNextSlot() {
|
||||
private int getNextSlot() {
|
||||
return nextSlotNumber;
|
||||
}
|
||||
|
||||
|
@ -274,13 +286,14 @@ public class Locals {
|
|||
public final Location location;
|
||||
public final String name;
|
||||
public final Type type;
|
||||
int slot = -1;
|
||||
public final boolean readonly;
|
||||
private final int slot;
|
||||
|
||||
public Variable(Location location, String name, Type type, boolean readonly) {
|
||||
public Variable(Location location, String name, Type type, int slot, boolean readonly) {
|
||||
this.location = location;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.slot = slot;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
|
||||
|
@ -289,7 +302,7 @@ public class Locals {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Parameter {
|
||||
public static final class Parameter {
|
||||
public final Location location;
|
||||
public final String name;
|
||||
public final Type type;
|
||||
|
|
|
@ -21,6 +21,9 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The superclass for all other nodes.
|
||||
*/
|
||||
|
@ -31,9 +34,17 @@ public abstract class ANode {
|
|||
final Location location;
|
||||
|
||||
ANode(Location location) {
|
||||
this.location = location;
|
||||
this.location = Objects.requireNonNull(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all variable names referenced to the variable set.
|
||||
* <p>
|
||||
* This can be called at any time, e.g. to support lambda capture.
|
||||
* @param variables set of variables referenced (any scope)
|
||||
*/
|
||||
abstract void extractVariables(Set<String> variables);
|
||||
|
||||
public RuntimeException createError(RuntimeException exception) {
|
||||
return location.createError(exception);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
|
@ -46,9 +50,15 @@ public final class EBinary extends AExpression {
|
|||
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
||||
this.operation = operation;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
right.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -39,9 +43,15 @@ public final class EBool extends AExpression {
|
|||
public EBool(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
||||
this.operation = operation;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
right.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a boolean constant.
|
||||
*/
|
||||
|
@ -35,6 +37,9 @@ public final class EBoolean extends AExpression {
|
|||
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -34,32 +34,39 @@ import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
|||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a capturing function reference.
|
||||
*/
|
||||
public class ECapturingFunctionRef extends AExpression implements ILambda {
|
||||
public final String type;
|
||||
public final String variable;
|
||||
public final String call;
|
||||
|
||||
private FunctionRef ref;
|
||||
Variable captured;
|
||||
String defPointer;
|
||||
|
||||
public ECapturingFunctionRef(Location location, String type, String call) {
|
||||
public ECapturingFunctionRef(Location location, String variable, String call) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.call = call;
|
||||
this.variable = Objects.requireNonNull(variable);
|
||||
this.call = Objects.requireNonNull(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals variables) {
|
||||
captured = variables.getVariable(location, type);
|
||||
captured = variables.getVariable(location, variable);
|
||||
if (expected == null) {
|
||||
if (captured.type.sort == Definition.Sort.DEF) {
|
||||
// dynamic implementation
|
||||
defPointer = "D" + type + "." + call + ",1";
|
||||
defPointer = "D" + variable + "." + call + ",1";
|
||||
} else {
|
||||
// typed implementation
|
||||
defPointer = "S" + captured.type.name + "." + call + ",1";
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
@ -40,9 +44,13 @@ final class ECast extends AExpression {
|
|||
super(location);
|
||||
|
||||
this.type = null;
|
||||
this.child = child;
|
||||
|
||||
this.cast = cast;
|
||||
this.child = Objects.requireNonNull(child);
|
||||
this.cast = Objects.requireNonNull(cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,6 +32,8 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the entirety of a variable/method chain for read/write operations.
|
||||
|
@ -59,12 +61,22 @@ public final class EChain extends AExpression {
|
|||
boolean pre, boolean post, Operation operation, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.links = links;
|
||||
this.links = Objects.requireNonNull(links);
|
||||
this.pre = pre;
|
||||
this.post = post;
|
||||
this.operation = operation;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (ALink link : links) {
|
||||
link.extractVariables(variables);
|
||||
}
|
||||
if (expression != null) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -29,6 +29,10 @@ import org.elasticsearch.painless.DefBootstrap;
|
|||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
|
||||
|
@ -48,9 +52,15 @@ public final class EComp extends AExpression {
|
|||
public EComp(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
||||
this.operation = operation;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
right.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,10 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -40,9 +44,16 @@ public final class EConditional extends AExpression {
|
|||
public EConditional(Location location, AExpression condition, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
||||
this.condition = condition;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.condition = Objects.requireNonNull(condition);
|
||||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
left.extractVariables(variables);
|
||||
right.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,9 @@ package org.elasticsearch.painless.node;
|
|||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
@ -37,6 +40,9 @@ final class EConstant extends AExpression {
|
|||
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -25,8 +25,11 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Respresents a decimal constant.
|
||||
* Represents a decimal constant.
|
||||
*/
|
||||
public final class EDecimal extends AExpression {
|
||||
|
||||
|
@ -35,8 +38,11 @@ public final class EDecimal extends AExpression {
|
|||
public EDecimal(Location location, String value) {
|
||||
super(location);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an explicit cast.
|
||||
*/
|
||||
|
@ -36,8 +39,13 @@ public final class EExplicit extends AExpression {
|
|||
public EExplicit(Location location, String type, AExpression child) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.child = child;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.child = Objects.requireNonNull(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,6 +32,8 @@ import org.objectweb.asm.Type;
|
|||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a function reference.
|
||||
|
@ -46,9 +48,12 @@ public class EFunctionRef extends AExpression implements ILambda {
|
|||
public EFunctionRef(Location location, String type, String call) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.call = call;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.call = Objects.requireNonNull(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -23,72 +23,194 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Lambda expression node.
|
||||
* <p>
|
||||
* This can currently only be the direct argument of a call (method/constructor).
|
||||
* When the argument is of a known type, it uses
|
||||
* <a href="http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html">
|
||||
* Java's lambda translation</a>. However, if its a def call, then we don't have
|
||||
* enough information, and have to defer this until link time. In that case a placeholder
|
||||
* and all captures are pushed onto the stack and folded into the signature of the parent call.
|
||||
* <p>
|
||||
* For example:
|
||||
* <br>
|
||||
* {@code def list = new ArrayList(); int capture = 0; list.sort((x,y) -> x - y + capture)}
|
||||
* <br>
|
||||
* is converted into a call (pseudocode) such as:
|
||||
* <br>
|
||||
* {@code sort(list, lambda$0, capture)}
|
||||
* <br>
|
||||
* At link time, when we know the interface type, this is decomposed with MethodHandle
|
||||
* combinators back into (pseudocode):
|
||||
* <br>
|
||||
* {@code sort(list, lambda$0(capture))}
|
||||
*/
|
||||
public class ELambda extends AExpression implements ILambda {
|
||||
final String name;
|
||||
final FunctionReserved reserved;
|
||||
final List<String> paramTypeStrs;
|
||||
final List<String> paramNameStrs;
|
||||
final List<AStatement> statements;
|
||||
|
||||
// desugared synthetic method (lambda body)
|
||||
SFunction desugared;
|
||||
// method ref (impl detail)
|
||||
ILambda impl;
|
||||
// captured variables
|
||||
List<Variable> captures;
|
||||
// static parent, static lambda
|
||||
FunctionRef ref;
|
||||
// dynamic parent, deferred until link time
|
||||
String defPointer;
|
||||
|
||||
public ELambda(String name, FunctionReserved reserved,
|
||||
Location location, List<String> paramTypes, List<String> paramNames,
|
||||
List<AStatement> statements) {
|
||||
super(location);
|
||||
this.name = name;
|
||||
this.reserved = reserved;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.reserved = Objects.requireNonNull(reserved);
|
||||
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
|
||||
this.paramNameStrs = Collections.unmodifiableList(paramNames);
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AStatement statement : statements) {
|
||||
statement.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
// gather any variables used by the lambda body first.
|
||||
Set<String> variables = new HashSet<>();
|
||||
for (AStatement statement : statements) {
|
||||
statement.extractVariables(variables);
|
||||
}
|
||||
// any of those variables defined in our scope need to be captured
|
||||
captures = new ArrayList<>();
|
||||
for (String variable : variables) {
|
||||
if (locals.hasVariable(variable)) {
|
||||
captures.add(locals.getVariable(location, variable));
|
||||
}
|
||||
}
|
||||
// prepend capture list to lambda's arguments
|
||||
List<String> paramTypes = new ArrayList<>();
|
||||
List<String> paramNames = new ArrayList<>();
|
||||
for (Variable var : captures) {
|
||||
paramTypes.add(var.type.name);
|
||||
paramNames.add(var.name);
|
||||
}
|
||||
paramTypes.addAll(paramTypeStrs);
|
||||
paramNames.addAll(paramNameStrs);
|
||||
|
||||
// desugar lambda body into a synthetic method
|
||||
desugared = new SFunction(reserved, location, "def", name,
|
||||
paramTypeStrs, paramNameStrs, statements, true);
|
||||
paramTypes, paramNames, statements, true);
|
||||
desugared.generate();
|
||||
List<Variable> captures = new ArrayList<>();
|
||||
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters, captures));
|
||||
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters,
|
||||
captures.size(), reserved.getMaxLoopCounter()));
|
||||
|
||||
// setup reference
|
||||
EFunctionRef ref = new EFunctionRef(location, "this", name);
|
||||
ref.expected = expected;
|
||||
// hack, create a new scope, with our method, so the ref can see it (impl detail)
|
||||
locals = Locals.newLocalScope(locals);
|
||||
locals.addMethod(desugared.method);
|
||||
ref.analyze(locals);
|
||||
actual = ref.actual;
|
||||
impl = ref;
|
||||
// setup method reference to synthetic method
|
||||
if (expected == null) {
|
||||
ref = null;
|
||||
actual = Definition.getType("String");
|
||||
defPointer = "Sthis." + name + "," + captures.size();
|
||||
} else {
|
||||
defPointer = null;
|
||||
try {
|
||||
Method interfaceMethod = expected.struct.getFunctionalMethod();
|
||||
if (interfaceMethod == null) {
|
||||
throw new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
|
||||
"], not a functional interface");
|
||||
}
|
||||
Class<?> captureClasses[] = new Class<?>[captures.size()];
|
||||
for (int i = 0; i < captures.size(); i++) {
|
||||
captureClasses[i] = captures.get(i).type.clazz;
|
||||
}
|
||||
ref = new FunctionRef(expected, interfaceMethod, desugared.method, captureClasses);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw createError(e);
|
||||
}
|
||||
actual = expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
AExpression expr = (AExpression) impl;
|
||||
expr.write(writer, globals);
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (ref != null) {
|
||||
writer.writeDebugInfo(location);
|
||||
// load captures
|
||||
for (Variable capture : captures) {
|
||||
writer.visitVarInsn(capture.type.type.getOpcode(Opcodes.ILOAD), capture.getSlot());
|
||||
}
|
||||
// convert MethodTypes to asm Type for the constant pool.
|
||||
String invokedType = ref.invokedType.toMethodDescriptorString();
|
||||
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
|
||||
Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
|
||||
if (ref.needsBridges()) {
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
LambdaMetafactory.FLAG_BRIDGES,
|
||||
1,
|
||||
interfaceType);
|
||||
} else {
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
0);
|
||||
}
|
||||
} else {
|
||||
// placeholder
|
||||
writer.push((String)null);
|
||||
// load captures
|
||||
for (Variable capture : captures) {
|
||||
writer.visitVarInsn(capture.type.type.getOpcode(Opcodes.ILOAD), capture.getSlot());
|
||||
}
|
||||
}
|
||||
|
||||
// add synthetic method to the queue to be written
|
||||
globals.addSyntheticMethod(desugared);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPointer() {
|
||||
return impl.getPointer();
|
||||
return defPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getCaptures() {
|
||||
return impl.getCaptures();
|
||||
Type[] types = new Type[captures.size()];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
types[i] = captures.get(i).type.type;
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -34,6 +37,9 @@ public final class ENull extends AExpression {
|
|||
public ENull(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -23,6 +23,10 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -37,9 +41,12 @@ public final class ENumeric extends AExpression {
|
|||
public ENumeric(Location location, String value, int radix) {
|
||||
super(location);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
this.radix = radix;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -29,6 +29,10 @@ import org.elasticsearch.painless.DefBootstrap;
|
|||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
@ -45,8 +49,13 @@ public final class EUnary extends AExpression {
|
|||
public EUnary(Location location, Operation operation, AExpression child) {
|
||||
super(location);
|
||||
|
||||
this.operation = operation;
|
||||
this.child = child;
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
this.child = Objects.requireNonNull(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array length field load.
|
||||
*/
|
||||
|
@ -35,8 +38,11 @@ public final class LArrayLength extends ALink {
|
|||
LArrayLength(Location location, String value) {
|
||||
super(location, -1);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array load/store or defers to possible shortcuts.
|
||||
|
@ -39,7 +41,12 @@ public final class LBrace extends ALink {
|
|||
public LBrace(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
|
||||
this.index = index;
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,9 +29,11 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a method call or deferes to a def call.
|
||||
* Represents a method call or defers to a def call.
|
||||
*/
|
||||
public final class LCallInvoke extends ALink {
|
||||
|
||||
|
@ -43,8 +45,15 @@ public final class LCallInvoke extends ALink {
|
|||
public LCallInvoke(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.name = name;
|
||||
this.arguments = arguments;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
||||
|
||||
|
@ -43,8 +45,15 @@ public class LCallLocal extends ALink {
|
|||
public LCallLocal(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.name = name;
|
||||
this.arguments = arguments;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,6 +23,9 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
@ -41,6 +44,9 @@ public final class LCast extends ALink {
|
|||
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
@ -39,7 +43,12 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
LDefArray(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
|
||||
this.index = index;
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.objectweb.asm.Type;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
||||
|
@ -39,26 +41,26 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
|
||||
final String name;
|
||||
final List<AExpression> arguments;
|
||||
long recipe;
|
||||
StringBuilder recipe;
|
||||
List<String> pointers = new ArrayList<>();
|
||||
|
||||
LDefCall(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.name = name;
|
||||
this.arguments = arguments;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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.
|
||||
// but NEED NOT BE. nothing with this many parameters is in the whitelist and we do not support varargs.
|
||||
throw new UnsupportedOperationException("methods with > 63 arguments are currently not supported");
|
||||
}
|
||||
|
||||
recipe = 0;
|
||||
recipe = new StringBuilder();
|
||||
int totalCaptures = 0;
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
@ -69,7 +71,9 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
if (expression instanceof ILambda) {
|
||||
ILambda lambda = (ILambda) expression;
|
||||
pointers.add(lambda.getPointer());
|
||||
recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
|
||||
// encode this parameter as a deferred reference
|
||||
char ch = (char) (argument + totalCaptures);
|
||||
recipe.append(ch);
|
||||
totalCaptures += lambda.getCaptureCount();
|
||||
}
|
||||
|
||||
|
@ -115,7 +119,7 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(DefBootstrap.METHOD_CALL);
|
||||
args.add(recipe);
|
||||
args.add(recipe.toString());
|
||||
args.addAll(pointers);
|
||||
writer.invokeDynamic(name, signature.toString(), DEF_BOOTSTRAP_HANDLE, args.toArray());
|
||||
}
|
||||
|
|
|
@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
@ -39,9 +43,11 @@ final class LDefField extends ALink implements IDefLink {
|
|||
LDefField(Location location, String value) {
|
||||
super(location, 1);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -30,6 +30,8 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a field load/store or defers to a possible shortcuts.
|
||||
|
@ -43,8 +45,11 @@ public final class LField extends ALink {
|
|||
public LField(Location location, String value) {
|
||||
super(location, 1);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -39,7 +43,12 @@ final class LListShortcut extends ALink {
|
|||
LListShortcut(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
|
||||
this.index = index;
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -39,7 +43,12 @@ final class LMapShortcut extends ALink {
|
|||
LMapShortcut(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
|
||||
this.index = index;
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array instantiation.
|
||||
|
@ -39,8 +41,15 @@ public final class LNewArray extends ALink {
|
|||
public LNewArray(Location location, String type, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.type = type;
|
||||
this.arguments = arguments;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents and object instantiation.
|
||||
|
@ -43,8 +45,15 @@ public final class LNewObj extends ALink {
|
|||
public LNewObj(Location location, String type, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.type = type;
|
||||
this.arguments = arguments;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
|
@ -54,6 +55,9 @@ public final class LRegex extends ALink {
|
|||
throw createError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Struct;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -41,8 +45,11 @@ final class LShortcut extends ALink {
|
|||
LShortcut(Location location, String value) {
|
||||
super(location, 1);
|
||||
|
||||
this.value = value;
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -23,6 +23,10 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
||||
/**
|
||||
|
@ -35,8 +39,11 @@ public final class LStatic extends ALink {
|
|||
public LStatic(Location location, String type) {
|
||||
super(location, 0);
|
||||
|
||||
this.type = type;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a string constant.
|
||||
*/
|
||||
|
@ -33,8 +36,11 @@ public final class LString extends ALink {
|
|||
public LString(Location location, String string) {
|
||||
super(location, -1);
|
||||
|
||||
this.string = string;
|
||||
this.string = Objects.requireNonNull(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
|
|
|
@ -26,6 +26,9 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a variable load/store.
|
||||
*/
|
||||
|
@ -38,7 +41,12 @@ public final class LVariable extends ALink {
|
|||
public LVariable(Location location, String name) {
|
||||
super(location, 0);
|
||||
|
||||
this.name = name;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a set of statements as a branch of control-flow.
|
||||
|
@ -39,6 +40,13 @@ public final class SBlock extends AStatement {
|
|||
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AStatement statement : statements) {
|
||||
statement.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a break statement.
|
||||
*/
|
||||
|
@ -32,6 +34,9 @@ public final class SBreak extends AStatement {
|
|||
public SBreak(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -27,6 +27,10 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -47,10 +51,18 @@ public final class SCatch extends AStatement {
|
|||
public SCatch(Location location, String type, String name, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a continue statement.
|
||||
*/
|
||||
|
@ -32,6 +34,9 @@ public final class SContinue extends AStatement {
|
|||
public SContinue(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a series of declarations.
|
||||
|
@ -39,6 +40,13 @@ public final class SDeclBlock extends AStatement {
|
|||
|
||||
this.declarations = Collections.unmodifiableList(declarations);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (SDeclaration declaration : declarations) {
|
||||
declaration.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -26,6 +26,10 @@ import org.elasticsearch.painless.Definition.Type;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -42,10 +46,18 @@ public final class SDeclaration extends AStatement {
|
|||
public SDeclaration(Location location, String type, String name, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
if (expression != null) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -37,9 +41,17 @@ public final class SDo extends AStatement {
|
|||
public SDo(Location location, SBlock block, AExpression condition) {
|
||||
super(location);
|
||||
|
||||
this.condition = condition;
|
||||
this.condition = Objects.requireNonNull(condition);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -35,6 +35,9 @@ import org.elasticsearch.painless.Locals.Variable;
|
|||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
|
||||
|
@ -66,11 +69,20 @@ public class SEach extends AStatement {
|
|||
public SEach(Location location, String type, String name, AExpression expression, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.expression = expression;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
expression.extractVariables(variables);
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
|
@ -36,7 +40,12 @@ public final class SExpression extends AStatement {
|
|||
public SExpression(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = expression;
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,9 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -44,6 +47,22 @@ public final class SFor extends AStatement {
|
|||
this.afterthought = afterthought;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
if (initializer != null) {
|
||||
initializer.extractVariables(variables);
|
||||
}
|
||||
if (condition != null) {
|
||||
condition.extractVariables(variables);
|
||||
}
|
||||
if (afterthought != null) {
|
||||
afterthought.extractVariables(variables);
|
||||
}
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -41,6 +41,8 @@ import java.lang.reflect.Modifier;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
||||
|
||||
|
@ -67,14 +69,20 @@ public class SFunction extends AStatement {
|
|||
List<String> paramNames, List<AStatement> statements, boolean synthetic) {
|
||||
super(location);
|
||||
|
||||
this.reserved = reserved;
|
||||
this.rtnTypeStr = rtnType;
|
||||
this.name = name;
|
||||
this.reserved = Objects.requireNonNull(reserved);
|
||||
this.rtnTypeStr = Objects.requireNonNull(rtnType);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
|
||||
this.paramNameStrs = Collections.unmodifiableList(paramNames);
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.synthetic = synthetic;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
// we should never be extracting from a function, as functions are top-level!
|
||||
throw new IllegalStateException("Illegal tree structure");
|
||||
}
|
||||
|
||||
void generate() {
|
||||
try {
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -37,9 +41,17 @@ public final class SIf extends AStatement {
|
|||
public SIf(Location location, AExpression condition, SBlock ifblock) {
|
||||
super(location);
|
||||
|
||||
this.condition = condition;
|
||||
this.condition = Objects.requireNonNull(condition);
|
||||
this.ifblock = ifblock;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
if (ifblock != null) {
|
||||
ifblock.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -38,10 +42,21 @@ public final class SIfElse extends AStatement {
|
|||
public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock elseblock) {
|
||||
super(location);
|
||||
|
||||
this.condition = condition;
|
||||
this.condition = Objects.requireNonNull(condition);
|
||||
this.ifblock = ifblock;
|
||||
this.elseblock = elseblock;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
if (ifblock != null) {
|
||||
ifblock.extractVariables(variables);
|
||||
}
|
||||
if (elseblock != null) {
|
||||
elseblock.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,9 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a return statement.
|
||||
*/
|
||||
|
@ -34,7 +37,12 @@ public final class SReturn extends AStatement {
|
|||
public SReturn(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = expression;
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,6 +43,8 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.BASE_CLASS_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
||||
|
@ -71,10 +73,10 @@ public final class SSource extends AStatement {
|
|||
List<SFunction> functions, Globals globals, List<AStatement> statements) {
|
||||
super(location);
|
||||
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.source = Objects.requireNonNull(source);
|
||||
this.debugStream = debugStream;
|
||||
this.reserved = reserved;
|
||||
this.reserved = Objects.requireNonNull(reserved);
|
||||
// process any synthetic functions generated by walker (because right now, thats still easy)
|
||||
functions.addAll(globals.getSyntheticMethods().values());
|
||||
globals.getSyntheticMethods().clear();
|
||||
|
@ -82,6 +84,12 @@ public final class SSource extends AStatement {
|
|||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
// we should never be extracting from a function, as functions are top-level!
|
||||
throw new IllegalStateException("Illegal tree structure");
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
Map<MethodKey, Method> methods = new HashMap<>();
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a throw statement.
|
||||
*/
|
||||
|
@ -35,7 +38,12 @@ public final class SThrow extends AStatement {
|
|||
public SThrow(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = expression;
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the try block as part of a try-catch block.
|
||||
|
@ -42,6 +43,16 @@ public final class STry extends AStatement {
|
|||
this.block = block;
|
||||
this.catches = Collections.unmodifiableList(catches);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
for (SCatch expr : catches) {
|
||||
expr.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -24,6 +24,10 @@ import org.elasticsearch.painless.Globals;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
|
@ -37,9 +41,17 @@ public final class SWhile extends AStatement {
|
|||
public SWhile(Location location, AExpression condition, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.condition = condition;
|
||||
this.condition = Objects.requireNonNull(condition);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -36,7 +36,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
DefBootstrap.METHOD_CALL, "");
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
DefBootstrap.METHOD_CALL, "");
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
||||
|
@ -75,7 +75,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
DefBootstrap.METHOD_CALL, "");
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
||||
|
@ -98,7 +98,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"size",
|
||||
MethodType.methodType(int.class, Object.class),
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
DefBootstrap.METHOD_CALL, "");
|
||||
site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertEquals(2, (int)handle.invokeExact((Object) Arrays.asList("1", "2")));
|
||||
|
|
|
@ -162,4 +162,18 @@ public class FunctionRefTests extends ScriptTestCase {
|
|||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
|
||||
});
|
||||
}
|
||||
|
||||
public void testWrongArity() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("Optional.empty().orElseGet(String::startsWith);");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Unknown reference"));
|
||||
}
|
||||
|
||||
public void testWrongArityDef() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Unknown reference"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,24 @@ public class LambdaTests extends ScriptTestCase {
|
|||
public void testUnneededCurlyStatements() {
|
||||
assertEquals(2, exec("int applyOne(IntFunction arg) { arg.apply(1) } applyOne(x -> { x + 1 })"));
|
||||
}
|
||||
|
||||
/** interface ignores return value */
|
||||
public void testVoidReturn() {
|
||||
assertEquals(2, exec("List list = new ArrayList(); "
|
||||
+ "list.add(2); "
|
||||
+ "List list2 = new ArrayList(); "
|
||||
+ "list.forEach(x -> list2.add(x));"
|
||||
+ "return list[0]"));
|
||||
}
|
||||
|
||||
/** interface ignores return value */
|
||||
public void testVoidReturnDef() {
|
||||
assertEquals(2, exec("def list = new ArrayList(); "
|
||||
+ "list.add(2); "
|
||||
+ "List list2 = new ArrayList(); "
|
||||
+ "list.forEach(x -> list2.add(x));"
|
||||
+ "return list[0]"));
|
||||
}
|
||||
|
||||
public void testTwoLambdas() {
|
||||
assertEquals("testingcdefg", exec(
|
||||
|
@ -103,4 +121,70 @@ public class LambdaTests extends ScriptTestCase {
|
|||
"}" +
|
||||
"return sum;"));
|
||||
}
|
||||
|
||||
public void testCapture() {
|
||||
assertEquals(5, exec("int x = 5; return Optional.empty().orElseGet(() -> x);"));
|
||||
}
|
||||
|
||||
public void testTwoCaptures() {
|
||||
assertEquals("1test", exec("int x = 1; String y = 'test'; return Optional.empty().orElseGet(() -> x + y);"));
|
||||
}
|
||||
|
||||
public void testCapturesAreReadOnly() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("List l = new ArrayList(); l.add(1); l.add(1); "
|
||||
+ "return l.stream().mapToInt(x -> { l = null; return x + 1 }).sum();");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("is read-only"));
|
||||
}
|
||||
|
||||
public void testOnlyCapturesAreReadOnly() {
|
||||
assertEquals(4, exec("List l = new ArrayList(); l.add(1); l.add(1); "
|
||||
+ "return l.stream().mapToInt(x -> { x += 1; return x }).sum();"));
|
||||
}
|
||||
|
||||
/** Lambda parameters shouldn't be able to mask a variable already in scope */
|
||||
public void testNoParamMasking() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("int x = 0; List l = new ArrayList(); l.add(1); l.add(1); "
|
||||
+ "return l.stream().mapToInt(x -> { x += 1; return x }).sum();");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("already defined"));
|
||||
}
|
||||
|
||||
public void testCaptureDef() {
|
||||
assertEquals(5, exec("int x = 5; def y = Optional.empty(); y.orElseGet(() -> x);"));
|
||||
}
|
||||
|
||||
public void testNestedCapture() {
|
||||
assertEquals(1, exec("boolean x = false; int y = 1;" +
|
||||
"return Optional.empty().orElseGet(() -> x ? 5 : Optional.empty().orElseGet(() -> y));"));
|
||||
}
|
||||
|
||||
public void testNestedCaptureParams() {
|
||||
assertEquals(2, exec("int foo(Function f) { return f.apply(1) }" +
|
||||
"return foo(x -> foo(y -> x + 1))"));
|
||||
}
|
||||
|
||||
public void testWrongArity() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("Optional.empty().orElseGet(x -> x);");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Incorrect number of parameters"));
|
||||
}
|
||||
|
||||
public void testWrongArityDef() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("def y = Optional.empty(); return y.orElseGet(x -> x);");
|
||||
});
|
||||
assertTrue(expected.getMessage(), expected.getMessage().contains("Incorrect number of parameters"));
|
||||
}
|
||||
|
||||
public void testLambdaInFunction() {
|
||||
assertEquals(5, exec("def foo() { Optional.empty().orElseGet(() -> 5) } return foo();"));
|
||||
}
|
||||
|
||||
public void testLambdaCaptureFunctionParam() {
|
||||
assertEquals(5, exec("def foo(int x) { Optional.empty().orElseGet(() -> x) } return foo(5);"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue