[Painless] Generate Bridge Methods (#36097)

We use MethodHandles.asType to cast argument types into the appropriate parameter types for 
method calls when the target of the call is a def type at runtime. Currently, certain implicit casts 
using the def type are asymmetric. It is possible to cast Integer -> float as an argument to parameter, but not from int -> Float (boxed to primitive with upcasting is okay, but primitive to 
boxed with upcasting is not).

This PR introduces a solution to the issue by generating bridge methods for all whitelisted methods 
that have at least a single boxed type as an argument. The bridge method will conduct appropriate 
casts and then call the original method. This adds a bit of overhead for correctness. It should not be
used often as Painless avoids boxed types as much as possible.

Note that a large portion of this change is adding methods to do the appropriate def to boxed type 
casts and a few mechanical changes as well. The most important method for review is 
generateBridgeMethod in PainlessLookupBuilder.
This commit is contained in:
Jack Conradson 2018-12-07 09:32:27 -08:00 committed by GitHub
parent 9d417984bd
commit 2df4bd1f81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 531 additions and 123 deletions

View File

@ -445,7 +445,7 @@ public final class Def {
}
throw new IllegalArgumentException(
"dynamic getter [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "] not found");
"dynamic setter [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "] not found");
}
/**
@ -619,18 +619,17 @@ public final class Def {
}
}
// Conversion methods for def to primitive types.
// Conversion methods for Def to primitive types.
public static boolean DefToboolean(final Object value) {
public static boolean defToboolean(final Object value) {
return (boolean)value;
}
public static byte DefTobyteImplicit(final Object value) {
public static byte defTobyteImplicit(final Object value) {
return (byte)value;
}
public static short DefToshortImplicit(final Object value) {
public static short defToshortImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else {
@ -638,15 +637,11 @@ public final class Def {
}
}
public static char DefTocharImplicit(final Object value) {
if (value instanceof Byte) {
return (char)(byte)value;
} else {
public static char defTocharImplicit(final Object value) {
return (char)value;
}
}
public static int DefTointImplicit(final Object value) {
public static int defTointImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
@ -658,7 +653,7 @@ public final class Def {
}
}
public static long DefTolongImplicit(final Object value) {
public static long defTolongImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
@ -672,7 +667,7 @@ public final class Def {
}
}
public static float DefTofloatImplicit(final Object value) {
public static float defTofloatImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
@ -688,7 +683,7 @@ public final class Def {
}
}
public static double DefTodoubleImplicit(final Object value) {
public static double defTodoubleImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
@ -706,7 +701,7 @@ public final class Def {
}
}
public static byte DefTobyteExplicit(final Object value) {
public static byte defTobyteExplicit(final Object value) {
if (value instanceof Character) {
return (byte)(char)value;
} else {
@ -714,7 +709,7 @@ public final class Def {
}
}
public static short DefToshortExplicit(final Object value) {
public static short defToshortExplicit(final Object value) {
if (value instanceof Character) {
return (short)(char)value;
} else {
@ -722,15 +717,15 @@ public final class Def {
}
}
public static char DefTocharExplicit(final Object value) {
public static char defTocharExplicit(final Object value) {
if (value instanceof Character) {
return ((Character)value);
return (char)value;
} else {
return (char)((Number)value).intValue();
}
}
public static int DefTointExplicit(final Object value) {
public static int defTointExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
@ -738,7 +733,7 @@ public final class Def {
}
}
public static long DefTolongExplicit(final Object value) {
public static long defTolongExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
@ -746,7 +741,7 @@ public final class Def {
}
}
public static float DefTofloatExplicit(final Object value) {
public static float defTofloatExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
@ -754,7 +749,7 @@ public final class Def {
}
}
public static double DefTodoubleExplicit(final Object value) {
public static double defTodoubleExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
@ -762,6 +757,172 @@ public final class Def {
}
}
// Conversion methods for def to boxed types.
public static Byte defToByteImplicit(final Object value) {
if (value == null) {
return null;
} else {
return (Byte)value;
}
}
public static Short defToShortImplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Byte) {
return (short)(byte)value;
} else {
return (Short)value;
}
}
public static Character defToCharacterImplicit(final Object value) {
if (value == null) {
return null;
} else {
return (Character)value;
}
}
public static Integer defToIntegerImplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Byte) {
return (int)(byte)value;
} else if (value instanceof Short) {
return (int)(short)value;
} else if (value instanceof Character) {
return (int)(char)value;
} else {
return (Integer)value;
}
}
public static Long defToLongImplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Byte) {
return (long)(byte)value;
} else if (value instanceof Short) {
return (long)(short)value;
} else if (value instanceof Character) {
return (long)(char)value;
} else if (value instanceof Integer) {
return (long)(int)value;
} else {
return (Long)value;
}
}
public static Float defToFloatImplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Byte) {
return (float)(byte)value;
} else if (value instanceof Short) {
return (float)(short)value;
} else if (value instanceof Character) {
return (float)(char)value;
} else if (value instanceof Integer) {
return (float)(int)value;
} else if (value instanceof Long) {
return (float)(long)value;
} else {
return (Float)value;
}
}
public static Double defToDoubleImplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Byte) {
return (double)(byte)value;
} else if (value instanceof Short) {
return (double)(short)value;
} else if (value instanceof Character) {
return (double)(char)value;
} else if (value instanceof Integer) {
return (double)(int)value;
} else if (value instanceof Long) {
return (double)(long)value;
} else if (value instanceof Float) {
return (double)(float)value;
} else {
return (Double)value;
}
}
public static Byte defToByteExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (byte)(char)value;
} else {
return ((Number)value).byteValue();
}
}
public static Short defToShortExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (short)(char)value;
} else {
return ((Number)value).shortValue();
}
}
public static Character defToCharacterExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (Character)value;
} else {
return (char)((Number)value).intValue();
}
}
public static Integer defToIntegerExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (int)(char)value;
} else {
return ((Number)value).intValue();
}
}
public static Long defToLongExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (long)(char)value;
} else {
return ((Number)value).longValue();
}
}
public static Float defToFloatExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (float)(char)value;
} else {
return ((Number)value).floatValue();
}
}
public static Double defToDoubleExplicit(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Character) {
return (double)(char)value;
} else {
return ((Number)value).doubleValue();
}
}
/**
* "Normalizes" the index into a {@code Map} by making no change to the index.
*/

