Painless: Clean up FunctionRef (#32644)

This change consolidates all the logic for generating a FunctionReference (renamed from 
FunctionRef) from several arbitrary constructors to a single static function that is used at 
both compile-time and run-time. This increases long-term maintainability as it is much 
easier to follow when and how a function reference is being generated. It moves most of 
the duplicated logic out of the ECapturingFuncRef, EFuncRef and ELambda nodes and 
Def as well.
This commit is contained in:
Jack Conradson 2018-08-07 12:26:57 -07:00 committed by GitHub
parent dcc816427e
commit 0b7fb4e7b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 304 additions and 415 deletions

View File

@ -279,10 +279,6 @@ public final class Def {
String type = signature.substring(1, separator);
String call = signature.substring(separator+1, separator2);
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
Class<?> captures[] = new Class<?>[numCaptures];
for (int capture = 0; capture < captures.length; capture++) {
captures[capture] = callSiteType.parameterType(i + 1 + capture);
}
MethodHandle filter;
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
if (signature.charAt(0) == 'S') {
@ -294,11 +290,15 @@ public final class Def {
interfaceType,
type,
call,
captures);
numCaptures);
} else if (signature.charAt(0) == 'D') {
// the interface type is now known, but we need to get the implementation.
// this is dynamically based on the receiver type (and cached separately, underneath
// this cache). It won't blow up since we never nest here (just references)
Class<?> captures[] = new Class<?>[numCaptures];
for (int capture = 0; capture < captures.length; capture++) {
captures[capture] = callSiteType.parameterType(i + 1 + capture);
}
MethodType nestedType = MethodType.methodType(interfaceType, captures);
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
localMethods,
@ -332,7 +332,7 @@ public final class Def {
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(interfaceType).functionalMethod;
PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType);
if (interfaceMethod == null) {
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
}
@ -340,36 +340,13 @@ public final class Def {
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
implMethod.javaMethod.getName(), receiverClass);
implMethod.javaMethod.getName(), 1);
}
/** Returns a method handle to an implementation of clazz, given method reference signature. */
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, Class<?>... captures) throws Throwable {
final FunctionRef ref;
if ("this".equals(type)) {
// user written method
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(clazz).functionalMethod;
if (interfaceMethod == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface");
}
int arity = interfaceMethod.typeParameters.size() + captures.length;
LocalMethod localMethod = localMethods.get(Locals.buildLocalMethodKey(call, arity));
if (localMethod == null) {
// 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.javaMethod.getName() +
"] in [" + clazz + "]");
}
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
}
ref = new FunctionRef(clazz, interfaceMethod, call, localMethod.methodType, captures.length);
} else {
// whitelist lookup
ref = FunctionRef.resolveFromLookup(painlessLookup, clazz, type, call, captures.length);
}
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures) throws Throwable {
final FunctionRef ref = FunctionRef.create(painlessLookup, localMethods, null, clazz, type, call, captures);
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
methodHandlesLookup,
ref.interfaceMethodName,
@ -381,7 +358,7 @@ public final class Def {
ref.delegateMethodType,
ref.isDelegateInterface ? 1 : 0
);
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures));
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray()));
}
/**

View File

@ -20,17 +20,17 @@
package org.elasticsearch.painless;
import org.elasticsearch.painless.Locals.LocalMethod;
import org.elasticsearch.painless.lookup.PainlessClass;
import org.elasticsearch.painless.lookup.PainlessConstructor;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.PainlessMethod;
import org.objectweb.asm.Type;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
@ -39,251 +39,210 @@ import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
/**
* Reference to a function or lambda.
* <p>
* Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap}
* either statically from bytecode with invokedynamic, or at runtime from Java.
* Contains all the values necessary to write the instruction to initiate a
* {@link LambdaBootstrap} for either a function reference or a user-defined
* lambda function.
*/
public class FunctionRef {
/**
* Creates a new FunctionRef which will resolve {@code type::call} from the whitelist.
* @param painlessLookup the whitelist against which this script is being compiled
* @param localMethods user-defined and synthetic methods generated directly on the script class
* @param location the character number within the script at compile-time
* @param targetClass functional interface type to implement.
* @param typeName the left hand side of a method reference expression
* @param methodName the right hand side of a method reference expression
* @param numberOfCaptures number of captured arguments
*/
public static FunctionRef create(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods, Location location,
Class<?> targetClass, String typeName, String methodName, int numberOfCaptures) {
Objects.requireNonNull(painlessLookup);
Objects.requireNonNull(targetClass);
Objects.requireNonNull(typeName);
Objects.requireNonNull(methodName);
String targetClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
PainlessMethod interfaceMethod;
try {
try {
interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(targetClass);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException("cannot convert function reference [" + typeName + "::" + methodName + "] " +
"to a non-functional interface [" + targetClassName + "]", iae);
}
String interfaceMethodName = interfaceMethod.javaMethod.getName();
MethodType interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
String delegateClassName;
boolean isDelegateInterface;
int delegateInvokeType;
String delegateMethodName;
MethodType delegateMethodType;
Class<?> delegateMethodReturnType;
List<Class<?>> delegateMethodParameters;
int interfaceTypeParametersSize = interfaceMethod.typeParameters.size();
if ("this".equals(typeName)) {
Objects.requireNonNull(localMethods);
if (numberOfCaptures < 0) {
throw new IllegalStateException("internal error");
}
String localMethodKey = Locals.buildLocalMethodKey(methodName, numberOfCaptures + interfaceTypeParametersSize);
LocalMethod localMethod = localMethods.get(localMethodKey);
if (localMethod == null) {
throw new IllegalArgumentException("function reference [this::" + localMethodKey + "] " +
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
"not found" + (localMethodKey.contains("$") ? " due to an incorrect number of arguments" : "")
);
}
delegateClassName = CLASS_NAME;
isDelegateInterface = false;
delegateInvokeType = H_INVOKESTATIC;
delegateMethodName = localMethod.name;
delegateMethodType = localMethod.methodType;
delegateMethodReturnType = localMethod.returnType;
delegateMethodParameters = localMethod.typeParameters;
} else if ("new".equals(methodName)) {
if (numberOfCaptures != 0) {
throw new IllegalStateException("internal error");
}
PainlessConstructor painlessConstructor;
try {
painlessConstructor = painlessLookup.lookupPainlessConstructor(typeName, interfaceTypeParametersSize);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException("function reference [" + typeName + "::new/" + interfaceTypeParametersSize + "] " +
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
"not found", iae);
}
delegateClassName = painlessConstructor.javaConstructor.getDeclaringClass().getName();
isDelegateInterface = false;
delegateInvokeType = H_NEWINVOKESPECIAL;
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
delegateMethodType = painlessConstructor.methodType;
delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass();
delegateMethodParameters = painlessConstructor.typeParameters;
} else {
if (numberOfCaptures != 0 && numberOfCaptures != 1) {
throw new IllegalStateException("internal error");
}
boolean captured = numberOfCaptures == 1;
PainlessMethod painlessMethod;
try {
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, true, methodName, interfaceTypeParametersSize);
if (captured) {
throw new IllegalStateException("internal error");
}
} catch (IllegalArgumentException staticIAE) {
try {
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, false, methodName,
captured ? interfaceTypeParametersSize : interfaceTypeParametersSize - 1);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException(
"function reference " + "[" + typeName + "::" + methodName + "/" + interfaceTypeParametersSize + "] " +
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
"not found", iae);
}
}
delegateClassName = painlessMethod.javaMethod.getDeclaringClass().getName();
isDelegateInterface = painlessMethod.javaMethod.getDeclaringClass().isInterface();
if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) {
delegateInvokeType = H_INVOKESTATIC;
} else if (isDelegateInterface) {
delegateInvokeType = H_INVOKEINTERFACE;
} else {
delegateInvokeType = H_INVOKEVIRTUAL;
}
delegateMethodName = painlessMethod.javaMethod.getName();
delegateMethodType = painlessMethod.methodType;
delegateMethodReturnType = painlessMethod.returnType;
if (delegateMethodType.parameterList().size() > painlessMethod.typeParameters.size()) {
delegateMethodParameters = new ArrayList<>(painlessMethod.typeParameters);
delegateMethodParameters.add(0, delegateMethodType.parameterType(0));
} else {
delegateMethodParameters = painlessMethod.typeParameters;
}
}
if (location != null) {
for (int typeParameter = 0; typeParameter < interfaceTypeParametersSize; ++typeParameter) {
Class<?> from = interfaceMethod.typeParameters.get(typeParameter);
Class<?> to = delegateMethodParameters.get(numberOfCaptures + typeParameter);
AnalyzerCaster.getLegalCast(location, from, to, false, true);
}
if (interfaceMethod.returnType != void.class) {
AnalyzerCaster.getLegalCast(location, delegateMethodReturnType, interfaceMethod.returnType, false, true);
}
}
MethodType factoryMethodType = MethodType.methodType(targetClass,
delegateMethodType.dropParameterTypes(numberOfCaptures, delegateMethodType.parameterCount()));
delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures);
return new FunctionRef(interfaceMethodName, interfaceMethodType,
delegateClassName, isDelegateInterface, delegateInvokeType, delegateMethodName, delegateMethodType,
factoryMethodType
);
} catch (IllegalArgumentException iae) {
if (location != null) {
throw location.createError(iae);
}
throw iae;
}
}
/** functional interface method name */
public final String interfaceMethodName;
/** factory (CallSite) method signature */
public final MethodType factoryMethodType;
/** functional interface method signature */
public final MethodType interfaceMethodType;
/** class of the delegate method to be called */
public final String delegateClassName;
/** whether a call is made on a delegate interface */
public final boolean isDelegateInterface;
/** the invocation type of the delegate method */
public final int delegateInvokeType;
/** the name of the delegate method */
public final String delegateMethodName;
/** delegate method signature */
public final MethodType delegateMethodType;
/** factory (CallSite) method signature */
public final MethodType factoryMethodType;
/** interface method */
public final PainlessMethod interfaceMethod;
/** delegate method type parameters */
public final List<Class<?>> delegateTypeParameters;
/** delegate method return type */
public final Class<?> delegateReturnType;
private FunctionRef(
String interfaceMethodName, MethodType interfaceMethodType,
String delegateClassName, boolean isDelegateInterface,
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
MethodType factoryMethodType) {
/** factory method type descriptor */
public final String factoryDescriptor;
/** functional interface method as type */
public final Type interfaceType;
/** delegate method type method as type */
public final Type delegateType;
/** whether a call is made on a delegate interface */
public final boolean isDelegateInterface;
/**
* Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist.
* @param painlessLookup the whitelist against which this script is being compiled
* @param expected functional interface type to implement.
* @param type the left hand side of a method reference expression
* @param call the right hand side of a method reference expression
* @param numCaptures number of captured arguments
*/
public static FunctionRef resolveFromLookup(
PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) {
if ("new".equals(call)) {
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(expected).functionalMethod,
lookup(painlessLookup, expected, type), numCaptures);
} else {
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(expected).functionalMethod,
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures);
}
}
/**
* Creates a new FunctionRef (already resolved)
* @param expected functional interface type to implement
* @param interfaceMethod functional interface method
* @param delegateConstructor implementation constructor
* @param numCaptures number of captured arguments
*/
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessConstructor delegateConstructor, int numCaptures) {
Constructor<?> javaConstructor = delegateConstructor.javaConstructor;
MethodType delegateMethodType = delegateConstructor.methodType;
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
this.factoryMethodType = MethodType.methodType(expected,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
this.delegateClassName = javaConstructor.getDeclaringClass().getName();
this.isDelegateInterface = false;
this.delegateInvokeType = H_NEWINVOKESPECIAL;
this.delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.interfaceMethod = interfaceMethod;
this.delegateTypeParameters = delegateConstructor.typeParameters;
this.delegateReturnType = void.class;
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
}
/**
* Creates a new FunctionRef (already resolved)
* @param expected functional interface type to implement
* @param interfaceMethod functional interface method
* @param delegateMethod implementation method
* @param numCaptures number of captured arguments
*/
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMethod delegateMethod, int numCaptures) {
MethodType delegateMethodType = delegateMethod.methodType;
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
this.factoryMethodType = MethodType.methodType(expected,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
this.delegateClassName = delegateMethod.javaMethod.getDeclaringClass().getName();
this.isDelegateInterface = delegateMethod.javaMethod.getDeclaringClass().isInterface();
if (Modifier.isStatic(delegateMethod.javaMethod.getModifiers())) {
this.delegateInvokeType = H_INVOKESTATIC;
} else if (delegateMethod.javaMethod.getDeclaringClass().isInterface()) {
this.delegateInvokeType = H_INVOKEINTERFACE;
} else {
this.delegateInvokeType = H_INVOKEVIRTUAL;
}
this.delegateMethodName = delegateMethod.javaMethod.getName();
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.interfaceMethod = interfaceMethod;
this.delegateTypeParameters = delegateMethod.typeParameters;
this.delegateReturnType = delegateMethod.returnType;
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
}
/**
* Creates a new FunctionRef (already resolved)
* @param expected functional interface type to implement
* @param interfaceMethod functional interface method
* @param delegateMethod implementation method
* @param numCaptures number of captured arguments
*/
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, LocalMethod delegateMethod, int numCaptures) {
MethodType delegateMethodType = delegateMethod.methodType;
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
this.factoryMethodType = MethodType.methodType(expected,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
this.delegateClassName = CLASS_NAME;
this.isDelegateInterface = false;
this.delegateInvokeType = H_INVOKESTATIC;
this.delegateMethodName = delegateMethod.name;
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.interfaceMethod = interfaceMethod;
this.delegateTypeParameters = delegateMethod.typeParameters;
this.delegateReturnType = delegateMethod.returnType;
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
}
/**
* Creates a new FunctionRef (low level).
* It is for runtime use only.
*/
public FunctionRef(Class<?> expected,
PainlessMethod interfaceMethod, String delegateMethodName, MethodType delegateMethodType, int numCaptures) {
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
this.factoryMethodType = MethodType.methodType(expected,
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
this.delegateClassName = CLASS_NAME;
this.delegateInvokeType = H_INVOKESTATIC;
this.interfaceMethodName = interfaceMethodName;
this.interfaceMethodType = interfaceMethodType;
this.delegateClassName = delegateClassName;
this.isDelegateInterface = isDelegateInterface;
this.delegateInvokeType = delegateInvokeType;
this.delegateMethodName = delegateMethodName;
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
this.isDelegateInterface = false;
this.interfaceMethod = null;
this.delegateTypeParameters = null;
this.delegateReturnType = null;
this.factoryDescriptor = null;
this.interfaceType = null;
this.delegateType = null;
}
/**
* Looks up {@code type} from the whitelist, and returns a matching constructor.
*/
private static PainlessConstructor lookup(PainlessLookup painlessLookup, Class<?> expected, String type) {
// check its really a functional interface
// for e.g. Comparable
PainlessMethod method = painlessLookup.lookupPainlessClass(expected).functionalMethod;
if (method == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::new] " +
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
}
// lookup requested constructor
PainlessClass struct = painlessLookup.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
PainlessConstructor impl = struct.constructors.get(PainlessLookupUtility.buildPainlessConstructorKey(method.typeParameters.size()));
if (impl == null) {
throw new IllegalArgumentException("Unknown reference [" + type + "::new] matching [" + expected + "]");
}
return impl;
}
/**
* Looks up {@code type::call} from the whitelist, and returns a matching method.
*/
private static PainlessMethod lookup(PainlessLookup painlessLookup, Class<?> expected,
String type, String call, boolean receiverCaptured) {
// check its really a functional interface
// for e.g. Comparable
PainlessMethod method = painlessLookup.lookupPainlessClass(expected).functionalMethod;
if (method == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
}
// lookup requested method
PainlessClass struct = painlessLookup.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
final PainlessMethod impl;
// look for a static impl first
PainlessMethod staticImpl =
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.typeParameters.size()));
if (staticImpl == null) {
// otherwise a virtual impl
final int arity;
if (receiverCaptured) {
// receiver captured
arity = method.typeParameters.size();
} else {
// receiver passed
arity = method.typeParameters.size() - 1;
}
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity));
} else {
impl = staticImpl;
}
if (impl == null) {
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " +
"[" + expected + "]");
}
return impl;
this.delegateMethodType = delegateMethodType;
this.factoryMethodType = factoryMethodType;
}
}

