From 2df4bd1f8135fca889312711979c13a61b90fafd Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Fri, 7 Dec 2018 09:32:27 -0800 Subject: [PATCH] [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. --- .../java/org/elasticsearch/painless/Def.java | 211 ++++++++++++-- .../elasticsearch/painless/FeatureTest.java | 21 ++ .../elasticsearch/painless/MethodWriter.java | 56 ++-- .../painless/WriterConstants.java | 48 +++- .../painless/lookup/PainlessClass.java | 17 +- .../painless/lookup/PainlessClassBuilder.java | 16 +- .../painless/lookup/PainlessLookup.java | 3 +- .../lookup/PainlessLookupBuilder.java | 258 +++++++++++++++--- .../painless/node/SFunction.java | 2 +- .../painless/spi/org.elasticsearch.txt | 4 + .../org/elasticsearch/painless/CastTests.java | 18 ++ 11 files changed, 531 insertions(+), 123 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java index 1e17d6024d4..867b481a2a4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java @@ -196,7 +196,7 @@ public final class Def { int numArguments = callSiteType.parameterCount(); // simple case: no lambdas if (recipeString.isEmpty()) { - PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - 1); + PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - 1); if (painlessMethod == null) { throw new IllegalArgumentException("dynamic method " + @@ -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 { - return (char)value; - } + 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. */ diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java index 28cbb4aee19..8806a388745 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java @@ -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 f, Function g) { return f.apply(g.apply(x)); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java index a39453838f3..b03587ba5e9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java @@ -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."); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java index 9c3d991080d..78125d7e750 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java @@ -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 */ diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java index 786b4c6a3b9..04bb3870189 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java @@ -27,36 +27,33 @@ import java.util.Objects; public final class PainlessClass { public final Map constructors; - public final Map staticMethods; public final Map methods; - public final Map staticFields; public final Map fields; + public final PainlessMethod functionalInterfaceMethod; + public final Map runtimeMethods; public final Map getterMethodHandles; public final Map setterMethodHandles; - public final PainlessMethod functionalInterfaceMethod; - PainlessClass(Map constructors, Map staticMethods, Map methods, Map staticFields, Map fields, - Map getterMethodHandles, Map setterMethodHandles, - PainlessMethod functionalInterfaceMethod) { + PainlessMethod functionalInterfaceMethod, + Map runtimeMethods, + Map getterMethodHandles, Map 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 diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java index fbf9e45bf16..04e086e2f4d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java @@ -27,36 +27,32 @@ import java.util.Objects; final class PainlessClassBuilder { final Map constructors; - final Map staticMethods; final Map methods; - final Map staticFields; final Map fields; + PainlessMethod functionalInterfaceMethod; + final Map runtimeMethods; final Map getterMethodHandles; final Map 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 diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java index 5ac5d5bf784..a482d94b42e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java @@ -227,7 +227,8 @@ public final class PainlessLookup { Objects.requireNonNull(methodName); String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity); - Function objectLookup = targetPainlessClass -> targetPainlessClass.methods.get(painlessMethodKey); + Function objectLookup = + targetPainlessClass -> targetPainlessClass.runtimeMethods.get(painlessMethodKey); return lookupRuntimePainlessObject(originalTargetClass, objectLookup); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java index 495a4ea94c9..6f4400cb4fb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java @@ -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 painlessConstructorCache = new HashMap<>(); private static final Map painlessMethodCache = new HashMap<>(); private static final Map painlessFieldCache = new HashMap<>(); private static final Map painlessClassBindingCache = new HashMap<>(); private static final Map painlessInstanceBindingCache = new HashMap<>(); + private static final Map 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 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, 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, 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("", 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> 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() { + @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 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); + } + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 6fe09627f9d..9464a54ea2c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -129,7 +129,7 @@ public final class SFunction extends AStatement { List> paramTypes = new ArrayList<>(); for (int param = 0; param < this.paramTypeStrs.size(); ++param) { - Class paramType = painlessLookup.canonicalTypeNameToType(this.paramTypeStrs.get(param)); + Class paramType = painlessLookup.canonicalTypeNameToType(this.paramTypeStrs.get(param)); if (paramType == null) { throw createError(new IllegalArgumentException( diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt index 9bb7ea94826..becf34c49ea 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt @@ -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() diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java index c9954fd7171..c955c98e82d 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java @@ -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);")); + } }