View File

@ -39,10 +39,17 @@ public class FeatureTest {
return x + y;
}
/** static method with a type parameter Number */
public static int staticNumberTest(Number number) {
return number.intValue();
}
private int x;
private int y;
public int z;
private Integer i;
/** empty ctor */
public FeatureTest() {
}
@ -73,6 +80,20 @@ public class FeatureTest {
this.y = y;
}
/** getter for i */
public Integer getI() {
return i;
}
/** setter for y */
public void setI(Integer i) {
this.i = i;
}
public Double mixedAdd(int i, Byte b, char c, Float f) {
return (double)(i + b + c + f);
}
/** method taking two functions! */
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
return f.apply(g.apply(x));

View File

@ -40,20 +40,20 @@ import java.util.List;
import static org.elasticsearch.painless.WriterConstants.CHAR_TO_STRING;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BOOLEAN;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BYTE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BYTE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_CHAR_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_CHAR_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_DOUBLE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_DOUBLE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_FLOAT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_FLOAT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_INT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_INT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_LONG_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_LONG_IMPLICIT;
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_TO_P_BYTE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_BYTE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_CHAR_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_CHAR_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_DOUBLE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_DOUBLE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_FLOAT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_FLOAT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_INT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_INT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_LONG_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_LONG_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_SHORT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_P_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;
@ -146,25 +146,25 @@ public final class MethodWriter extends GeneratorAdapter {
if (cast.originalType == def.class) {
if (cast.explicitCast) {
if (cast.targetType == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_BYTE_EXPLICIT);
else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_SHORT_EXPLICIT);
else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_CHAR_EXPLICIT);
else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_INT_EXPLICIT);
else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_LONG_EXPLICIT);
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_FLOAT_EXPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_DOUBLE_EXPLICIT);
else {
throw new IllegalStateException("Illegal tree structure.");
}
} else {
if (cast.targetType == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_BYTE_IMPLICIT);
else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_SHORT_IMPLICIT);
else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_CHAR_IMPLICIT);
else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_INT_IMPLICIT);
else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_LONG_IMPLICIT);
else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_FLOAT_IMPLICIT);
else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_P_DOUBLE_IMPLICIT);
else {
throw new IllegalStateException("Illegal tree structure.");
}