View File

@ -56,6 +56,7 @@ import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
@ -439,4 +440,18 @@ public final class MethodWriter extends GeneratorAdapter {
invokeVirtual(type, method);
}
}
public void invokeLambdaCall(FunctionRef functionRef) {
invokeDynamic(
functionRef.interfaceMethodName,
functionRef.factoryMethodType.toMethodDescriptorString(),
LAMBDA_BOOTSTRAP_HANDLE,
Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()),
functionRef.delegateClassName,
functionRef.delegateInvokeType,
functionRef.delegateMethodName,
Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()),
functionRef.isDelegateInterface ? 1 : 0
);
}
}

View File

@ -35,13 +35,13 @@ public final class PainlessClass {
public final Map<String, MethodHandle> getterMethodHandles;
public final Map<String, MethodHandle> setterMethodHandles;
public final PainlessMethod functionalMethod;
public final PainlessMethod functionalInterfaceMethod;
PainlessClass(Map<String, PainlessConstructor> constructors,
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
PainlessMethod functionalMethod) {
PainlessMethod functionalInterfaceMethod) {
this.constructors = Collections.unmodifiableMap(constructors);
@ -54,6 +54,6 @@ public final class PainlessClass {
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
this.functionalMethod = functionalMethod;
this.functionalInterfaceMethod = functionalInterfaceMethod;
}
}

View File

@ -35,7 +35,7 @@ final class PainlessClassBuilder {
final Map<String, MethodHandle> getterMethodHandles;
final Map<String, MethodHandle> setterMethodHandles;
PainlessMethod functionalMethod;
PainlessMethod functionalInterfaceMethod;
PainlessClassBuilder() {
constructors = new HashMap<>();
@ -49,11 +49,11 @@ final class PainlessClassBuilder {
getterMethodHandles = new HashMap<>();
setterMethodHandles = new HashMap<>();
functionalMethod = null;
functionalInterfaceMethod = null;
}
PainlessClass build() {
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
getterMethodHandles, setterMethodHandles, functionalMethod);
getterMethodHandles, setterMethodHandles, functionalInterfaceMethod);
}
}

