Merge pull request #18954 from rmuir/lambda_captures

Painless: add lambda captures
This commit is contained in:
Robert Muir 2016-06-20 13:05:55 -04:00 committed by GitHub
commit 09305a0f98
61 changed files with 839 additions and 206 deletions

View File

@ -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);

View File

@ -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");
}

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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";

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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());
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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<>();

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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")));

View File

@ -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"));
}
}

View File

@ -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);"));
}
}