View File

@ -123,21 +123,39 @@ public final class WriterConstants {
Map.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class);
public static final Type DEF_UTIL_TYPE = Type.getType(Def.class);
public static final Method DEF_TO_BOOLEAN = getAsmMethod(boolean.class, "DefToboolean" , Object.class);
public static final Method DEF_TO_BYTE_IMPLICIT = getAsmMethod(byte.class , "DefTobyteImplicit" , Object.class);
public static final Method DEF_TO_SHORT_IMPLICIT = getAsmMethod(short.class , "DefToshortImplicit" , Object.class);
public static final Method DEF_TO_CHAR_IMPLICIT = getAsmMethod(char.class , "DefTocharImplicit" , Object.class);
public static final Method DEF_TO_INT_IMPLICIT = getAsmMethod(int.class , "DefTointImplicit" , Object.class);
public static final Method DEF_TO_LONG_IMPLICIT = getAsmMethod(long.class , "DefTolongImplicit" , Object.class);
public static final Method DEF_TO_FLOAT_IMPLICIT = getAsmMethod(float.class , "DefTofloatImplicit" , Object.class);
public static final Method DEF_TO_DOUBLE_IMPLICIT = getAsmMethod(double.class , "DefTodoubleImplicit", Object.class);
public static final Method DEF_TO_BYTE_EXPLICIT = getAsmMethod(byte.class , "DefTobyteExplicit" , Object.class);
public static final Method DEF_TO_SHORT_EXPLICIT = getAsmMethod(short.class , "DefToshortExplicit" , Object.class);
public static final Method DEF_TO_CHAR_EXPLICIT = getAsmMethod(char.class , "DefTocharExplicit" , Object.class);
public static final Method DEF_TO_INT_EXPLICIT = getAsmMethod(int.class , "DefTointExplicit" , Object.class);
public static final Method DEF_TO_LONG_EXPLICIT = getAsmMethod(long.class , "DefTolongExplicit" , Object.class);
public static final Method DEF_TO_FLOAT_EXPLICIT = getAsmMethod(float.class , "DefTofloatExplicit" , Object.class);
public static final Method DEF_TO_DOUBLE_EXPLICIT = getAsmMethod(double.class , "DefTodoubleExplicit", Object.class);
public static final Method DEF_TO_BOOLEAN = getAsmMethod(boolean.class, "defToboolean", Object.class);
public static final Method DEF_TO_P_BYTE_IMPLICIT = getAsmMethod(byte.class , "defTobyteImplicit" , Object.class);
public static final Method DEF_TO_P_SHORT_IMPLICIT = getAsmMethod(short.class , "defToshortImplicit" , Object.class);
public static final Method DEF_TO_P_CHAR_IMPLICIT = getAsmMethod(char.class , "defTocharImplicit" , Object.class);
public static final Method DEF_TO_P_INT_IMPLICIT = getAsmMethod(int.class , "defTointImplicit" , Object.class);
public static final Method DEF_TO_P_LONG_IMPLICIT = getAsmMethod(long.class , "defTolongImplicit" , Object.class);
public static final Method DEF_TO_P_FLOAT_IMPLICIT = getAsmMethod(float.class , "defTofloatImplicit" , Object.class);
public static final Method DEF_TO_P_DOUBLE_IMPLICIT = getAsmMethod(double.class , "defTodoubleImplicit" , Object.class);
public static final Method DEF_TO_P_BYTE_EXPLICIT = getAsmMethod(byte.class , "defTobyteExplicit" , Object.class);
public static final Method DEF_TO_P_SHORT_EXPLICIT = getAsmMethod(short.class , "defToshortExplicit" , Object.class);
public static final Method DEF_TO_P_CHAR_EXPLICIT = getAsmMethod(char.class , "defTocharExplicit" , Object.class);
public static final Method DEF_TO_P_INT_EXPLICIT = getAsmMethod(int.class , "defTointExplicit" , Object.class);
public static final Method DEF_TO_P_LONG_EXPLICIT = getAsmMethod(long.class , "defTolongExplicit" , Object.class);
public static final Method DEF_TO_P_FLOAT_EXPLICIT = getAsmMethod(float.class , "defTofloatExplicit" , Object.class);
public static final Method DEF_TO_P_DOUBLE_EXPLICIT = getAsmMethod(double.class , "defTodoubleExplicit" , Object.class);
public static final Method DEF_TO_B_BYTE_IMPLICIT = getAsmMethod(Byte.class , "defToByteImplicit" , Object.class);
public static final Method DEF_TO_B_SHORT_IMPLICIT = getAsmMethod(Short.class , "defToShortImplicit" , Object.class);
public static final Method DEF_TO_B_CHARACTER_IMPLICIT = getAsmMethod(Character.class , "defToCharacterImplicit" , Object.class);
public static final Method DEF_TO_B_INTEGER_IMPLICIT = getAsmMethod(Integer.class , "defToIntegerImplicit" , Object.class);
public static final Method DEF_TO_B_LONG_IMPLICIT = getAsmMethod(Long.class , "defToLongImplicit" , Object.class);
public static final Method DEF_TO_B_FLOAT_IMPLICIT = getAsmMethod(Float.class , "defToFloatImplicit" , Object.class);
public static final Method DEF_TO_B_DOUBLE_IMPLICIT = getAsmMethod(Double.class , "defToDoubleImplicit" , Object.class);
public static final Method DEF_TO_B_BYTE_EXPLICIT = getAsmMethod(Byte.class , "defToByteExplicit" , Object.class);
public static final Method DEF_TO_B_SHORT_EXPLICIT = getAsmMethod(Short.class , "defToShortExplicit" , Object.class);
public static final Method DEF_TO_B_CHARACTER_EXPLICIT = getAsmMethod(Character.class , "defToCharacterExplicit" , Object.class);
public static final Method DEF_TO_B_INTEGER_EXPLICIT = getAsmMethod(Integer.class , "defToIntegerExplicit" , Object.class);
public static final Method DEF_TO_B_LONG_EXPLICIT = getAsmMethod(Long.class , "defToLongExplicit" , Object.class);
public static final Method DEF_TO_B_FLOAT_EXPLICIT = getAsmMethod(Float.class , "defToFloatExplicit" , Object.class);
public static final Method DEF_TO_B_DOUBLE_EXPLICIT = getAsmMethod(Double.class , "defToDoubleExplicit" , Object.class);
public static final Type DEF_ARRAY_LENGTH_METHOD_TYPE = Type.getMethodType(Type.INT_TYPE, Type.getType(Object.class));
/** invokedynamic bootstrap for lambda expression/method references */