View File

@ -62,6 +62,14 @@ public final class PainlessLookup {
return classesToPainlessClasses.get(targetClass);
}
public PainlessConstructor lookupPainlessConstructor(String targetClassName, int constructorArity) {
Objects.requireNonNull(targetClassName);
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
return lookupPainlessConstructor(targetClass, constructorArity);
}
public PainlessConstructor lookupPainlessConstructor(Class<?> targetClass, int constructorArity) {
Objects.requireNonNull(targetClass);
@ -83,6 +91,14 @@ public final class PainlessLookup {
return painlessConstructor;
}
public PainlessMethod lookupPainlessMethod(String targetClassName, boolean isStatic, String methodName, int methodArity) {
Objects.requireNonNull(targetClassName);
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
return lookupPainlessMethod(targetClass, isStatic, methodName, methodArity);
}
public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStatic, String methodName, int methodArity) {
Objects.requireNonNull(targetClass);
Objects.requireNonNull(methodName);
@ -111,6 +127,14 @@ public final class PainlessLookup {
return painlessMethod;
}
public PainlessField lookupPainlessField(String targetClassName, boolean isStatic, String fieldName) {
Objects.requireNonNull(targetClassName);
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
return lookupPainlessField(targetClass, isStatic, fieldName);
}
public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
Objects.requireNonNull(targetClass);
Objects.requireNonNull(fieldName);
@ -134,4 +158,20 @@ public final class PainlessLookup {
return painlessField;
}
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
if (targetPainlessClass == null) {
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] not found");
}
PainlessMethod functionalInterfacePainlessMethod = targetPainlessClass.functionalInterfaceMethod;
if (functionalInterfacePainlessMethod == null) {
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] is not a functional interface");
}
return functionalInterfacePainlessMethod;
}
}

