From 0b15fde27a071037885c8d644b06d607ebe77428 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 18 Apr 2017 10:39:42 -0400 Subject: [PATCH] Start on custom whitelists for Painless (#23563) We'd like to be able to support context-sensitive whitelists in Painless but we can't now because the whitelist is a static thing. This begins to de-static the whitelist, in particular removing the static keyword from most of the methods on `Definition` and plumbing the static instance into the appropriate spots as though it weren't static. Once we de-static all the methods we should be able to fairly simply build context-sensitive whitelists. The only "fun" bit of this is that I added another layer in the chain of methods that bootstraps `def` calls. Instead of running `invokedynamic` directly on `DefBootstrap` we now `invokedynamic` `$bootstrapDef` on the script itself loads the `Definition` that the script was compiled against and then calls `DefBootstrap`. I chose to put `Definition` into `Locals` so I didn't have to change the signature of all the `analyze` methods. I could have do it another way, but that seems ok for now. --- .../org/elasticsearch/painless/Compiler.java | 17 ++- .../java/org/elasticsearch/painless/Def.java | 52 ++++---- .../elasticsearch/painless/DefBootstrap.java | 26 ++-- .../elasticsearch/painless/Definition.java | 85 ++++++------- .../elasticsearch/painless/FunctionRef.java | 12 +- .../org/elasticsearch/painless/Locals.java | 25 ++-- .../painless/PainlessExplainError.java | 4 +- .../painless/ScriptInterface.java | 17 +-- .../painless/WriterConstants.java | 21 ++-- .../painless/antlr/EnhancedPainlessLexer.java | 9 +- .../elasticsearch/painless/antlr/Walker.java | 16 ++- .../painless/node/ECapturingFunctionRef.java | 8 +- .../painless/node/EExplicit.java | 5 +- .../painless/node/EFunctionRef.java | 4 +- .../painless/node/EInstanceof.java | 2 +- .../elasticsearch/painless/node/ELambda.java | 4 +- .../painless/node/EListInit.java | 6 +- .../elasticsearch/painless/node/EMapInit.java | 6 +- .../painless/node/ENewArray.java | 13 +- .../elasticsearch/painless/node/ENewObj.java | 2 +- .../elasticsearch/painless/node/EStatic.java | 6 +- .../painless/node/PCallInvoke.java | 3 +- .../painless/node/PSubBrace.java | 2 +- .../elasticsearch/painless/node/SCatch.java | 3 +- .../painless/node/SDeclaration.java | 3 +- .../elasticsearch/painless/node/SEach.java | 3 +- .../painless/node/SFunction.java | 6 +- .../elasticsearch/painless/node/SSource.java | 27 +++- .../painless/node/SSubEachArray.java | 9 +- .../painless/node/SSubEachIterable.java | 8 +- .../elasticsearch/painless/DebugTests.java | 14 ++- .../painless/DefBootstrapTests.java | 115 +++++++++++------- .../painless/ScriptTestCase.java | 6 +- .../painless/node/NodeToStringTests.java | 21 ++-- 34 files changed, 318 insertions(+), 242 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index 56c1b2e0ba79f..9961dcbe156f9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -100,15 +100,18 @@ static T compile(Loader loader, Class iface, String name, String source, " characters. The passed in script is " + source.length() + " characters. Consider using a" + " plugin if a script longer than this length is a requirement."); } - ScriptInterface scriptInterface = new ScriptInterface(iface); + Definition definition = Definition.BUILTINS; + ScriptInterface scriptInterface = new ScriptInterface(definition, iface); - SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, null); + SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition, + null); - root.analyze(); + root.analyze(definition); root.write(); try { Class clazz = loader.define(CLASS_NAME, root.getBytes()); + clazz.getField("$DEFINITION").set(null, definition); java.lang.reflect.Constructor constructor = clazz.getConstructor(String.class, String.class, BitSet.class); @@ -131,11 +134,13 @@ static byte[] compile(Class iface, String name, String source, CompilerSettin " characters. The passed in script is " + source.length() + " characters. Consider using a" + " plugin if a script longer than this length is a requirement."); } - ScriptInterface scriptInterface = new ScriptInterface(iface); + Definition definition = Definition.BUILTINS; + ScriptInterface scriptInterface = new ScriptInterface(definition, iface); - SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, debugStream); + SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition, + debugStream); - root.analyze(); + root.analyze(definition); root.write(); return root.getBytes(); 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 1438dab084f65..5250238c81784 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 @@ -175,17 +175,18 @@ static MethodHandle arrayLengthGetter(Class arrayType) { * until it finds a matching whitelisted method. If one is not found, it throws an exception. * Otherwise it returns the matching method. *

+ * @params definition the whitelist * @param receiverClass Class of the object to invoke the method on. * @param name Name of the method. * @param arity arity of method * @return matching method to invoke. never returns null. * @throws IllegalArgumentException if no matching whitelisted method was found. */ - static Method lookupMethodInternal(Class receiverClass, String name, int arity) { + static Method lookupMethodInternal(Definition definition, Class receiverClass, String name, int arity) { Definition.MethodKey key = new Definition.MethodKey(name, arity); // check whitelist for matching method for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - RuntimeClass struct = Definition.getRuntimeClass(clazz); + RuntimeClass struct = definition.getRuntimeClass(clazz); if (struct != null) { Method method = struct.methods.get(key); @@ -195,7 +196,7 @@ static Method lookupMethodInternal(Class receiverClass, String name, int arit } for (Class iface : clazz.getInterfaces()) { - struct = Definition.getRuntimeClass(iface); + struct = definition.getRuntimeClass(iface); if (struct != null) { Method method = struct.methods.get(key); @@ -220,6 +221,7 @@ static Method lookupMethodInternal(Class receiverClass, String name, int arit * until it finds a matching whitelisted method. If one is not found, it throws an exception. * Otherwise it returns a handle to the matching method. *

+ * @param definition the whitelist * @param lookup caller's lookup * @param callSiteType callsite's type * @param receiverClass Class of the object to invoke the method on. @@ -229,13 +231,13 @@ static Method lookupMethodInternal(Class receiverClass, String name, int arit * @throws IllegalArgumentException if no matching whitelisted method was found. * @throws Throwable if a method reference cannot be converted to an functional interface */ - static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, + static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodType callSiteType, Class receiverClass, String name, Object args[]) throws Throwable { String recipeString = (String) args[0]; int numArguments = callSiteType.parameterCount(); // simple case: no lambdas if (recipeString.isEmpty()) { - return lookupMethodInternal(receiverClass, name, numArguments - 1).handle; + return lookupMethodInternal(definition, receiverClass, name, numArguments - 1).handle; } // convert recipe string to a bitset for convenience (the code below should be refactored...) @@ -258,7 +260,7 @@ static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, // lookup the method with the proper arity, then we know everything (e.g. interface types of parameters). // based on these we can finally link any remaining lambdas that were deferred. - Method method = lookupMethodInternal(receiverClass, name, arity); + Method method = lookupMethodInternal(definition, receiverClass, name, arity); MethodHandle handle = method.handle; int replaced = 0; @@ -282,7 +284,8 @@ static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, if (signature.charAt(0) == 'S') { // the implementation is strongly typed, now that we know the interface type, // we have everything. - filter = lookupReferenceInternal(lookup, + filter = lookupReferenceInternal(definition, + lookup, interfaceType, type, call, @@ -292,7 +295,8 @@ static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, // 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) MethodType nestedType = MethodType.methodType(interfaceType.clazz, captures); - CallSite nested = DefBootstrap.bootstrap(lookup, + CallSite nested = DefBootstrap.bootstrap(definition, + lookup, call, nestedType, 0, @@ -319,21 +323,23 @@ static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, * This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known, * so we simply need to lookup the matching implementation method based on receiver type. */ - static MethodHandle lookupReference(Lookup lookup, String interfaceClass, - Class receiverClass, String name) throws Throwable { - Definition.Type interfaceType = Definition.getType(interfaceClass); + static MethodHandle lookupReference(Definition definition, Lookup lookup, String interfaceClass, + Class receiverClass, String name) throws Throwable { + Definition.Type interfaceType = definition.getType(interfaceClass); Method interfaceMethod = interfaceType.struct.getFunctionalMethod(); if (interfaceMethod == null) { throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface"); } int arity = interfaceMethod.arguments.size(); - Method implMethod = lookupMethodInternal(receiverClass, name, arity); - return lookupReferenceInternal(lookup, interfaceType, implMethod.owner.name, implMethod.name, receiverClass); + Method implMethod = lookupMethodInternal(definition, receiverClass, name, arity); + return lookupReferenceInternal(definition, lookup, interfaceType, implMethod.owner.name, + implMethod.name, receiverClass); } /** Returns a method handle to an implementation of clazz, given method reference signature. */ - private static MethodHandle lookupReferenceInternal(Lookup lookup, Definition.Type clazz, String type, - String call, Class... captures) throws Throwable { + private static MethodHandle lookupReferenceInternal(Definition definition, Lookup lookup, + Definition.Type clazz, String type, String call, Class... captures) + throws Throwable { final FunctionRef ref; if ("this".equals(type)) { // user written method @@ -361,7 +367,7 @@ private static MethodHandle lookupReferenceInternal(Lookup lookup, Definition.Ty ref = new FunctionRef(clazz, interfaceMethod, handle, captures.length); } else { // whitelist lookup - ref = new FunctionRef(clazz, type, call, captures.length); + ref = new FunctionRef(definition, clazz, type, call, captures.length); } final CallSite callSite; if (ref.needsBridges()) { @@ -411,15 +417,16 @@ public static String getUserFunctionHandleFieldName(String name, int arity) { * until it finds a matching whitelisted getter. If one is not found, it throws an exception. * Otherwise it returns a handle to the matching getter. *

+ * @param definition the whitelist * @param receiverClass Class of the object to retrieve the field from. * @param name Name of the field. * @return pointer to matching field. never returns null. * @throws IllegalArgumentException if no matching whitelisted field was found. */ - static MethodHandle lookupGetter(Class receiverClass, String name) { + static MethodHandle lookupGetter(Definition definition, Class receiverClass, String name) { // first try whitelist for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - RuntimeClass struct = Definition.getRuntimeClass(clazz); + RuntimeClass struct = definition.getRuntimeClass(clazz); if (struct != null) { MethodHandle handle = struct.getters.get(name); @@ -429,7 +436,7 @@ static MethodHandle lookupGetter(Class receiverClass, String name) { } for (final Class iface : clazz.getInterfaces()) { - struct = Definition.getRuntimeClass(iface); + struct = definition.getRuntimeClass(iface); if (struct != null) { MethodHandle handle = struct.getters.get(name); @@ -481,15 +488,16 @@ static MethodHandle lookupGetter(Class receiverClass, String name) { * until it finds a matching whitelisted setter. If one is not found, it throws an exception. * Otherwise it returns a handle to the matching setter. *

+ * @param definition the whitelist * @param receiverClass Class of the object to retrieve the field from. * @param name Name of the field. * @return pointer to matching field. never returns null. * @throws IllegalArgumentException if no matching whitelisted field was found. */ - static MethodHandle lookupSetter(Class receiverClass, String name) { + static MethodHandle lookupSetter(Definition definition, Class receiverClass, String name) { // first try whitelist for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - RuntimeClass struct = Definition.getRuntimeClass(clazz); + RuntimeClass struct = definition.getRuntimeClass(clazz); if (struct != null) { MethodHandle handle = struct.setters.get(name); @@ -499,7 +507,7 @@ static MethodHandle lookupSetter(Class receiverClass, String name) { } for (final Class iface : clazz.getInterfaces()) { - struct = Definition.getRuntimeClass(iface); + struct = definition.getRuntimeClass(iface); if (struct != null) { MethodHandle handle = struct.setters.get(name); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java index 307316efdf406..31fba8f757954 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java @@ -104,17 +104,19 @@ static final class PIC extends MutableCallSite { /** maximum number of types before we go megamorphic */ static final int MAX_DEPTH = 5; + private final Definition definition; private final Lookup lookup; private final String name; private final int flavor; private final Object[] args; int depth; // pkg-protected for testing - PIC(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) { + PIC(Definition definition, Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) { super(type); if (type.parameterType(0) != Object.class) { throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object."); } + this.definition = definition; this.lookup = lookup; this.name = name; this.flavor = flavor; @@ -142,11 +144,11 @@ static boolean checkClass(Class clazz, Object receiver) { private MethodHandle lookup(int flavor, String name, Class receiver) throws Throwable { switch(flavor) { case METHOD_CALL: - return Def.lookupMethod(lookup, type(), receiver, name, args); + return Def.lookupMethod(definition, lookup, type(), receiver, name, args); case LOAD: - return Def.lookupGetter(receiver, name); + return Def.lookupGetter(definition, receiver, name); case STORE: - return Def.lookupSetter(receiver, name); + return Def.lookupSetter(definition, receiver, name); case ARRAY_LOAD: return Def.lookupArrayLoad(receiver); case ARRAY_STORE: @@ -154,7 +156,7 @@ private MethodHandle lookup(int flavor, String name, Class receiver) throws T case ITERATOR: return Def.lookupIterator(receiver); case REFERENCE: - return Def.lookupReference(lookup, (String) args[0], receiver, name); + return Def.lookupReference(definition, lookup, (String) args[0], receiver, name); case INDEX_NORMALIZE: return Def.lookupIndexNormalize(receiver); default: throw new AssertionError(); @@ -237,7 +239,7 @@ Object fallback(final Object[] callArgs) throws Throwable { */ static final class MIC extends MutableCallSite { private boolean initialized; - + private final String name; private final int flavor; private final int flags; @@ -419,16 +421,18 @@ static boolean checkBoth(Class left, Class right, Object leftObject, Objec /** * invokeDynamic bootstrap method *

- * In addition to ordinary parameters, we also take some static parameters: + * In addition to ordinary parameters, we also take some parameters defined at the call site: *

    *
  • {@code initialDepth}: initial call site depth. this is used to exercise megamorphic fallback. *
  • {@code flavor}: type of dynamic call it is (and which part of whitelist to look at). *
  • {@code args}: flavor-specific args. *
+ * And we take the {@link Definition} used to compile the script for whitelist checking. *

* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic */ - public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object... args) { + public static CallSite bootstrap(Definition definition, Lookup lookup, String name, MethodType type, int initialDepth, int flavor, + Object... args) { // validate arguments switch(flavor) { // "function-call" like things get a polymorphic cache @@ -447,7 +451,7 @@ public static CallSite bootstrap(Lookup lookup, String name, MethodType type, in if (args.length != numLambdas + 1) { throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references"); } - return new PIC(lookup, name, type, initialDepth, flavor, args); + return new PIC(definition, lookup, name, type, initialDepth, flavor, args); case LOAD: case STORE: case ARRAY_LOAD: @@ -457,7 +461,7 @@ public static CallSite bootstrap(Lookup lookup, String name, MethodType type, in if (args.length > 0) { throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor); } - return new PIC(lookup, name, type, initialDepth, flavor, args); + return new PIC(definition, lookup, name, type, initialDepth, flavor, args); case REFERENCE: if (args.length != 1) { throw new BootstrapMethodError("Invalid number of parameters for reference call"); @@ -465,7 +469,7 @@ public static CallSite bootstrap(Lookup lookup, String name, MethodType type, in if (args[0] instanceof String == false) { throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]); } - return new PIC(lookup, name, type, initialDepth, flavor, args); + return new PIC(definition, lookup, name, type, initialDepth, flavor, args); // operators get monomorphic cache, with a generic impl for a fallback case UNARY_OPERATOR: diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index 16f0339677e3a..f8bee4e5cfc66 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -65,33 +65,39 @@ public final class Definition { "java.util.stream.txt", "joda.time.txt")); - private static final Definition INSTANCE = new Definition(); + /** + * Whitelist that is "built in" to Painless and required by all scripts. + */ + public static final Definition BUILTINS = new Definition(); /** Some native types as constants: */ - public static final Type VOID_TYPE = getType("void"); - public static final Type BOOLEAN_TYPE = getType("boolean"); - public static final Type BOOLEAN_OBJ_TYPE = getType("Boolean"); - public static final Type BYTE_TYPE = getType("byte"); - public static final Type BYTE_OBJ_TYPE = getType("Byte"); - public static final Type SHORT_TYPE = getType("short"); - public static final Type SHORT_OBJ_TYPE = getType("Short"); - public static final Type INT_TYPE = getType("int"); - public static final Type INT_OBJ_TYPE = getType("Integer"); - public static final Type LONG_TYPE = getType("long"); - public static final Type LONG_OBJ_TYPE = getType("Long"); - public static final Type FLOAT_TYPE = getType("float"); - public static final Type FLOAT_OBJ_TYPE = getType("Float"); - public static final Type DOUBLE_TYPE = getType("double"); - public static final Type DOUBLE_OBJ_TYPE = getType("Double"); - public static final Type CHAR_TYPE = getType("char"); - public static final Type CHAR_OBJ_TYPE = getType("Character"); - public static final Type OBJECT_TYPE = getType("Object"); - public static final Type DEF_TYPE = getType("def"); - public static final Type NUMBER_TYPE = getType("Number"); - public static final Type STRING_TYPE = getType("String"); - public static final Type EXCEPTION_TYPE = getType("Exception"); - public static final Type PATTERN_TYPE = getType("Pattern"); - public static final Type MATCHER_TYPE = getType("Matcher"); + public static final Type VOID_TYPE = BUILTINS.getType("void"); + public static final Type BOOLEAN_TYPE = BUILTINS.getType("boolean"); + public static final Type BOOLEAN_OBJ_TYPE = BUILTINS.getType("Boolean"); + public static final Type BYTE_TYPE = BUILTINS.getType("byte"); + public static final Type BYTE_OBJ_TYPE = BUILTINS.getType("Byte"); + public static final Type SHORT_TYPE = BUILTINS.getType("short"); + public static final Type SHORT_OBJ_TYPE = BUILTINS.getType("Short"); + public static final Type INT_TYPE = BUILTINS.getType("int"); + public static final Type INT_OBJ_TYPE = BUILTINS.getType("Integer"); + public static final Type LONG_TYPE = BUILTINS.getType("long"); + public static final Type LONG_OBJ_TYPE = BUILTINS.getType("Long"); + public static final Type FLOAT_TYPE = BUILTINS.getType("float"); + public static final Type FLOAT_OBJ_TYPE = BUILTINS.getType("Float"); + public static final Type DOUBLE_TYPE = BUILTINS.getType("double"); + public static final Type DOUBLE_OBJ_TYPE = BUILTINS.getType("Double"); + public static final Type CHAR_TYPE = BUILTINS.getType("char"); + public static final Type CHAR_OBJ_TYPE = BUILTINS.getType("Character"); + public static final Type OBJECT_TYPE = BUILTINS.getType("Object"); + public static final Type DEF_TYPE = BUILTINS.getType("def"); + public static final Type NUMBER_TYPE = BUILTINS.getType("Number"); + public static final Type STRING_TYPE = BUILTINS.getType("String"); + public static final Type EXCEPTION_TYPE = BUILTINS.getType("Exception"); + public static final Type PATTERN_TYPE = BUILTINS.getType("Pattern"); + public static final Type MATCHER_TYPE = BUILTINS.getType("Matcher"); + public static final Type ITERATOR_TYPE = BUILTINS.getType("Iterator"); + public static final Type ARRAY_LIST_TYPE = BUILTINS.getType("ArrayList"); + public static final Type HASH_MAP_TYPE = BUILTINS.getType("HashMap"); public enum Sort { VOID( void.class , Void.class , null , 0 , true , false , false , false ), @@ -483,38 +489,27 @@ public Struct getStruct() { } /** Returns whether or not a non-array type exists. */ - public static boolean isSimpleType(final String name) { - return INSTANCE.structsMap.containsKey(name); - } - - /** Returns whether or not a type exists without an exception. */ - public static boolean isType(final String name) { - try { - INSTANCE.getTypeInternal(name); - - return true; - } catch (IllegalArgumentException exception) { - return false; - } + public boolean isSimpleType(final String name) { + return BUILTINS.structsMap.containsKey(name); } /** Gets the type given by its name */ - public static Type getType(final String name) { - return INSTANCE.getTypeInternal(name); + public Type getType(final String name) { + return BUILTINS.getTypeInternal(name); } /** Creates an array type from the given Struct. */ - public static Type getType(final Struct struct, final int dimensions) { - return INSTANCE.getTypeInternal(struct, dimensions); + public Type getType(final Struct struct, final int dimensions) { + return BUILTINS.getTypeInternal(struct, dimensions); } - public static RuntimeClass getRuntimeClass(Class clazz) { - return INSTANCE.runtimeMap.get(clazz); + public RuntimeClass getRuntimeClass(Class clazz) { + return BUILTINS.runtimeMap.get(clazz); } /** Collection of all simple types. Used by {@code PainlessDocGenerator} to generate an API reference. */ static Collection allSimpleTypes() { - return INSTANCE.simpleTypesMap.values(); + return BUILTINS.simpleTypesMap.values(); } // INTERNAL IMPLEMENTATION: diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java index d5e02e12058bf..dddd91663115d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java @@ -50,13 +50,16 @@ public class FunctionRef { /** * Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist. + * @param definition the whitelist against which this script is being compiled * @param expected 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 FunctionRef(Definition.Type expected, String type, String call, int numCaptures) { - this(expected, expected.struct.getFunctionalMethod(), lookup(expected, type, call, numCaptures > 0), numCaptures); + public FunctionRef(Definition definition, Definition.Type expected, String type, String call, + int numCaptures) { + this(expected, expected.struct.getFunctionalMethod(), + lookup(definition, expected, type, call, numCaptures > 0), numCaptures); } /** @@ -134,7 +137,8 @@ public FunctionRef(Definition.Type expected, Definition.Method method, MethodHan /** * Looks up {@code type::call} from the whitelist, and returns a matching method. */ - private static Definition.Method lookup(Definition.Type expected, String type, String call, boolean receiverCaptured) { + private static Definition.Method lookup(Definition definition, Definition.Type expected, + String type, String call, boolean receiverCaptured) { // check its really a functional interface // for e.g. Comparable Method method = expected.struct.getFunctionalMethod(); @@ -144,7 +148,7 @@ private static Definition.Method lookup(Definition.Type expected, String type, S } // lookup requested method - Definition.Struct struct = Definition.getType(type).struct; + Definition.Struct struct = definition.getType(type).struct; final Definition.Method impl; // ctor ref if ("new".equals(call)) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java index 9bbe9d9def39a..6eff9e3228bb4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java @@ -60,7 +60,7 @@ public static Locals newLocalScope(Locals currentScope) { */ public static Locals newLambdaScope(Locals programScope, Type returnType, List parameters, int captureCount, int maxLoopCounter) { - Locals locals = new Locals(programScope, returnType, KEYWORDS); + Locals locals = new Locals(programScope, programScope.definition, returnType, KEYWORDS); for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); // TODO: allow non-captures to be r/w: @@ -79,7 +79,7 @@ public static Locals newLambdaScope(Locals programScope, Type returnType, List

parameters, int maxLoopCounter) { - Locals locals = new Locals(programScope, returnType, KEYWORDS); + Locals locals = new Locals(programScope, programScope.definition, returnType, KEYWORDS); for (Parameter parameter : parameters) { locals.addVariable(parameter.location, parameter.type, parameter.name, false); } @@ -92,9 +92,10 @@ public static Locals newFunctionScope(Locals programScope, Type returnType, List /** Creates a new main method scope */ public static Locals newMainMethodScope(ScriptInterface scriptInterface, Locals programScope, int maxLoopCounter) { - Locals locals = new Locals(programScope, scriptInterface.getExecuteMethodReturnType(), KEYWORDS); + Locals locals = new Locals(programScope, programScope.definition, + scriptInterface.getExecuteMethodReturnType(), KEYWORDS); // This reference. Internal use only. - locals.defineVariable(null, Definition.getType("Object"), THIS, true); + locals.defineVariable(null, programScope.definition.getType("Object"), THIS, true); // Method arguments for (MethodArgument arg : scriptInterface.getExecuteArguments()) { @@ -109,8 +110,8 @@ public static Locals newMainMethodScope(ScriptInterface scriptInterface, Locals } /** Creates a new program scope: the list of methods. It is the parent for all methods */ - public static Locals newProgramScope(Collection methods) { - Locals locals = new Locals(null, null, null); + public static Locals newProgramScope(Definition definition, Collection methods) { + Locals locals = new Locals(null, definition, null, null); for (Method method : methods) { locals.addMethod(method); } @@ -178,8 +179,15 @@ public Locals getProgramScope() { return locals; } + /** Whitelist against which this script is being compiled. */ + public Definition getDefinition() { + return definition; + } + ///// private impl + /** Whitelist against which thhis script is being compiled. */ + private final Definition definition; // parent scope private final Locals parent; // return type of this scope @@ -197,14 +205,15 @@ public Locals getProgramScope() { * Create a new Locals */ private Locals(Locals parent) { - this(parent, parent.returnType, parent.keywords); + this(parent, parent.definition, parent.returnType, parent.keywords); } /** * Create a new Locals with specified return type */ - private Locals(Locals parent, Type returnType, Set keywords) { + private Locals(Locals parent, Definition definition, Type returnType, Set keywords) { this.parent = parent; + this.definition = definition; this.returnType = returnType; this.keywords = keywords; if (parent == null) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java index fff692fdb9f3e..291d852bdde22 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java @@ -46,7 +46,7 @@ Object getObjectToExplain() { /** * Headers to be added to the {@link ScriptException} for structured rendering. */ - public Map> getHeaders() { + public Map> getHeaders(Definition definition) { Map> headers = new TreeMap<>(); String toString = "null"; String javaClassName = null; @@ -54,7 +54,7 @@ public Map> getHeaders() { if (objectToExplain != null) { toString = objectToExplain.toString(); javaClassName = objectToExplain.getClass().getName(); - Definition.RuntimeClass runtimeClass = Definition.getRuntimeClass(objectToExplain.getClass()); + Definition.RuntimeClass runtimeClass = definition.getRuntimeClass(objectToExplain.getClass()); if (runtimeClass != null) { painlessClassName = runtimeClass.getStruct().name; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInterface.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInterface.java index b8ab32d1c6d88..28fa6fd42806c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInterface.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInterface.java @@ -40,7 +40,7 @@ public class ScriptInterface { private final List executeArguments; private final List usesMethods; - public ScriptInterface(Class iface) { + public ScriptInterface(Definition definition, Class iface) { this.iface = iface; // Find the main method and the uses$argName methods @@ -77,7 +77,7 @@ public ScriptInterface(Class iface) { } MethodType methodType = MethodType.methodType(executeMethod.getReturnType(), executeMethod.getParameterTypes()); this.executeMethod = new org.objectweb.asm.commons.Method(executeMethod.getName(), methodType.toMethodDescriptorString()); - executeMethodReturnType = definitionTypeForClass(executeMethod.getReturnType(), + executeMethodReturnType = definitionTypeForClass(definition, executeMethod.getReturnType(), componentType -> "Painless can only implement execute methods returning a whitelisted type but [" + iface.getName() + "#execute] returns [" + componentType.getName() + "] which isn't whitelisted."); @@ -91,7 +91,7 @@ public ScriptInterface(Class iface) { + iface.getName() + "#execute] takes [1] argument."); } for (int arg = 0; arg < types.length; arg++) { - arguments.add(methodArgument(types[arg], argumentNamesConstant[arg])); + arguments.add(methodArgument(definition, types[arg], argumentNamesConstant[arg])); argumentNames.add(argumentNamesConstant[arg]); } this.executeArguments = unmodifiableList(arguments); @@ -164,13 +164,14 @@ public String getName() { } } - private static MethodArgument methodArgument(Class type, String argName) { - Definition.Type defType = definitionTypeForClass(type, componentType -> "[" + argName + "] is of unknown type [" + private MethodArgument methodArgument(Definition definition, Class type, String argName) { + Definition.Type defType = definitionTypeForClass(definition, type, componentType -> "[" + argName + "] is of unknown type [" + componentType.getName() + ". Painless interfaces can only accept arguments that are of whitelisted types."); return new MethodArgument(defType, argName); } - private static Definition.Type definitionTypeForClass(Class type, Function, String> unknownErrorMessageSource) { + private static Definition.Type definitionTypeForClass(Definition definition, Class type, + Function, String> unknownErrorMessageSource) { int dimensions = 0; Class componentType = type; while (componentType.isArray()) { @@ -181,13 +182,13 @@ private static Definition.Type definitionTypeForClass(Class type, Function iface) { 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 6ae637e59b1e5..f29afbb74e67f 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 @@ -65,7 +65,9 @@ public final class WriterConstants { public static final Type STACK_OVERFLOW_ERROR_TYPE = Type.getType(StackOverflowError.class); public static final Type EXCEPTION_TYPE = Type.getType(Exception.class); public static final Type PAINLESS_EXPLAIN_ERROR_TYPE = Type.getType(PainlessExplainError.class); - public static final Method PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD = getAsmMethod(Map.class, "getHeaders"); + public static final Method PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD = getAsmMethod(Map.class, "getHeaders", Definition.class); + + public static final Type DEFINITION_TYPE = Type.getType(Definition.class); public static final Type COLLECTIONS_TYPE = Type.getType(Collections.class); public static final Method EMPTY_MAP_METHOD = getAsmMethod(Map.class, "emptyMap"); @@ -83,6 +85,8 @@ public final class WriterConstants { public static final Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class); public static final Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class); + public static final Type OBJECT_ARRAY_TYPE = Type.getType("[Ljava/lang/Object;"); + public static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); public static final Type AUGMENTATION_TYPE = Type.getType(Augmentation.class); @@ -98,13 +102,14 @@ public final class WriterConstants { public static final Method MATCHER_MATCHES = getAsmMethod(boolean.class, "matches"); public static final Method MATCHER_FIND = getAsmMethod(boolean.class, "find"); - /** dynamic callsite bootstrap signature */ - static final MethodType DEF_BOOTSTRAP_TYPE = - MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, - int.class, int.class, Object[].class); - static final Handle DEF_BOOTSTRAP_HANDLE = - new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DefBootstrap.class), - "bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString(), false); + public static final Method DEF_BOOTSTRAP_METHOD = getAsmMethod(CallSite.class, "$bootstrapDef", MethodHandles.Lookup.class, + String.class, MethodType.class, int.class, int.class, Object[].class); + static final Handle DEF_BOOTSTRAP_HANDLE = new Handle(Opcodes.H_INVOKESTATIC, CLASS_TYPE.getInternalName(), "$bootstrapDef", + DEF_BOOTSTRAP_METHOD.getDescriptor(), false); + public static final Type DEF_BOOTSTRAP_DELEGATE_TYPE = Type.getType(DefBootstrap.class); + public static final Method DEF_BOOTSTRAP_DELEGATE_METHOD = getAsmMethod(CallSite.class, "bootstrap", Definition.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); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java index 640d9c29b2053..506ac8fcdecdb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java @@ -41,13 +41,16 @@ * */ final class EnhancedPainlessLexer extends PainlessLexer { - final String sourceName; + private final String sourceName; + private final Definition definition; + private Token stashedNext = null; private Token previous = null; - EnhancedPainlessLexer(CharStream charStream, String sourceName) { + EnhancedPainlessLexer(CharStream charStream, String sourceName, Definition definition) { super(charStream); this.sourceName = sourceName; + this.definition = definition; } public Token getPreviousToken() { @@ -93,7 +96,7 @@ public void recover(final LexerNoViableAltException lnvae) { @Override protected boolean isSimpleType(String name) { - return Definition.isSimpleType(name); + return definition.isSimpleType(name); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java index 19d15a4beb36c..51f374478219b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java @@ -29,10 +29,11 @@ import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.tree.TerminalNode; import org.elasticsearch.painless.CompilerSettings; +import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; -import org.elasticsearch.painless.ScriptInterface; import org.elasticsearch.painless.Operation; +import org.elasticsearch.painless.ScriptInterface; import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext; @@ -173,9 +174,11 @@ */ public final class Walker extends PainlessParserBaseVisitor { - public static SSource buildPainlessTree(ScriptInterface mainMethod, String sourceName, String sourceText, CompilerSettings settings, + public static SSource buildPainlessTree(ScriptInterface mainMethod, String sourceName, + String sourceText, CompilerSettings settings, Definition definition, Printer debugStream) { - return new Walker(mainMethod, sourceName, sourceText, settings, debugStream).source; + return new Walker(mainMethod, sourceName, sourceText, settings, definition, + debugStream).source; } private final ScriptInterface scriptInterface; @@ -184,24 +187,27 @@ public static SSource buildPainlessTree(ScriptInterface mainMethod, String sourc private final Printer debugStream; private final String sourceName; private final String sourceText; + private final Definition definition; private final Deque reserved = new ArrayDeque<>(); private final Globals globals; private int syntheticCounter = 0; - private Walker(ScriptInterface scriptInterface, String sourceName, String sourceText, CompilerSettings settings, Printer debugStream) { + private Walker(ScriptInterface scriptInterface, String sourceName, String sourceText, + CompilerSettings settings, Definition definition, Printer debugStream) { this.scriptInterface = scriptInterface; this.debugStream = debugStream; this.settings = settings; this.sourceName = Location.computeSourceName(sourceName, sourceText); this.sourceText = sourceText; this.globals = new Globals(new BitSet(sourceText.length())); + this.definition = definition; this.source = (SSource)visit(buildAntlrTree(sourceText)); } private SourceContext buildAntlrTree(String source) { ANTLRInputStream stream = new ANTLRInputStream(source); - PainlessLexer lexer = new EnhancedPainlessLexer(stream, sourceName); + PainlessLexer lexer = new EnhancedPainlessLexer(stream, sourceName, definition); PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer)); ParserErrorStrategy strategy = new ParserErrorStrategy(sourceName); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index d305bf08373f5..717d2a43b1ea1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -60,8 +60,8 @@ void extractVariables(Set variables) { } @Override - void analyze(Locals variables) { - captured = variables.getVariable(location, variable); + void analyze(Locals locals) { + captured = locals.getVariable(location, variable); if (expected == null) { if (captured.type.sort == Definition.Sort.DEF) { // dynamic implementation @@ -70,13 +70,13 @@ void analyze(Locals variables) { // typed implementation defPointer = "S" + captured.type.name + "." + call + ",1"; } - actual = Definition.getType("String"); + actual = locals.getDefinition().getType("String"); } else { defPointer = null; // static case if (captured.type.sort != Definition.Sort.DEF) { try { - ref = new FunctionRef(expected, captured.type.name, call, 1); + ref = new FunctionRef(locals.getDefinition(), expected, captured.type.name, call, 1); } catch (IllegalArgumentException e) { throw createError(e); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java index f5e6f6f96b261..2624735aa07ee 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java @@ -19,10 +19,9 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Globals; -import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Locals; +import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import java.util.Objects; @@ -51,7 +50,7 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { try { - actual = Definition.getType(this.type); + actual = locals.getDefinition().getType(type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java index 48230646b01e9..0fe11400269b4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java @@ -59,7 +59,7 @@ void extractVariables(Set variables) {} void analyze(Locals locals) { if (expected == null) { ref = null; - actual = Definition.getType("String"); + actual = locals.getDefinition().getType("String"); defPointer = "S" + type + "." + call + ",0"; } else { defPointer = null; @@ -79,7 +79,7 @@ void analyze(Locals locals) { ref = new FunctionRef(expected, interfaceMethod, implMethod, 0); } else { // whitelist lookup - ref = new FunctionRef(expected, type, call, 0); + ref = new FunctionRef(locals.getDefinition(), expected, type, call, 0); } } catch (IllegalArgumentException e) { throw createError(e); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java index 9bdeea93fde2c..c9b2c95bb4c2d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java @@ -59,7 +59,7 @@ void analyze(Locals locals) { // ensure the specified type is part of the definition try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index 4960c6520c06b..ca086561d6989 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -164,14 +164,14 @@ void analyze(Locals locals) { // desugar lambda body into a synthetic method desugared = new SFunction(reserved, location, returnType.name, name, paramTypes, paramNames, statements, true); - desugared.generateSignature(); + desugared.generateSignature(locals.getDefinition()); desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters, captures.size(), reserved.getMaxLoopCounter())); // setup method reference to synthetic method if (expected == null) { ref = null; - actual = Definition.getType("String"); + actual = locals.getDefinition().getType("String"); defPointer = "Sthis." + name + "," + captures.size(); } else { defPointer = null; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java index eb6ff14fb8b0f..999d35551cef2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java @@ -58,11 +58,7 @@ void analyze(Locals locals) { throw createError(new IllegalArgumentException("Must read from list initializer.")); } - try { - actual = Definition.getType("ArrayList"); - } catch (IllegalArgumentException exception) { - throw createError(new IllegalStateException("Illegal tree structure.")); - } + actual = Definition.ARRAY_LIST_TYPE; constructor = actual.struct.constructors.get(new MethodKey("", 0)); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java index 1e437d0a71caf..0647b5716e047 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java @@ -64,11 +64,7 @@ void analyze(Locals locals) { throw createError(new IllegalArgumentException("Must read from map initializer.")); } - try { - actual = Definition.getType("HashMap"); - } catch (IllegalArgumentException exception) { - throw createError(new IllegalStateException("Illegal tree structure.")); - } + actual = Definition.HASH_MAP_TYPE; constructor = actual.struct.constructors.get(new MethodKey("", 0)); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java index f19bff33e5d1a..d32a153b79730 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java @@ -63,7 +63,7 @@ void analyze(Locals locals) { final Type type; try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } @@ -71,13 +71,14 @@ void analyze(Locals locals) { for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); - expression.expected = initialize ? Definition.getType(type.struct, 0) : Definition.INT_TYPE; + expression.expected = initialize ? locals.getDefinition().getType(type.struct, 0) + : Definition.INT_TYPE; expression.internal = true; expression.analyze(locals); arguments.set(argument, expression.cast(locals)); } - actual = Definition.getType(type.struct, initialize ? 1 : arguments.size()); + actual = locals.getDefinition().getType(type.struct, initialize ? 1 : arguments.size()); } @Override @@ -86,7 +87,7 @@ void write(MethodWriter writer, Globals globals) { if (initialize) { writer.push(arguments.size()); - writer.newArray(Definition.getType(actual.struct, 0).type); + writer.newArray(actual.struct.type); for (int index = 0; index < arguments.size(); ++index) { AExpression argument = arguments.get(index); @@ -94,7 +95,7 @@ void write(MethodWriter writer, Globals globals) { writer.dup(); writer.push(index); argument.write(writer, globals); - writer.arrayStore(Definition.getType(actual.struct, 0).type); + writer.arrayStore(actual.struct.type); } } else { for (AExpression argument : arguments) { @@ -104,7 +105,7 @@ void write(MethodWriter writer, Globals globals) { if (arguments.size() > 1) { writer.visitMultiANewArrayInsn(actual.type.getDescriptor(), actual.type.getDimensions()); } else { - writer.newArray(Definition.getType(actual.struct, 0).type); + writer.newArray(actual.struct.type); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java index 7f2458a8dc109..08cd76917c5fd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java @@ -61,7 +61,7 @@ void analyze(Locals locals) { final Type type; try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java index 9db9f6f8caeda..e675fb1510894 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java @@ -19,16 +19,14 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Globals; +import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import java.util.Objects; import java.util.Set; -import org.elasticsearch.painless.Locals; - /** * Represents a static type target. */ @@ -50,7 +48,7 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { try { - actual = Definition.getType(type); + actual = locals.getDefinition().getType(type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java index 29369cd945689..80bdc3d597c8a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.Sort; @@ -74,7 +73,7 @@ void analyze(Locals locals) { Struct struct = prefix.actual.struct; if (prefix.actual.sort.primitive) { - struct = Definition.getType(prefix.actual.sort.boxed.getSimpleName()).struct; + struct = locals.getDefinition().getType(prefix.actual.sort.boxed.getSimpleName()).struct; } MethodKey methodKey = new MethodKey(name, arguments.size()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java index 1a12f23211ea8..1f3d4109bca8a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java @@ -55,7 +55,7 @@ void analyze(Locals locals) { index.analyze(locals); index = index.cast(locals); - actual = Definition.getType(type.struct, type.dimensions - 1); + actual = locals.getDefinition().getType(type.struct, type.dimensions - 1); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java index 90940ae81d07e..6940e48342a26 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; @@ -69,7 +68,7 @@ void analyze(Locals locals) { final Type type; try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java index 57477561f7f6d..ab9e58db23ebb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; @@ -64,7 +63,7 @@ void analyze(Locals locals) { final Type type; try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index 04c9e6697cfcb..003f303b5e023 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Globals; @@ -72,7 +71,7 @@ void analyze(Locals locals) { final Type type; try { - type = Definition.getType(this.type); + type = locals.getDefinition().getType(this.type); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } 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 760b0d15d83ee..3ef20b023ce75 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 @@ -106,9 +106,9 @@ void extractVariables(Set variables) { throw new IllegalStateException("Illegal tree structure"); } - void generateSignature() { + void generateSignature(Definition definition) { try { - rtnType = Definition.getType(rtnTypeStr); + rtnType = definition.getType(rtnTypeStr); } catch (IllegalArgumentException exception) { throw createError(new IllegalArgumentException("Illegal return type [" + rtnTypeStr + "] for function [" + name + "].")); } @@ -122,7 +122,7 @@ void generateSignature() { for (int param = 0; param < this.paramTypeStrs.size(); ++param) { try { - Type paramType = Definition.getType(this.paramTypeStrs.get(param)); + Type paramType = definition.getType(this.paramTypeStrs.get(param)); paramClasses[param] = paramType.clazz; paramTypes.add(paramType); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java index 56e08b4ddf152..eb323e3293fdd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.CompilerSettings; import org.elasticsearch.painless.Constant; +import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Globals; @@ -58,6 +59,10 @@ import static org.elasticsearch.painless.WriterConstants.COLLECTIONS_TYPE; import static org.elasticsearch.painless.WriterConstants.CONSTRUCTOR; import static org.elasticsearch.painless.WriterConstants.CONVERT_TO_SCRIPT_EXCEPTION_METHOD; +import static org.elasticsearch.painless.WriterConstants.DEFINITION_TYPE; +import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_METHOD; +import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_TYPE; +import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_METHOD; import static org.elasticsearch.painless.WriterConstants.EMPTY_MAP_METHOD; import static org.elasticsearch.painless.WriterConstants.EXCEPTION_TYPE; import static org.elasticsearch.painless.WriterConstants.OUT_OF_MEMORY_ERROR_TYPE; @@ -145,11 +150,11 @@ void extractVariables(Set variables) { throw new IllegalStateException("Illegal tree structure."); } - public void analyze() { + public void analyze(Definition definition) { Map methods = new HashMap<>(); for (SFunction function : functions) { - function.generateSignature(); + function.generateSignature(definition); MethodKey key = new MethodKey(function.name, function.parameters.size()); @@ -158,7 +163,7 @@ public void analyze() { } } - analyze(Locals.newProgramScope(methods.values())); + analyze(Locals.newProgramScope(definition, methods.values())); } @Override @@ -216,6 +221,19 @@ public void write() { visitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, classBase, classInterfaces); visitor.visitSource(Location.computeSourceName(name, source), null); + // Write the a method to bootstrap def calls + MethodWriter bootstrapDef = new MethodWriter(Opcodes.ACC_STATIC | Opcodes.ACC_VARARGS, DEF_BOOTSTRAP_METHOD, visitor, + globals.getStatements(), settings); + bootstrapDef.visitCode(); + bootstrapDef.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE); + bootstrapDef.loadArgs(); + bootstrapDef.invokeStatic(DEF_BOOTSTRAP_DELEGATE_TYPE, DEF_BOOTSTRAP_DELEGATE_METHOD); + bootstrapDef.returnValue(); + bootstrapDef.endMethod(); + + // Write the static variable used by the method to bootstrap def calls + visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$DEFINITION", DEFINITION_TYPE.getDescriptor(), null, null).visitEnd(); + // Write the constructor: MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements(), settings); constructor.visitCode(); @@ -330,13 +348,14 @@ void write(MethodWriter writer, Globals globals) { writer.goTo(endCatch); // This looks like: // } catch (PainlessExplainError e) { - // throw this.convertToScriptException(e, e.getHeaders()) + // throw this.convertToScriptException(e, e.getHeaders($DEFINITION)) // } writer.visitTryCatchBlock(startTry, endTry, startExplainCatch, PAINLESS_EXPLAIN_ERROR_TYPE.getInternalName()); writer.mark(startExplainCatch); writer.loadThis(); writer.swap(); writer.dup(); + writer.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE); writer.invokeVirtual(PAINLESS_EXPLAIN_ERROR_TYPE, PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD); writer.invokeVirtual(BASE_CLASS_TYPE, CONVERT_TO_SCRIPT_EXCEPTION_METHOD); writer.throwException(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java index c153eacbf9275..2083d3ddbe5e5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java @@ -64,9 +64,12 @@ void extractVariables(Set variables) { void analyze(Locals locals) { // We must store the array and index as variables for securing slots on the stack, and // also add the location offset to make the names unique in case of nested for each loops. - array = locals.addVariable(location, expression.actual, "#array" + location.getOffset(), true); - index = locals.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true); - indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1); + array = locals.addVariable(location, expression.actual, "#array" + location.getOffset(), + true); + index = locals.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), + true); + indexed = locals.getDefinition().getType(expression.actual.struct, + expression.actual.dimensions - 1); cast = AnalyzerCaster.getLegalCast(location, indexed, variable.type, true, true); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java index 845cc264530e4..b014e952e32a2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java @@ -26,7 +26,6 @@ import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.Sort; -import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals.Variable; @@ -72,7 +71,8 @@ void extractVariables(Set variables) { void analyze(Locals locals) { // We must store the iterator as a variable for securing a slot on the stack, and // also add the location offset to make the name unique in case of nested for each loops. - iterator = locals.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true); + iterator = locals.addVariable(location, locals.getDefinition().getType("Iterator"), + "#itr" + location.getOffset(), true); if (expression.actual.sort == Sort.DEF) { method = null; @@ -95,8 +95,8 @@ void write(MethodWriter writer, Globals globals) { expression.write(writer, globals); if (method == null) { - Type itr = Definition.getType("Iterator"); - org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type); + org.objectweb.asm.Type methodType = org.objectweb.asm.Type + .getMethodType(Definition.ITERATOR_TYPE.type, Definition.DEF_TYPE.type); writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR); } else { method.write(writer); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java index 0c96251a51aaa..c1098a1e7afa4 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java @@ -34,22 +34,24 @@ import static org.hamcrest.Matchers.not; public class DebugTests extends ScriptTestCase { + private final Definition definition = Definition.BUILTINS; + public void testExplain() { // Debug.explain can explain an object Object dummy = new Object(); PainlessExplainError e = expectScriptThrows(PainlessExplainError.class, () -> exec( "Debug.explain(params.a)", singletonMap("a", dummy), true)); assertSame(dummy, e.getObjectToExplain()); - assertThat(e.getHeaders(), hasEntry("es.to_string", singletonList(dummy.toString()))); - assertThat(e.getHeaders(), hasEntry("es.java_class", singletonList("java.lang.Object"))); - assertThat(e.getHeaders(), hasEntry("es.painless_class", singletonList("Object"))); + assertThat(e.getHeaders(definition), hasEntry("es.to_string", singletonList(dummy.toString()))); + assertThat(e.getHeaders(definition), hasEntry("es.java_class", singletonList("java.lang.Object"))); + assertThat(e.getHeaders(definition), hasEntry("es.painless_class", singletonList("Object"))); // Null should be ok e = expectScriptThrows(PainlessExplainError.class, () -> exec("Debug.explain(null)")); assertNull(e.getObjectToExplain()); - assertThat(e.getHeaders(), hasEntry("es.to_string", singletonList("null"))); - assertThat(e.getHeaders(), not(hasKey("es.java_class"))); - assertThat(e.getHeaders(), not(hasKey("es.painless_class"))); + assertThat(e.getHeaders(definition), hasEntry("es.to_string", singletonList("null"))); + assertThat(e.getHeaders(definition), not(hasKey("es.java_class"))); + assertThat(e.getHeaders(definition), not(hasKey("es.painless_class"))); // You can't catch the explain exception e = expectScriptThrows(PainlessExplainError.class, () -> exec( diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java index 68d184cb1732b..bc7e4b5ebf774 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java @@ -30,14 +30,17 @@ import org.elasticsearch.test.ESTestCase; public class DefBootstrapTests extends ESTestCase { + private final Definition definition = Definition.BUILTINS; /** calls toString() on integers, twice */ public void testOneType() throws Throwable { - CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "toString", - MethodType.methodType(String.class, Object.class), - 0, - DefBootstrap.METHOD_CALL, ""); + CallSite site = DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "toString", + MethodType.methodType(String.class, Object.class), + 0, + DefBootstrap.METHOD_CALL, + ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -51,11 +54,13 @@ public void testOneType() throws Throwable { } public void testTwoTypes() throws Throwable { - CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "toString", - MethodType.methodType(String.class, Object.class), - 0, - DefBootstrap.METHOD_CALL, ""); + CallSite site = DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "toString", + MethodType.methodType(String.class, Object.class), + 0, + DefBootstrap.METHOD_CALL, + ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -74,11 +79,13 @@ public void testTwoTypes() throws Throwable { public void testTooManyTypes() throws Throwable { // if this changes, test must be rewritten assertEquals(5, DefBootstrap.PIC.MAX_DEPTH); - CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "toString", - MethodType.methodType(String.class, Object.class), - 0, - DefBootstrap.METHOD_CALL, ""); + CallSite site = DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "toString", + MethodType.methodType(String.class, Object.class), + 0, + DefBootstrap.METHOD_CALL, + ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -98,11 +105,13 @@ public void testTooManyTypes() throws Throwable { /** test that we revert to the megamorphic classvalue cache and that it works as expected */ public void testMegamorphic() throws Throwable { - DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), + DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), "size", MethodType.methodType(int.class, Object.class), 0, - DefBootstrap.METHOD_CALL, ""); + DefBootstrap.METHOD_CALL, + ""); site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (int)handle.invokeExact((Object) Arrays.asList("1", "2"))); @@ -128,43 +137,51 @@ public void testMegamorphic() throws Throwable { // test operators with null guards public void testNullGuardAdd() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "add", - MethodType.methodType(Object.class, Object.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "add", + MethodType.methodType(Object.class, Object.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test")); } public void testNullGuardAddWhenCached() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "add", - MethodType.methodType(Object.class, Object.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "add", + MethodType.methodType(Object.class, Object.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (Object)handle.invokeExact((Object)1, (Object)1)); assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test")); } public void testNullGuardEq() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "eq", - MethodType.methodType(boolean.class, Object.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "eq", + MethodType.methodType(boolean.class, Object.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test")); assertTrue((boolean) handle.invokeExact((Object)null, (Object)null)); } public void testNullGuardEqWhenCached() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "eq", - MethodType.methodType(boolean.class, Object.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "eq", + MethodType.methodType(boolean.class, Object.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertTrue((boolean) handle.invokeExact((Object)1, (Object)1)); assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test")); @@ -176,11 +193,13 @@ public void testNullGuardEqWhenCached() throws Throwable { // and can be disabled in some circumstances. public void testNoNullGuardAdd() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "add", - MethodType.methodType(Object.class, int.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, 0); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "add", + MethodType.methodType(Object.class, int.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + 0); MethodHandle handle = site.dynamicInvoker(); expectThrows(NullPointerException.class, () -> { assertNotNull((Object)handle.invokeExact(5, (Object)null)); @@ -188,11 +207,13 @@ public void testNoNullGuardAdd() throws Throwable { } public void testNoNullGuardAddWhenCached() throws Throwable { - DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), - "add", - MethodType.methodType(Object.class, int.class, Object.class), - 0, - DefBootstrap.BINARY_OPERATOR, 0); + DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, + MethodHandles.publicLookup(), + "add", + MethodType.methodType(Object.class, int.class, Object.class), + 0, + DefBootstrap.BINARY_OPERATOR, + 0); MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (Object)handle.invokeExact(1, (Object)1)); expectThrows(NullPointerException.class, () -> { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index 544095caf9dc6..74c6c9a5628f0 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -76,11 +76,13 @@ public Object exec(String script, Map vars, boolean picky) { public Object exec(String script, Map vars, Map compileParams, Scorer scorer, boolean picky) { // test for ambiguity errors before running the actual script if picky is true if (picky) { - ScriptInterface scriptInterface = new ScriptInterface(GenericElasticsearchScript.class); + Definition definition = Definition.BUILTINS; + ScriptInterface scriptInterface = new ScriptInterface(definition, GenericElasticsearchScript.class); CompilerSettings pickySettings = new CompilerSettings(); pickySettings.setPicky(true); pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings())); - Walker.buildPainlessTree(scriptInterface, getTestName(), script, pickySettings, null); + Walker.buildPainlessTree(scriptInterface, getTestName(), script, pickySettings, + definition, null); } // test actual script execution Object object = scriptEngine.compile(null, script, compileParams); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java index a4530823c9ed3..df9d0c0f4eaab 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java @@ -47,6 +47,8 @@ * Tests {@link Object#toString} implementations on all extensions of {@link ANode}. */ public class NodeToStringTests extends ESTestCase { + private final Definition definition = Definition.BUILTINS; + public void testEAssignment() { assertToString( "(SSource\n" @@ -399,7 +401,7 @@ public void testPSubBrace() { public void testPSubCallInvoke() { Location l = new Location(getTestName(), 0); - RuntimeClass c = Definition.getRuntimeClass(Integer.class); + RuntimeClass c = definition.getRuntimeClass(Integer.class); Method m = c.methods.get(new MethodKey("toString", 0)); PSubCallInvoke node = new PSubCallInvoke(l, m, null, emptyList()); node.prefix = new EVariable(l, "a"); @@ -454,7 +456,7 @@ public void testPSubDefField() { public void testPSubField() { Location l = new Location(getTestName(), 0); - Struct s = Definition.getType(Boolean.class.getSimpleName()).struct; + Struct s = definition.getType(Boolean.class.getSimpleName()).struct; Field f = s.staticMembers.get("TRUE"); PSubField node = new PSubField(l, f); node.prefix = new EStatic(l, "Boolean"); @@ -464,7 +466,7 @@ public void testPSubField() { public void testPSubListShortcut() { Location l = new Location(getTestName(), 0); - Struct s = Definition.getType(List.class.getSimpleName()).struct; + Struct s = definition.getType(List.class.getSimpleName()).struct; PSubListShortcut node = new PSubListShortcut(l, s, new EConstant(l, 1)); node.prefix = new EVariable(l, "a"); assertEquals("(PSubListShortcut (EVariable a) (EConstant Integer 1))", node.toString()); @@ -472,7 +474,7 @@ public void testPSubListShortcut() { new PSubNullSafeCallInvoke(l, node).toString()); l = new Location(getTestName(), 0); - s = Definition.getType(List.class.getSimpleName()).struct; + s = definition.getType(List.class.getSimpleName()).struct; node = new PSubListShortcut(l, s, new EBinary(l, Operation.ADD, new EConstant(l, 1), new EConstant(l, 4))); node.prefix = new EVariable(l, "a"); assertEquals("(PSubListShortcut (EVariable a) (EBinary (EConstant Integer 1) + (EConstant Integer 4)))", node.toString()); @@ -480,7 +482,7 @@ public void testPSubListShortcut() { public void testPSubMapShortcut() { Location l = new Location(getTestName(), 0); - Struct s = Definition.getType(Map.class.getSimpleName()).struct; + Struct s = definition.getType(Map.class.getSimpleName()).struct; PSubMapShortcut node = new PSubMapShortcut(l, s, new EConstant(l, "cat")); node.prefix = new EVariable(l, "a"); assertEquals("(PSubMapShortcut (EVariable a) (EConstant String 'cat'))", node.toString()); @@ -488,7 +490,7 @@ public void testPSubMapShortcut() { new PSubNullSafeCallInvoke(l, node).toString()); l = new Location(getTestName(), 1); - s = Definition.getType(Map.class.getSimpleName()).struct; + s = definition.getType(Map.class.getSimpleName()).struct; node = new PSubMapShortcut(l, s, new EBinary(l, Operation.ADD, new EConstant(l, 1), new EConstant(l, 4))); node.prefix = new EVariable(l, "a"); assertEquals("(PSubMapShortcut (EVariable a) (EBinary (EConstant Integer 1) + (EConstant Integer 4)))", node.toString()); @@ -496,7 +498,7 @@ public void testPSubMapShortcut() { public void testPSubShortcut() { Location l = new Location(getTestName(), 0); - Struct s = Definition.getType(FeatureTest.class.getName()).struct; + Struct s = definition.getType(FeatureTest.class.getName()).struct; Method getter = s.methods.get(new MethodKey("getX", 0)); Method setter = s.methods.get(new MethodKey("setX", 1)); PSubShortcut node = new PSubShortcut(l, "x", FeatureTest.class.getName(), getter, setter); @@ -896,11 +898,12 @@ private void assertToString(String expected, String code) { } private SSource walk(String code) { - ScriptInterface scriptInterface = new ScriptInterface(GenericElasticsearchScript.class); + ScriptInterface scriptInterface = new ScriptInterface(definition, GenericElasticsearchScript.class); CompilerSettings compilerSettings = new CompilerSettings(); compilerSettings.setRegexesEnabled(true); try { - return Walker.buildPainlessTree(scriptInterface, getTestName(), code, compilerSettings, null); + return Walker.buildPainlessTree(scriptInterface, getTestName(), code, compilerSettings, + definition, null); } catch (Exception e) { throw new AssertionError("Failed to compile: " + code, e); }