View File

@ -27,36 +27,33 @@ import java.util.Objects;
public final class PainlessClass {
public final Map<String, PainlessConstructor> constructors;
public final Map<String, PainlessMethod> staticMethods;
public final Map<String, PainlessMethod> methods;
public final Map<String, PainlessField> staticFields;
public final Map<String, PainlessField> fields;
public final PainlessMethod functionalInterfaceMethod;
public final Map<String, PainlessMethod> runtimeMethods;
public final Map<String, MethodHandle> getterMethodHandles;
public final Map<String, MethodHandle> setterMethodHandles;
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 functionalInterfaceMethod) {
PainlessMethod functionalInterfaceMethod,
Map<String, PainlessMethod> runtimeMethods,
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles) {
this.constructors = Collections.unmodifiableMap(constructors);
this.staticMethods = Collections.unmodifiableMap(staticMethods);
this.methods = Collections.unmodifiableMap(methods);
this.staticFields = Collections.unmodifiableMap(staticFields);
this.fields = Collections.unmodifiableMap(fields);
this.functionalInterfaceMethod = functionalInterfaceMethod;
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
this.functionalInterfaceMethod = functionalInterfaceMethod;
this.runtimeMethods = Collections.unmodifiableMap(runtimeMethods);
}
@Override

View File