View File

@ -875,7 +875,7 @@ public final class PainlessLookupBuilder {
} else if (javaMethods.size() == 1) {
java.lang.reflect.Method javaMethod = javaMethods.get(0);
String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
painlessClassBuilder.functionalMethod = painlessClassBuilder.methods.get(painlessMethodKey);
painlessClassBuilder.functionalInterfaceMethod = painlessClassBuilder.methods.get(painlessMethodKey);
}
}
}

View File

@ -19,7 +19,6 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
@ -35,8 +34,6 @@ import org.objectweb.asm.Type;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
/**
* Represents a capturing function reference.
*/
@ -76,23 +73,8 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
defPointer = null;
// static case
if (captured.clazz != def.class) {
try {
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected,
PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
// check casts between the interface method and the delegate method are legal
for (int i = 0; i < ref.interfaceMethod.typeParameters.size(); ++i) {
Class<?> from = ref.interfaceMethod.typeParameters.get(i);
Class<?> to = ref.delegateTypeParameters.get(i);
AnalyzerCaster.getLegalCast(location, from, to, false, true);
}
if (ref.interfaceMethod.returnType != void.class) {
AnalyzerCaster.getLegalCast(location, ref.delegateReturnType, ref.interfaceMethod.returnType, false, true);
}
} catch (IllegalArgumentException e) {
throw createError(e);
}
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location,
expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
}
actual = expected;
}
@ -114,17 +96,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
} else {
// typed interface, typed implementation
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
writer.invokeDynamic(
ref.interfaceMethodName,
ref.factoryDescriptor,
LAMBDA_BOOTSTRAP_HANDLE,
ref.interfaceType,
ref.delegateClassName,
ref.delegateInvokeType,
ref.delegateMethodName,
ref.delegateType,
ref.isDelegateInterface ? 1 : 0
);
writer.invokeLambdaCall(ref);
}
}