@ -27,36 +27,32 @@ import java.util.Objects;
final class PainlessClassBuilder {
final Map<String, PainlessConstructor> constructors;
final Map<String, PainlessMethod> staticMethods;
final Map<String, PainlessMethod> methods;
final Map<String, PainlessField> staticFields;
final Map<String, PainlessField> fields;
PainlessMethod functionalInterfaceMethod;
final Map<String, PainlessMethod> runtimeMethods;
final Map<String, MethodHandle> getterMethodHandles;
final Map<String, MethodHandle> setterMethodHandles;
PainlessMethod functionalInterfaceMethod;
PainlessClassBuilder() {
constructors = new HashMap<>();
staticMethods = new HashMap<>();
methods = new HashMap<>();
staticFields = new HashMap<>();
fields = new HashMap<>();
functionalInterfaceMethod = null;
runtimeMethods = new HashMap<>();
getterMethodHandles = new HashMap<>();
setterMethodHandles = new HashMap<>();
functionalInterfaceMethod = null;
}
PainlessClass build() {
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
getterMethodHandles, setterMethodHandles, functionalInterfaceMethod);
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod,
runtimeMethods, getterMethodHandles, setterMethodHandles);
}
@Override

View File

@ -227,7 +227,8 @@ public final class PainlessLookup {
Objects.requireNonNull(methodName);
String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity);
Function<PainlessClass, PainlessMethod> objectLookup = targetPainlessClass -> targetPainlessClass.methods.get(painlessMethodKey);
Function<PainlessClass, PainlessMethod> objectLookup =
targetPainlessClass -> targetPainlessClass.runtimeMethods.get(painlessMethodKey);
return lookupRuntimePainlessObject(originalTargetClass, objectLookup);
}

View File

@ -19,6 +19,10 @@
package org.elasticsearch.painless.lookup;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistClass;
import org.elasticsearch.painless.spi.WhitelistClassBinding;
@ -26,6 +30,9 @@ import org.elasticsearch.painless.spi.WhitelistConstructor;
import org.elasticsearch.painless.spi.WhitelistField;
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
import org.elasticsearch.painless.spi.WhitelistMethod;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@ -34,6 +41,13 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -41,6 +55,15 @@ import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_BYTE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_CHARACTER_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_DOUBLE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_FLOAT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_INTEGER_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_LONG_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_SHORT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey;
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey;
@ -51,16 +74,42 @@ import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCan
public final class PainlessLookupBuilder {
private static final class BridgeLoader extends SecureClassLoader {
BridgeLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
return Def.class.getName().equals(name) ? Def.class : super.findClass(name);
}
Class<?> defineBridge(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length, CODESOURCE);
}
}
private static final CodeSource CODESOURCE;
private static final Map<PainlessConstructor , PainlessConstructor> painlessConstructorCache = new HashMap<>();
private static final Map<PainlessMethod , PainlessMethod> painlessMethodCache = new HashMap<>();
private static final Map<PainlessField , PainlessField> painlessFieldCache = new HashMap<>();
private static final Map<PainlessClassBinding , PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
private static final Map<PainlessInstanceBinding, PainlessInstanceBinding> painlessInstanceBindingCache = new HashMap<>();
private static final Map<PainlessMethod , PainlessMethod> painlessBridgeCache = new HashMap<>();
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
static {
try {
CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[])null);
} catch (MalformedURLException mue) {
throw new RuntimeException(mue);
}
}
public static PainlessLookup buildFromWhitelists(List<Whitelist> whitelists) {
PainlessLookupBuilder painlessLookupBuilder = new PainlessLookupBuilder();
String origin = "internal error";
@ -1136,8 +1185,9 @@ public final class PainlessLookupBuilder {
public PainlessLookup build() {
copyPainlessClassMembers();
cacheRuntimeHandles();
setFunctionalInterfaceMethods();
generateRuntimeMethods();
cacheRuntimeHandles();
Map<Class<?>, PainlessClass> classesToPainlessClasses = new HashMap<>(classesToPainlessClassBuilders.size());
@ -1234,38 +1284,6 @@ public final class PainlessLookupBuilder {
}
}
private void cacheRuntimeHandles() {
for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
cacheRuntimeHandles(painlessClassBuilder);
}
}
private void cacheRuntimeHandles(PainlessClassBuilder painlessClassBuilder) {
for (PainlessMethod painlessMethod : painlessClassBuilder.methods.values()) {
String methodName = painlessMethod.javaMethod.getName();
int typeParametersSize = painlessMethod.typeParameters.size();
if (typeParametersSize == 0 && methodName.startsWith("get") && methodName.length() > 3 &&
Character.isUpperCase(methodName.charAt(3))) {
painlessClassBuilder.getterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.methodHandle);
} else if (typeParametersSize == 0 && methodName.startsWith("is") && methodName.length() > 2 &&
Character.isUpperCase(methodName.charAt(2))) {
painlessClassBuilder.getterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3), painlessMethod.methodHandle);
} else if (typeParametersSize == 1 && methodName.startsWith("set") && methodName.length() > 3 &&
Character.isUpperCase(methodName.charAt(3))) {
painlessClassBuilder.setterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.methodHandle);
}
}
for (PainlessField painlessField : painlessClassBuilder.fields.values()) {
painlessClassBuilder.getterMethodHandles.put(painlessField.javaField.getName(), painlessField.getterMethodHandle);
painlessClassBuilder.setterMethodHandles.put(painlessField.javaField.getName(), painlessField.setterMethodHandle);
}
}
private void setFunctionalInterfaceMethods() {
for (Map.Entry<Class<?>, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
setFunctionalInterfaceMethod(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue());
@ -1296,4 +1314,178 @@ public final class PainlessLookupBuilder {
}
}
}
/**
* Creates a {@link Map} of PainlessMethodKeys to {@link PainlessMethod}s per {@link PainlessClass} stored as
* {@link PainlessClass#runtimeMethods} identical to {@link PainlessClass#methods} with the exception of generated
* bridge methods. A generated bridge method is created for each whitelisted method that has at least one parameter
* with a boxed type to cast from other numeric primitive/boxed types in a symmetric was not handled by
* {@link MethodHandle#asType(MethodType)}. As an example {@link MethodHandle#asType(MethodType)} legally casts
* from {@link Integer} to long but not from int to {@link Long}. Generated bridge methods cover the latter case.
* A generated bridge method replaces the method its a bridge to in the {@link PainlessClass#runtimeMethods}
* {@link Map}. The {@link PainlessClass#runtimeMethods} {@link Map} is used exclusively to look up methods at
* run-time resulting from calls with a def type value target.
*/
private void generateRuntimeMethods() {
for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
painlessClassBuilder.runtimeMethods.putAll(painlessClassBuilder.methods);
for (PainlessMethod painlessMethod : painlessClassBuilder.runtimeMethods.values()) {
for (Class<?> typeParameter : painlessMethod.typeParameters) {
if (
typeParameter == Byte.class ||
typeParameter == Short.class ||
typeParameter == Character.class ||
typeParameter == Integer.class ||
typeParameter == Long.class ||
typeParameter == Float.class ||
typeParameter == Double.class
) {
generateBridgeMethod(painlessClassBuilder, painlessMethod);
}
}
}
}
}
private void generateBridgeMethod(PainlessClassBuilder painlessClassBuilder, PainlessMethod painlessMethod) {
String painlessMethodKey = buildPainlessMethodKey(painlessMethod.javaMethod.getName(), painlessMethod.typeParameters.size());
PainlessMethod bridgePainlessMethod = painlessBridgeCache.get(painlessMethod);
if (bridgePainlessMethod == null) {
Method javaMethod = painlessMethod.javaMethod;
boolean isStatic = Modifier.isStatic(painlessMethod.javaMethod.getModifiers());
int bridgeClassFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
int bridgeClassAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
String bridgeClassName =
"org/elasticsearch/painless/Bridge$" + javaMethod.getDeclaringClass().getSimpleName() + "$" + javaMethod.getName();
ClassWriter bridgeClassWriter = new ClassWriter(bridgeClassFrames);
bridgeClassWriter.visit(
WriterConstants.CLASS_VERSION, bridgeClassAccess, bridgeClassName, null, OBJECT_TYPE.getInternalName(), null);
org.objectweb.asm.commons.Method bridgeConstructorType =
new org.objectweb.asm.commons.Method("<init>", MethodType.methodType(void.class).toMethodDescriptorString());
GeneratorAdapter bridgeConstructorWriter =
new GeneratorAdapter(Opcodes.ASM5, bridgeConstructorType, bridgeClassWriter.visitMethod(
Opcodes.ACC_PRIVATE, bridgeConstructorType.getName(), bridgeConstructorType.getDescriptor(), null, null));
bridgeConstructorWriter.visitCode();
bridgeConstructorWriter.loadThis();
bridgeConstructorWriter.invokeConstructor(OBJECT_TYPE, bridgeConstructorType);
bridgeConstructorWriter.returnValue();
bridgeConstructorWriter.endMethod();
int bridgeTypeParameterOffset = isStatic ? 0 : 1;
List<Class<?>> bridgeTypeParameters = new ArrayList<>(javaMethod.getParameterTypes().length + bridgeTypeParameterOffset);
if (isStatic == false) {
bridgeTypeParameters.add(javaMethod.getDeclaringClass());
}
for (Class<?> typeParameter : javaMethod.getParameterTypes()) {
if (
typeParameter == Byte.class ||
typeParameter == Short.class ||
typeParameter == Character.class ||
typeParameter == Integer.class ||
typeParameter == Long.class ||
typeParameter == Float.class ||
typeParameter == Double.class
) {
bridgeTypeParameters.add(Object.class);
} else {
bridgeTypeParameters.add(typeParameter);
}
}
MethodType bridgeMethodType = MethodType.methodType(painlessMethod.returnType, bridgeTypeParameters);
MethodWriter bridgeMethodWriter =
new MethodWriter(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
new org.objectweb.asm.commons.Method(
painlessMethod.javaMethod.getName(), bridgeMethodType.toMethodDescriptorString()),
bridgeClassWriter, null, null);
bridgeMethodWriter.visitCode();
if (isStatic == false) {
bridgeMethodWriter.loadArg(0);
}
for (int typeParameterCount = 0; typeParameterCount < javaMethod.getParameterTypes().length; ++typeParameterCount) {
bridgeMethodWriter.loadArg(typeParameterCount + bridgeTypeParameterOffset);
Class<?> typeParameter = javaMethod.getParameterTypes()[typeParameterCount];
if (typeParameter == Byte.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_BYTE_IMPLICIT);
else if (typeParameter == Short.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_SHORT_IMPLICIT);
else if (typeParameter == Character.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_CHARACTER_IMPLICIT);
else if (typeParameter == Integer.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_INTEGER_IMPLICIT);
else if (typeParameter == Long.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_LONG_IMPLICIT);
else if (typeParameter == Float.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_FLOAT_IMPLICIT);
else if (typeParameter == Double.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_DOUBLE_IMPLICIT);
}
bridgeMethodWriter.invokeMethodCall(painlessMethod);
bridgeMethodWriter.returnValue();
bridgeMethodWriter.endMethod();
bridgeClassWriter.visitEnd();
try {
BridgeLoader bridgeLoader = AccessController.doPrivileged(new PrivilegedAction<BridgeLoader>() {
@Override
public BridgeLoader run() {
return new BridgeLoader(javaMethod.getDeclaringClass().getClassLoader());
}
});
Class<?> bridgeClass = bridgeLoader.defineBridge(bridgeClassName.replace('/', '.'), bridgeClassWriter.toByteArray());
Method bridgeMethod = bridgeClass.getMethod(
painlessMethod.javaMethod.getName(), bridgeTypeParameters.toArray(new Class<?>[0]));
MethodHandle bridgeHandle = MethodHandles.publicLookup().in(bridgeClass).unreflect(bridgeClass.getMethods()[0]);
bridgePainlessMethod = new PainlessMethod(bridgeMethod, bridgeClass,
painlessMethod.returnType, bridgeTypeParameters, bridgeHandle, bridgeMethodType);
painlessClassBuilder.runtimeMethods.put(painlessMethodKey, bridgePainlessMethod);
painlessBridgeCache.put(painlessMethod, bridgePainlessMethod);
} catch (Exception exception) {
throw new IllegalStateException(
"internal error occurred attempting to generate a bridge method [" + bridgeClassName + "]", exception);
}
} else {
painlessClassBuilder.runtimeMethods.put(painlessMethodKey, bridgePainlessMethod);
}
}
private void cacheRuntimeHandles() {
for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
cacheRuntimeHandles(painlessClassBuilder);
}
}
private void cacheRuntimeHandles(PainlessClassBuilder painlessClassBuilder) {
for (Map.Entry<String, PainlessMethod> painlessMethodEntry : painlessClassBuilder.methods.entrySet()) {
String methodKey = painlessMethodEntry.getKey();
PainlessMethod painlessMethod = painlessMethodEntry.getValue();
PainlessMethod bridgePainlessMethod = painlessClassBuilder.runtimeMethods.get(methodKey);
String methodName = painlessMethod.javaMethod.getName();
int typeParametersSize = painlessMethod.typeParameters.size();
if (typeParametersSize == 0 && methodName.startsWith("get") && methodName.length() > 3 &&
Character.isUpperCase(methodName.charAt(3))) {
painlessClassBuilder.getterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), bridgePainlessMethod.methodHandle);
} else if (typeParametersSize == 0 && methodName.startsWith("is") && methodName.length() > 2 &&
Character.isUpperCase(methodName.charAt(2))) {
painlessClassBuilder.getterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3), bridgePainlessMethod.methodHandle);
} else if (typeParametersSize == 1 && methodName.startsWith("set") && methodName.length() > 3 &&
Character.isUpperCase(methodName.charAt(3))) {
painlessClassBuilder.setterMethodHandles.putIfAbsent(
Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), bridgePainlessMethod.methodHandle);
}
}
for (PainlessField painlessField : painlessClassBuilder.fields.values()) {
painlessClassBuilder.getterMethodHandles.put(painlessField.javaField.getName(), painlessField.getterMethodHandle);
painlessClassBuilder.setterMethodHandles.put(painlessField.javaField.getName(), painlessField.setterMethodHandle);
}
}
}