View File

@ -19,22 +19,16 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.LocalMethod;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.PainlessMethod;
import org.objectweb.asm.Type;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
/**
* Represents a function reference.
*/
@ -63,39 +57,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
defPointer = "S" + type + "." + call + ",0";
} else {
defPointer = null;
try {
if ("this".equals(type)) {
// user's own function
PainlessMethod interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
if (interfaceMethod == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
}
LocalMethod delegateMethod = locals.getMethod(call, interfaceMethod.typeParameters.size());
if (delegateMethod == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], function not found");
}
ref = new FunctionRef(expected, interfaceMethod, delegateMethod, 0);
// check casts between the interface method and the delegate method are legal
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
Class<?> from = interfaceMethod.typeParameters.get(i);
Class<?> to = delegateMethod.typeParameters.get(i);
AnalyzerCaster.getLegalCast(location, from, to, false, true);
}
if (interfaceMethod.returnType != void.class) {
AnalyzerCaster.getLegalCast(location, delegateMethod.returnType, interfaceMethod.returnType, false, true);
}
} else {
// whitelist lookup
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected, type, call, 0);
}
} catch (IllegalArgumentException e) {
throw createError(e);
}
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, type, call, 0);
actual = expected;
}
}
@ -104,17 +66,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
void write(MethodWriter writer, Globals globals) {
if (ref != null) {
writer.writeDebugInfo(location);
writer.invokeDynamic(
ref.interfaceMethodName,
ref.factoryDescriptor,
LAMBDA_BOOTSTRAP_HANDLE,
ref.interfaceType,
ref.delegateClassName,
ref.delegateInvokeType,
ref.delegateMethodName,
ref.delegateType,
ref.isDelegateInterface ? 1 : 0
);
writer.invokeLambdaCall(ref);
} else {
// TODO: don't do this: its just to cutover :)
writer.push((String)null);

View File

@ -19,11 +19,9 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.LocalMethod;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
@ -40,8 +38,6 @@ 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>
@ -122,7 +118,7 @@ public final class ELambda extends AExpression implements ILambda {
} else {
// we know the method statically, infer return type and any unknown/def types
interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
interfaceMethod = locals.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected);
if (interfaceMethod == null) {
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
@ -184,25 +180,8 @@ public final class ELambda extends AExpression implements ILambda {
defPointer = "Sthis." + name + "," + captures.size();
} else {
defPointer = null;
try {
LocalMethod localMethod =
new LocalMethod(desugared.name, desugared.returnType, desugared.typeParameters, desugared.methodType);
ref = new FunctionRef(expected, interfaceMethod, localMethod, captures.size());
} catch (IllegalArgumentException e) {
throw createError(e);
}
// check casts between the interface method and the delegate method are legal
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
Class<?> from = interfaceMethod.typeParameters.get(i);
Class<?> to = desugared.parameters.get(i + captures.size()).clazz;
AnalyzerCaster.getLegalCast(location, from, to, false, true);
}
if (interfaceMethod.returnType != void.class) {
AnalyzerCaster.getLegalCast(location, desugared.returnType, interfaceMethod.returnType, false, true);
}
ref = FunctionRef.create(
locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", desugared.name, captures.size());
actual = expected;
}
}
@ -218,17 +197,7 @@ public final class ELambda extends AExpression implements ILambda {
writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
}
writer.invokeDynamic(
ref.interfaceMethodName,
ref.factoryDescriptor,
LAMBDA_BOOTSTRAP_HANDLE,
ref.interfaceType,
ref.delegateClassName,
ref.delegateInvokeType,
ref.delegateMethodName,
ref.delegateType,
ref.isDelegateInterface ? 1 : 0
);
writer.invokeLambdaCall(ref);
} else {
// placeholder
writer.push((String)null);

View File

@ -27,7 +27,6 @@ import java.lang.invoke.LambdaConversionException;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
public class FunctionRefTests extends ScriptTestCase {
@ -193,14 +192,15 @@ public class FunctionRefTests extends ScriptTestCase {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
});
assertThat(e.getMessage(), startsWith("Unknown reference"));
assertThat(e.getMessage(), containsString("function reference [Integer::bogus/2] matching [java.util.Comparator"));
}
public void testQualifiedMethodMissing() {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
});
assertThat(e.getMessage(), startsWith("Unknown reference"));
assertThat(e.getMessage(),
containsString("function reference [org.joda.time.ReadableDateTime::bogus/2] matching [java.util.Comparator"));
}
public void testClassMissing() {
@ -223,11 +223,12 @@ public class FunctionRefTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
});
assertThat(expected.getMessage(), containsString("Cannot convert function reference"));
assertThat(expected.getMessage(),
containsString("cannot convert function reference [Integer::bogus] to a non-functional interface [def]"));
}
public void testIncompatible() {
expectScriptThrows(BootstrapMethodError.class, () -> {
expectScriptThrows(ClassCastException.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
});
}
@ -236,28 +237,32 @@ public class FunctionRefTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("Optional.empty().orElseGet(String::startsWith);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
assertThat(expected.getMessage(),
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
}
public void testWrongArityNotEnough() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
});
assertTrue(expected.getMessage().contains("Unknown reference"));
assertThat(expected.getMessage(), containsString(
"function reference [String::isEmpty/2] matching [java.util.Comparator"));
}
public void testWrongArityDef() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
assertThat(expected.getMessage(),
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
}
public void testWrongArityNotEnoughDef() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
assertThat(expected.getMessage(),
containsString("function reference [String::isEmpty/2] matching [java.util.Comparator"));
}
public void testReturnVoid() {

View File

@ -184,7 +184,7 @@ public class LambdaTests extends ScriptTestCase {
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"));
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
}
public void testWrongArityNotEnough() {
@ -200,7 +200,7 @@ public class LambdaTests extends ScriptTestCase {
exec("def l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(() -> 5).sum();");
});
assertTrue(expected.getMessage().contains("Incorrect number of parameters"));
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
}
public void testLambdaInFunction() {