View File

@ -249,10 +249,14 @@ class org.elasticsearch.painless.FeatureTest no_import {
(int,int)
int getX()
int getY()
Integer getI()
void setX(int)
void setY(int)
void setI(Integer)
boolean overloadedStatic()
boolean overloadedStatic(boolean)
int staticNumberTest(Number)
Double mixedAdd(int, Byte, char, Float)
Object twoFunctionsOfX(Function,Function)
void listInput(List)
int org.elasticsearch.painless.FeatureTestAugmentation getTotal()

View File

@ -327,4 +327,22 @@ public class CastTests extends ScriptTestCase {
exec("Map map = ['a': 1,'b': 2,'c': 3]; def x = new HashMap(); x.put(1, map.clear());");
});
}
public void testBoxedDefCalls() {
assertEquals(1, exec("def x = 1; def y = 2.0; y.compareTo(x);"));
assertEquals(1, exec("def y = 2.0; y.compareTo(1);"));
assertEquals(1, exec("int x = 1; def y = 2.0; y.compareTo(x);"));
assertEquals(-1, exec("Integer x = Integer.valueOf(3); def y = 2.0; y.compareTo(x);"));
assertEquals(2, exec("def f = new org.elasticsearch.painless.FeatureTest(); f.i = (byte)2; f.i"));
assertEquals(4.0, exec(
"def x = new org.elasticsearch.painless.FeatureTest(); " +
"Byte i = Byte.valueOf(3); " +
"byte j = 1;" +
"Short s = Short.valueOf(-2);" +
"x.mixedAdd(j, i, (char)2, s)"
));
assertNull(exec("def f = new org.elasticsearch.painless.FeatureTest(); f.i = null; f.i"));
expectScriptThrows(ClassCastException.class, () -> exec("def x = 2.0; def y = 1; y.compareTo(x);"));
expectScriptThrows(ClassCastException.class, () -> exec("float f = 1.0f; def y = 1; y.compareTo(f);"));
}
}