From e3448283c45e1138c3e9374b4f2be572be9739e4 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 26 Jan 2021 08:46:41 -0500 Subject: [PATCH 1/4] Add typed emit functionality This creates `.emit()` methods on a few primitive things for `keyword` and `date` style runtime fields. Now you can do stuff like: ``` "d": { "type": "date", "script": "'2020-01-18T17:41:34.000Z'.emit()" } ``` We get to piggy back of painless's dynamic dispatch code to regonize that you are in a `date` and trying to emit a `String` so we can "do the right thing" without any extra runtime cost. In this case we just parse the date using the `formatter` on the field. And since the example above doesn't use a formatter we get ISO8601, the date format of kings. Similarly, you can do stuff like: ``` "s": { "type": "keyword", "script": """ for (int i = 0; i < 100; i++) { i.emit(); } """ } ``` Painless *knows* `i` is an `int` and will call an emit method that emits its string value. Also! Assuming we get the syntax proposed in #68088, because this is a chain of method invocations you can do something like: ``` "i": { "type": "long", "script": """ grok('%{NUMBER:i} %{NUMBER:j}').extract(doc['message'].value)?.i?.emit() """ } ``` This should be read as "if grok matches the message and extracts a value for `i` then emit it to the runtime field." If either the grok doesn't match or doesn't extract an `i` value then nothing will be emitted. As an extra nice thing - we'll automatilly convert whatever the `grok` expression outputs into a `long`. All of it handled for us using painless's standard dynamic dispatch code. --- .../annotation/InjectScriptAnnotation.java | 27 ++ .../InjectScriptAnnotationParser.java | 37 ++ .../annotation/WhitelistAnnotationParser.java | 3 +- .../java/org/elasticsearch/painless/Def.java | 29 +- .../elasticsearch/painless/DefBootstrap.java | 4 +- .../elasticsearch/painless/FunctionRef.java | 14 + .../painless/LambdaBootstrap.java | 62 ++- .../elasticsearch/painless/MethodWriter.java | 3 +- .../painless/WriterConstants.java | 18 +- .../painless/ir/LoadScriptNode.java | 45 ++ .../painless/lookup/PainlessLookup.java | 15 +- .../lookup/PainlessLookupBuilder.java | 37 +- .../phase/DefaultIRTreeToASMBytesPhase.java | 31 +- .../phase/DefaultUserTreeToIRTreePhase.java | 417 +++++++++++------- .../painless/phase/IRTreeBaseVisitor.java | 8 +- .../painless/phase/IRTreeVisitor.java | 4 +- .../phase/PainlessUserTreeToIRTreePhase.java | 4 +- .../painless/symbol/IRDecorations.java | 5 + .../painless/AugmentationTests.java | 105 ++++- .../painless/BaseClassTests.java | 12 +- .../org/elasticsearch/painless/Debugger.java | 7 +- .../painless/spi/inject_scripts_whitelist.txt | 12 + .../runtimefields/mapper/DateFieldScript.java | 12 + .../mapper/StringFieldScript.java | 12 + .../runtimefields/mapper/date_whitelist.txt | 10 + .../runtimefields/mapper/string_whitelist.txt | 12 + .../test/runtime_fields/13_keyword_emit.yml | 150 +++++++ .../test/runtime_fields/43_date_emit.yml | 143 ++++++ 28 files changed, 1024 insertions(+), 214 deletions(-) create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotation.java create mode 100644 modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotationParser.java create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadScriptNode.java create mode 100644 modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/inject_scripts_whitelist.txt create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotation.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotation.java new file mode 100644 index 0000000000000..cc12c07d52862 --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotation.java @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi.annotation; + +/** + * Inject the script itself into a method call. Only allowed on augmentations. + */ +public class InjectScriptAnnotation { + public static final String NAME = "inject_script"; +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotationParser.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotationParser.java new file mode 100644 index 0000000000000..10ae94db56678 --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/InjectScriptAnnotationParser.java @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi.annotation; + +import java.util.Map; + +public class InjectScriptAnnotationParser implements WhitelistAnnotationParser { + + public static final InjectScriptAnnotationParser INSTANCE = new InjectScriptAnnotationParser(); + + private InjectScriptAnnotationParser() {} + + @Override + public Object parse(Map arguments) { + if (false == arguments.isEmpty()) { + throw new IllegalArgumentException("[@inject_script] can no have parameters"); + } + return new InjectScriptAnnotation(); + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java index 43acf061e062c..8a60d8f706826 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java @@ -36,7 +36,8 @@ public interface WhitelistAnnotationParser { new AbstractMap.SimpleEntry<>(NoImportAnnotation.NAME, NoImportAnnotationParser.INSTANCE), new AbstractMap.SimpleEntry<>(DeprecatedAnnotation.NAME, DeprecatedAnnotationParser.INSTANCE), new AbstractMap.SimpleEntry<>(NonDeterministicAnnotation.NAME, NonDeterministicAnnotationParser.INSTANCE), - new AbstractMap.SimpleEntry<>(InjectConstantAnnotation.NAME, InjectConstantAnnotationParser.INSTANCE) + new AbstractMap.SimpleEntry<>(InjectConstantAnnotation.NAME, InjectConstantAnnotationParser.INSTANCE), + new AbstractMap.SimpleEntry<>(InjectScriptAnnotation.NAME, InjectScriptAnnotationParser.INSTANCE) ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); 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 f877a7a4c1617..54cf9b4a32f3f 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 @@ -22,6 +22,7 @@ import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; +import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.script.JodaCompatibleZonedDateTime; @@ -196,25 +197,37 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, FunctionTable fu MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, Class receiverClass, String name, Object[] args) throws Throwable { - String recipeString = (String) args[0]; + int argsOffset = 0; + String recipeString = (String) args[argsOffset++]; + boolean injectedScript = ((Integer) args[argsOffset++] > 0); int numArguments = callSiteType.parameterCount(); + int arityDiff = 1; + if (injectedScript) { + arityDiff++; + } // simple case: no lambdas if (recipeString.isEmpty()) { - PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - 1); + PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - arityDiff); if (painlessMethod == null) { throw new IllegalArgumentException("dynamic method " + - "[" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + (numArguments - 1) + "] not found"); + "[" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + (numArguments - arityDiff) + "] not found"); } MethodHandle handle = painlessMethod.methodHandle; - Object[] injections = PainlessLookupUtility.buildInjections(painlessMethod, constants); + Object[] injections = PainlessLookupUtility.buildInjections(painlessMethod, constants); if (injections.length > 0) { - // method handle contains the "this" pointer so start injections at 1 + // method handle contains the receiver pointer so start injections at 1 handle = MethodHandles.insertArguments(handle, 1, injections); } + if (injectedScript && painlessMethod.annotations.containsKey(InjectScriptAnnotation.class) == false) { + // Strip the `Script` parameter if we don't need it + Class typeToInject = painlessLookup.typeOfScriptToInjectForMethod(name, numArguments - arityDiff); + handle = MethodHandles.dropArguments(handle, 1, typeToInject); + } + return handle; } @@ -227,7 +240,7 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, FunctionTable fu // otherwise: first we have to compute the "real" arity. This is because we have extra arguments: // e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i). int arity = callSiteType.parameterCount() - 1; - int upTo = 1; + int upTo = argsOffset; for (int i = 1; i < numArguments; i++) { if (lambdaArgs.get(i - 1)) { String signature = (String) args[upTo++]; @@ -247,14 +260,13 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, FunctionTable fu MethodHandle handle = method.methodHandle; Object[] injections = PainlessLookupUtility.buildInjections(method, constants); - if (injections.length > 0) { // method handle contains the "this" pointer so start injections at 1 handle = MethodHandles.insertArguments(handle, 1, injections); } int replaced = 0; - upTo = 1; + upTo = argsOffset; for (int i = 1; i < numArguments; i++) { // its a functional reference, replace the argument with an impl if (lambdaArgs.get(i - 1)) { @@ -360,6 +372,7 @@ private static MethodHandle lookupReferenceInternal( ref.delegateMethodType, ref.isDelegateInterface ? 1 : 0, ref.isDelegateAugmented ? 1 : 0, + ref.injectScript ? 1 : 0, ref.delegateInjections ); return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray())); 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 f67f8a5334825..b8f825a06eb08 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 @@ -445,7 +445,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, FunctionTable fu switch(flavor) { // "function-call" like things get a polymorphic cache case METHOD_CALL: - if (args.length == 0) { + if (args.length < 2) { throw new BootstrapMethodError("Invalid number of parameters for method call"); } if (args[0] instanceof String == false) { @@ -456,7 +456,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, FunctionTable fu if (numLambdas > type.parameterCount()) { throw new BootstrapMethodError("Illegal recipe for method call: too many bits"); } - if (args.length != numLambdas + 1) { + if (args.length != numLambdas + 2) { throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references"); } return new PIC(painlessLookup, functions, constants, methodHandlesLookup, name, type, initialDepth, flavor, args); 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 ed8402b74a025..98f711ec39043 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 @@ -23,6 +23,7 @@ import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; +import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.painless.symbol.FunctionTable.LocalFunction; @@ -84,6 +85,7 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu String delegateMethodName; MethodType delegateMethodType; Object[] delegateInjections; + boolean injectScript; Class delegateMethodReturnType; List> delegateMethodParameters; @@ -113,6 +115,7 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu delegateMethodName = localFunction.getFunctionName(); delegateMethodType = localFunction.getMethodType(); delegateInjections = new Object[0]; + injectScript = false; delegateMethodReturnType = localFunction.getReturnType(); delegateMethodParameters = localFunction.getTypeParameters(); @@ -136,6 +139,7 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME; delegateMethodType = painlessConstructor.methodType; delegateInjections = new Object[0]; + injectScript = false; delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass(); delegateMethodParameters = painlessConstructor.typeParameters; @@ -177,6 +181,7 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu delegateMethodName = painlessMethod.javaMethod.getName(); delegateMethodType = painlessMethod.methodType; delegateInjections = PainlessLookupUtility.buildInjections(painlessMethod, constants); + injectScript = painlessMethod.annotations.containsKey(InjectScriptAnnotation.class); delegateMethodReturnType = painlessMethod.returnType; @@ -204,9 +209,14 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu delegateMethodType.dropParameterTypes(numberOfCaptures, delegateMethodType.parameterCount())); delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures); + if (injectScript) { + factoryMethodType = factoryMethodType.insertParameterTypes(0, delegateMethodType.parameterType(1)); + } + return new FunctionRef(interfaceMethodName, interfaceMethodType, delegateClassName, isDelegateInterface, isDelegateAugmented, delegateInvokeType, delegateMethodName, delegateMethodType, delegateInjections, + injectScript, factoryMethodType ); } catch (IllegalArgumentException iae) { @@ -236,6 +246,8 @@ public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable fu public final MethodType delegateMethodType; /** injected constants */ public final Object[] delegateInjections; + /** Should the script be injected into the method arguments? */ + public final boolean injectScript; /** factory (CallSite) method signature */ public final MethodType factoryMethodType; @@ -243,6 +255,7 @@ private FunctionRef( String interfaceMethodName, MethodType interfaceMethodType, String delegateClassName, boolean isDelegateInterface, boolean isDelegateAugmented, int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType, Object[] delegateInjections, + boolean injectScript, MethodType factoryMethodType) { this.interfaceMethodName = interfaceMethodName; @@ -254,6 +267,7 @@ private FunctionRef( this.delegateMethodName = delegateMethodName; this.delegateMethodType = delegateMethodType; this.delegateInjections = delegateInjections; + this.injectScript = injectScript; this.factoryMethodType = factoryMethodType; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java index 12a9ccc34bdff..38a020afa734d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java @@ -31,11 +31,11 @@ import java.lang.invoke.LambdaConversionException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.security.AccessController; import java.security.PrivilegedAction; -import static java.lang.invoke.MethodHandles.Lookup; import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION; import static org.elasticsearch.painless.WriterConstants.CTOR_METHOD_NAME; import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE; @@ -210,6 +210,7 @@ public static CallSite lambdaBootstrap( MethodType delegateMethodType, int isDelegateInterface, int isDelegateAugmented, + int injectScript, Object... injections) throws LambdaConversionException { Compiler.Loader loader = (Compiler.Loader)lookup.lookupClass().getClassLoader(); @@ -233,9 +234,22 @@ public static CallSite lambdaBootstrap( delegateInvokeType = H_INVOKESTATIC; } - generateInterfaceMethod(cw, factoryMethodType, lambdaClassType, interfaceMethodName, - interfaceMethodType, delegateClassType, delegateInvokeType, - delegateMethodName, delegateMethodType, isDelegateInterface == 1, isDelegateAugmented == 1, captures, injections); + generateInterfaceMethod( + cw, + factoryMethodType, + lambdaClassType, + interfaceMethodName, + interfaceMethodType, + delegateClassType, + delegateInvokeType, + delegateMethodName, + delegateMethodType, + isDelegateInterface == 1, + isDelegateAugmented == 1, + captures, + injectScript == 1, + injections + ); endLambdaClass(cw); @@ -382,6 +396,7 @@ private static void generateInterfaceMethod( boolean isDelegateInterface, boolean isDelegateAugmented, Capture[] captures, + boolean injectScript, Object... injections) throws LambdaConversionException { @@ -394,15 +409,24 @@ private static void generateInterfaceMethod( iface.visitCode(); // Loads any captured variables onto the stack. - for (int captureCount = 0; captureCount < captures.length; ++captureCount) { + int capturesBeforeArgs = captures.length; + if (injectScript) { + capturesBeforeArgs--; + } + for (int captureCount = 0; captureCount < capturesBeforeArgs; ++captureCount) { iface.loadThis(); - iface.getField( - lambdaClassType, captures[captureCount].name, captures[captureCount].type); + iface.getField(lambdaClassType, captures[captureCount].name, captures[captureCount].type); } // Loads any passed in arguments onto the stack. iface.loadArgs(); + if (injectScript) { + iface.loadThis(); + Capture scriptCapture = captures[captures.length - 1]; + iface.getField(lambdaClassType, scriptCapture.name, scriptCapture.type); + } + // Handles the case for a lambda function or a static reference method. // interfaceMethodType and delegateMethodType both have the captured types // inserted into their type signatures. This later allows the delegate @@ -411,10 +435,24 @@ private static void generateInterfaceMethod( // Example: Integer::parseInt // Example: something.each(x -> x + 1) if (delegateInvokeType == H_INVOKESTATIC) { - interfaceMethodType = - interfaceMethodType.insertParameterTypes(0, factoryMethodType.parameterArray()); - delegateMethodType = - delegateMethodType.insertParameterTypes(0, factoryMethodType.parameterArray()); + Class[] inject = factoryMethodType.parameterArray(); + if (injectScript) { + inject = new Class[inject.length - 1]; + System.arraycopy(factoryMethodType.parameterArray(), 0, inject, 0, inject.length); + Class scriptParam = factoryMethodType.parameterType(inject.length); + interfaceMethodType = interfaceMethodType.insertParameterTypes(1, scriptParam); + } + interfaceMethodType = interfaceMethodType.insertParameterTypes(0, inject); + delegateMethodType = delegateMethodType.insertParameterTypes(0, inject); + if (injectScript) { + /* + * The script needs to be shifted after the receiver. + */ + factoryMethodType = factoryMethodType.dropParameterTypes( + factoryMethodType.parameterCount() - 1, + factoryMethodType.parameterCount() + ); + } } else if (delegateInvokeType == H_INVOKEVIRTUAL || delegateInvokeType == H_INVOKEINTERFACE) { // Handles the case for a virtual or interface reference method with no captures. @@ -482,7 +520,7 @@ private static Class createLambdaClass( byte[] classBytes = cw.toByteArray(); // DEBUG: - // new ClassReader(classBytes).accept(new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG); +// new ClassReader(classBytes).accept(new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG); return AccessController.doPrivileged((PrivilegedAction>)() -> loader.defineLambda(lambdaClassType.getClassName(), classBytes)); } 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 379ce5d9bc6ec..3b47a1dda872a 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 @@ -515,7 +515,7 @@ public void invokeMethodCall(PainlessMethod painlessMethod) { } public void invokeLambdaCall(FunctionRef functionRef) { - Object[] args = new Object[7 + functionRef.delegateInjections.length]; + Object[] args = new Object[8 + functionRef.delegateInjections.length]; args[0] = Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()); args[1] = functionRef.delegateClassName; args[2] = functionRef.delegateInvokeType; @@ -523,6 +523,7 @@ public void invokeLambdaCall(FunctionRef functionRef) { args[4] = Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()); args[5] = functionRef.isDelegateInterface ? 1 : 0; args[6] = functionRef.isDelegateAugmented ? 1 : 0; + args[7] = functionRef.injectScript ? 1 : 0; System.arraycopy(functionRef.delegateInjections, 0, args, 7, functionRef.delegateInjections.length); invokeDynamic( 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 5398579b091df..18bcbf97257fd 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 @@ -132,9 +132,21 @@ public final class WriterConstants { public static final Method DEF_TO_ZONEDDATETIME = getAsmMethod(ZonedDateTime.class, "defToZonedDateTime", Object.class); /** invokedynamic bootstrap for lambda expression/method references */ - public static final MethodType LAMBDA_BOOTSTRAP_TYPE = - MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, - MethodType.class, String.class, int.class, String.class, MethodType.class, int.class, int.class, Object[].class); + public static final MethodType LAMBDA_BOOTSTRAP_TYPE = MethodType.methodType( + CallSite.class, + MethodHandles.Lookup.class, + String.class, + MethodType.class, + MethodType.class, + String.class, + int.class, + String.class, + MethodType.class, + int.class, + int.class, + int.class, + Object[].class + ); public static final Handle LAMBDA_BOOTSTRAP_HANDLE = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(LambdaBootstrap.class), "lambdaBootstrap", LAMBDA_BOOTSTRAP_TYPE.toMethodDescriptorString(), false); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadScriptNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadScriptNode.java new file mode 100644 index 0000000000000..65a574ed6704b --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadScriptNode.java @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.ir; + +import org.elasticsearch.painless.Location; +import org.elasticsearch.painless.phase.IRTreeVisitor; + +public class LoadScriptNode extends ExpressionNode { + + /* ---- begin visitor ---- */ + + @Override + public void visit(IRTreeVisitor irTreeVisitor, Scope scope) { + irTreeVisitor.visitLoadScript(this, scope); + } + + @Override + public void visitChildren(IRTreeVisitor irTreeVisitor, Scope scope) { + // do nothing; terminal node + } + + /* ---- end visitor ---- */ + + public LoadScriptNode(Location location) { + super(location); + } + +} 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 66fb8d2920cdb..98d2663d2a959 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 @@ -19,6 +19,8 @@ package org.elasticsearch.painless.lookup; +import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; + import java.lang.invoke.MethodHandle; import java.util.Map; import java.util.Objects; @@ -40,6 +42,7 @@ public final class PainlessLookup { private final Map painlessMethodKeysToImportedPainlessMethods; private final Map painlessMethodKeysToPainlessClassBindings; private final Map painlessMethodKeysToPainlessInstanceBindings; + private final Map> methodNameToInjectedScriptType; PainlessLookup( Map> javaClassNamesToClasses, @@ -47,7 +50,8 @@ public final class PainlessLookup { Map, PainlessClass> classesToPainlessClasses, Map painlessMethodKeysToImportedPainlessMethods, Map painlessMethodKeysToPainlessClassBindings, - Map painlessMethodKeysToPainlessInstanceBindings) { + Map painlessMethodKeysToPainlessInstanceBindings, + Map> methodNameToInjectedScriptType) { Objects.requireNonNull(javaClassNamesToClasses); Objects.requireNonNull(canonicalClassNamesToClasses); @@ -56,6 +60,7 @@ public final class PainlessLookup { Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods); Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings); Objects.requireNonNull(painlessMethodKeysToPainlessInstanceBindings); + Objects.requireNonNull(methodNameToInjectedScriptType); this.javaClassNamesToClasses = javaClassNamesToClasses; this.canonicalClassNamesToClasses = Map.copyOf(canonicalClassNamesToClasses); @@ -64,6 +69,7 @@ public final class PainlessLookup { this.painlessMethodKeysToImportedPainlessMethods = Map.copyOf(painlessMethodKeysToImportedPainlessMethods); this.painlessMethodKeysToPainlessClassBindings = Map.copyOf(painlessMethodKeysToPainlessClassBindings); this.painlessMethodKeysToPainlessInstanceBindings = Map.copyOf(painlessMethodKeysToPainlessInstanceBindings); + this.methodNameToInjectedScriptType = Map.copyOf(methodNameToInjectedScriptType); } public Class javaClassNameToClass(String javaClassName) { @@ -159,7 +165,6 @@ public PainlessMethod lookupPainlessMethod(Class targetClass, boolean isStati if (targetPainlessClass == null) { return null; } - return isStatic ? targetPainlessClass.staticMethods.get(painlessMethodKey) : targetPainlessClass.methods.get(painlessMethodKey); @@ -233,6 +238,12 @@ public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class targetCla return targetPainlessClass.functionalInterfaceMethod; } + public Class typeOfScriptToInjectForMethod(String methodName, int methodArity) { + Objects.requireNonNull(methodName); + String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity); + return methodNameToInjectedScriptType.get(painlessMethodKey); + } + public PainlessMethod lookupRuntimePainlessMethod(Class originalTargetClass, String methodName, int methodArity) { Objects.requireNonNull(originalTargetClass); Objects.requireNonNull(methodName); 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 517c742b3d6ea..fc2a797463757 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 @@ -31,6 +31,7 @@ import org.elasticsearch.painless.spi.WhitelistInstanceBinding; import org.elasticsearch.painless.spi.WhitelistMethod; import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation; +import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; import org.elasticsearch.painless.spi.annotation.NoImportAnnotation; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; @@ -199,6 +200,7 @@ public static PainlessLookup buildFromWhitelists(List whitelists) { private final Map painlessMethodKeysToImportedPainlessMethods; private final Map painlessMethodKeysToPainlessClassBindings; private final Map painlessMethodKeysToPainlessInstanceBindings; + private final Map> painlessMethodKeyToInjectedScriptType; public PainlessLookupBuilder() { javaClassNamesToClasses = new HashMap<>(); @@ -208,6 +210,7 @@ public PainlessLookupBuilder() { painlessMethodKeysToImportedPainlessMethods = new HashMap<>(); painlessMethodKeysToPainlessClassBindings = new HashMap<>(); painlessMethodKeysToPainlessInstanceBindings = new HashMap<>(); + painlessMethodKeyToInjectedScriptType = new HashMap<>(); } private Class canonicalTypeNameToType(String canonicalTypeName) { @@ -545,6 +548,29 @@ public void addPainlessMethod(Class targetClass, Class augmentedClass, typeParametersSize = typeParameters.size(); } + if (annotations.containsKey(InjectScriptAnnotation.class)) { + if (augmentedClass == null) { + throw new IllegalArgumentException("@" + InjectScriptAnnotation.NAME + " is only supported on augmentations"); + } + Class typeToInject = typeParameters.remove(0); + typeParametersSize = typeParameters.size(); + + String key = buildPainlessMethodKey(methodName, typeParametersSize); + Class oldTypeToInject = painlessMethodKeyToInjectedScriptType.put(key, typeToInject); + if (oldTypeToInject != null && oldTypeToInject != typeToInject) { + throw new IllegalArgumentException( + "Two methods with key [" + + key + + "] @" + + InjectScriptAnnotation.NAME + + " but attempt to inject different types [" + + oldTypeToInject.getName() + + "] vs [" + + typeToInject.getName() + + "]" + ); + } + } if (javaMethod.getReturnType() != typeToJavaType(returnType)) { throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " + @@ -839,6 +865,10 @@ public void addImportedPainlessMethod(Class targetClass, String methodName, C typesToCanonicalTypeNames(typeParameters) + "] must be static"); } + if (annotations.containsKey(InjectScriptAnnotation.class)) { + throw new IllegalArgumentException("@" + InjectScriptAnnotation.NAME + " is only supported on augmentations"); + } + String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize); if (painlessMethodKeysToPainlessClassBindings.containsKey(painlessMethodKey)) { @@ -1042,6 +1072,10 @@ public void addPainlessClassBinding(Class targetClass, String methodName, Cla typesToCanonicalTypeNames(typeParameters) + "]"); } + if (annotations.containsKey(InjectScriptAnnotation.class)) { + throw new IllegalArgumentException("@" + InjectScriptAnnotation.NAME + " is only supported on augmentations"); + } + String painlessMethodKey = buildPainlessMethodKey(methodName, constructorTypeParametersSize + methodTypeParametersSize); if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) { @@ -1242,7 +1276,8 @@ public PainlessLookup build() { classesToPainlessClasses, painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessClassBindings, - painlessMethodKeysToPainlessInstanceBindings); + painlessMethodKeysToPainlessInstanceBindings, + painlessMethodKeyToInjectedScriptType); } private void copyPainlessClassMembers() { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java index 3338d3240ef12..b7d4a1a6d55b5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.ClassWriter; import org.elasticsearch.painless.DefBootstrap; +import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Operation; @@ -72,6 +73,7 @@ import org.elasticsearch.painless.ir.LoadFieldMemberNode; import org.elasticsearch.painless.ir.LoadListShortcutNode; import org.elasticsearch.painless.ir.LoadMapShortcutNode; +import org.elasticsearch.painless.ir.LoadScriptNode; import org.elasticsearch.painless.ir.LoadVariableNode; import org.elasticsearch.painless.ir.MapInitializationNode; import org.elasticsearch.painless.ir.NewArrayNode; @@ -109,6 +111,7 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRCAllEscape; import org.elasticsearch.painless.symbol.IRDecorations.IRCContinuous; import org.elasticsearch.painless.symbol.IRDecorations.IRCInitialize; +import org.elasticsearch.painless.symbol.IRDecorations.IRCInjectedScript; import org.elasticsearch.painless.symbol.IRDecorations.IRCRead; import org.elasticsearch.painless.symbol.IRDecorations.IRCStatic; import org.elasticsearch.painless.symbol.IRDecorations.IRCSynthetic; @@ -123,8 +126,8 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRDConstant; import org.elasticsearch.painless.symbol.IRDecorations.IRDConstructor; import org.elasticsearch.painless.symbol.IRDecorations.IRDDeclarationType; -import org.elasticsearch.painless.symbol.IRDecorations.IRDDepth; import org.elasticsearch.painless.symbol.IRDecorations.IRDDefReferenceEncoding; +import org.elasticsearch.painless.symbol.IRDecorations.IRDDepth; import org.elasticsearch.painless.symbol.IRDecorations.IRDExceptionType; import org.elasticsearch.painless.symbol.IRDecorations.IRDExpressionType; import org.elasticsearch.painless.symbol.IRDecorations.IRDField; @@ -185,6 +188,8 @@ public class DefaultIRTreeToASMBytesPhase implements IRTreeVisitor { + protected FunctionNode currentFunction; + protected void visit(IRNode irNode, WriteScope writeScope) { irNode.visit(this, writeScope); } @@ -312,7 +317,10 @@ public void visitFunction(FunctionNode irFunctionNode, WriteScope writeScope) { methodWriter.visitVarInsn(Opcodes.ISTORE, loop.getSlot()); } + FunctionNode prevFunction = currentFunction; + currentFunction = irFunctionNode; visit(irFunctionNode.getBlockNode(), writeScope.newBlockScope()); + currentFunction = prevFunction; methodWriter.endMethod(); } @@ -1255,7 +1263,11 @@ public void visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInte methodWriter.writeDebugInfo(irTypedInterfaceReferenceNode.getLocation()); List captureNames = irTypedInterfaceReferenceNode.getDecorationValue(IRDCaptureNames.class); - + FunctionRef ref = irTypedInterfaceReferenceNode.getDecorationValue(IRDReference.class); + if (ref.injectScript) { + methodWriter.loadThis(); + } + if (captureNames != null) { for (String captureName : captureNames) { Variable captureVariable = writeScope.getVariable(captureName); @@ -1263,7 +1275,7 @@ public void visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInte } } - methodWriter.invokeLambdaCall(irTypedInterfaceReferenceNode.getDecorationValue(IRDReference.class)); + methodWriter.invokeLambdaCall(ref); } @Override @@ -1379,6 +1391,18 @@ public void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Writ } } + @Override + public void visitLoadScript(LoadScriptNode irLoadThisNode, WriteScope writeScope) { + if (currentFunction.hasCondition(IRCStatic.class)) { + // NOCOMMIT keep function name around? + throw irLoadThisNode.getLocation() + .createError(new IllegalArgumentException("this function is not available in functions or lambdas")); + } + MethodWriter methodWriter = writeScope.getMethodWriter(); + methodWriter.writeDebugInfo(irLoadThisNode.getLocation()); + methodWriter.loadThis(); + } + @Override public void visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, WriteScope writeScope) { MethodWriter methodWriter = writeScope.getMethodWriter(); @@ -1600,6 +1624,7 @@ public void visitInvokeCallDef(InvokeCallDefNode irInvokeCallDefNode, WriteScope irInvokeCallDefNode.getDecorationValue(IRDExpressionType.class)), asmParameterTypes); boostrapArguments.add(0, defCallRecipe.toString()); + boostrapArguments.add(1, irInvokeCallDefNode.hasCondition(IRCInjectedScript.class) ? 1 : 0); methodWriter.invokeDefCall(methodName, methodType, DefBootstrap.METHOD_CALL, boostrapArguments.toArray()); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java index 97dfdb8a1dcd4..bc2c418610d9e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java @@ -24,6 +24,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Operation; +import org.elasticsearch.painless.PainlessScript; import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.ir.BinaryImplNode; import org.elasticsearch.painless.ir.BinaryMathNode; @@ -71,6 +72,7 @@ import org.elasticsearch.painless.ir.LoadFieldMemberNode; import org.elasticsearch.painless.ir.LoadListShortcutNode; import org.elasticsearch.painless.ir.LoadMapShortcutNode; +import org.elasticsearch.painless.ir.LoadScriptNode; import org.elasticsearch.painless.ir.LoadVariableNode; import org.elasticsearch.painless.ir.MapInitializationNode; import org.elasticsearch.painless.ir.NewArrayNode; @@ -155,6 +157,7 @@ import org.elasticsearch.painless.node.SThrow; import org.elasticsearch.painless.node.STry; import org.elasticsearch.painless.node.SWhile; +import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; import org.elasticsearch.painless.symbol.Decorations.AccessDepth; import org.elasticsearch.painless.symbol.Decorations.AllEscape; import org.elasticsearch.painless.symbol.Decorations.BinaryType; @@ -203,6 +206,7 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRCAllEscape; import org.elasticsearch.painless.symbol.IRDecorations.IRCContinuous; import org.elasticsearch.painless.symbol.IRDecorations.IRCInitialize; +import org.elasticsearch.painless.symbol.IRDecorations.IRCInjectedScript; import org.elasticsearch.painless.symbol.IRDecorations.IRCRead; import org.elasticsearch.painless.symbol.IRDecorations.IRCStatic; import org.elasticsearch.painless.symbol.IRDecorations.IRCSynthetic; @@ -217,8 +221,8 @@ import org.elasticsearch.painless.symbol.IRDecorations.IRDConstant; import org.elasticsearch.painless.symbol.IRDecorations.IRDConstructor; import org.elasticsearch.painless.symbol.IRDecorations.IRDDeclarationType; -import org.elasticsearch.painless.symbol.IRDecorations.IRDDepth; import org.elasticsearch.painless.symbol.IRDecorations.IRDDefReferenceEncoding; +import org.elasticsearch.painless.symbol.IRDecorations.IRDDepth; import org.elasticsearch.painless.symbol.IRDecorations.IRDExceptionType; import org.elasticsearch.painless.symbol.IRDecorations.IRDExpressionType; import org.elasticsearch.painless.symbol.IRDecorations.IRDField; @@ -309,10 +313,12 @@ protected void injectBootstrapMethod(ScriptScope scriptScope) { FunctionNode irFunctionNode = new FunctionNode(internalLocation); irFunctionNode.attachDecoration(new IRDName("$bootstrapDef")); irFunctionNode.attachDecoration(new IRDReturnType(CallSite.class)); - irFunctionNode.attachDecoration(new IRDTypeParameters( - Arrays.asList(Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class))); - irFunctionNode.attachDecoration(new IRDParameterNames( - Arrays.asList("methodHandlesLookup", "name", "type", "initialDepth", "flavor", "args"))); + irFunctionNode.attachDecoration( + new IRDTypeParameters(Arrays.asList(Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class)) + ); + irFunctionNode.attachDecoration( + new IRDParameterNames(Arrays.asList("methodHandlesLookup", "name", "type", "initialDepth", "flavor", "args")) + ); irFunctionNode.attachCondition(IRCStatic.class); irFunctionNode.attachCondition(IRCVarArgs.class); irFunctionNode.attachCondition(IRCSynthetic.class); @@ -341,33 +347,37 @@ protected void injectBootstrapMethod(ScriptScope scriptScope) { InvokeCallNode invokeCallNode = new InvokeCallNode(internalLocation); invokeCallNode.attachDecoration(new IRDExpressionType(CallSite.class)); - invokeCallNode.setMethod(new PainlessMethod( - DefBootstrap.class.getMethod("bootstrap", - PainlessLookup.class, - FunctionTable.class, - Map.class, - Lookup.class, - String.class, - MethodType.class, - int.class, - int.class, - Object[].class), - DefBootstrap.class, - CallSite.class, - Arrays.asList( - PainlessLookup.class, - FunctionTable.class, - Map.class, - Lookup.class, - String.class, - MethodType.class, - int.class, - int.class, - Object[].class), - null, - null, - null - ) + invokeCallNode.setMethod( + new PainlessMethod( + DefBootstrap.class.getMethod( + "bootstrap", + PainlessLookup.class, + FunctionTable.class, + Map.class, + Lookup.class, + String.class, + MethodType.class, + int.class, + int.class, + Object[].class + ), + DefBootstrap.class, + CallSite.class, + Arrays.asList( + PainlessLookup.class, + FunctionTable.class, + Map.class, + Lookup.class, + String.class, + MethodType.class, + int.class, + int.class, + Object[].class + ), + null, + null, + null + ) ); invokeCallNode.setBox(DefBootstrap.class); @@ -435,7 +445,7 @@ protected void injectBootstrapMethod(ScriptScope scriptScope) { } protected ExpressionNode injectCast(AExpression userExpressionNode, ScriptScope scriptScope) { - ExpressionNode irExpressionNode = (ExpressionNode)visit(userExpressionNode, scriptScope); + ExpressionNode irExpressionNode = (ExpressionNode) visit(userExpressionNode, scriptScope); if (irExpressionNode == null) { return null; @@ -477,8 +487,15 @@ protected ExpressionNode injectCast(AExpression userExpressionNode, ScriptScope * @param irStoreNode The store node if this is a write. * @return The root node for this assignment. */ - protected ExpressionNode buildLoadStore(int accessDepth, Location location, boolean isNullSafe, - ExpressionNode irPrefixNode, ExpressionNode irIndexNode, ExpressionNode irLoadNode, UnaryNode irStoreNode) { + protected ExpressionNode buildLoadStore( + int accessDepth, + Location location, + boolean isNullSafe, + ExpressionNode irPrefixNode, + ExpressionNode irIndexNode, + ExpressionNode irLoadNode, + UnaryNode irStoreNode + ) { // build out the load structure for load/compound assignment or the store structure for just store ExpressionNode irExpressionNode = irLoadNode != null ? irLoadNode : irStoreNode; @@ -557,7 +574,7 @@ public void visitClass(SClass userClassNode, ScriptScope scriptScope) { irClassNode = new ClassNode(userClassNode.getLocation()); for (SFunction userFunctionNode : userClassNode.getFunctionNodes()) { - irClassNode.addFunctionNode((FunctionNode)visit(userFunctionNode, scriptScope)); + irClassNode.addFunctionNode((FunctionNode) visit(userFunctionNode, scriptScope)); } irClassNode.setScriptScope(scriptScope); @@ -574,7 +591,7 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) { Class returnType = localFunction.getReturnType(); boolean methodEscape = scriptScope.getCondition(userFunctionNode, MethodEscape.class); - BlockNode irBlockNode = (BlockNode)visit(userFunctionNode.getBlockNode(), scriptScope); + BlockNode irBlockNode = (BlockNode) visit(userFunctionNode.getBlockNode(), scriptScope); if (methodEscape == false) { ExpressionNode irExpressionNode; @@ -589,19 +606,19 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) { if (returnType == boolean.class) { irConstantNode.attachDecoration(new IRDConstant(false)); } else if (returnType == byte.class - || returnType == char.class - || returnType == short.class - || returnType == int.class) { - irConstantNode.attachDecoration(new IRDConstant(0)); - } else if (returnType == long.class) { - irConstantNode.attachDecoration(new IRDConstant(0L)); - } else if (returnType == float.class) { - irConstantNode.attachDecoration(new IRDConstant(0f)); - } else if (returnType == double.class) { - irConstantNode.attachDecoration(new IRDConstant(0d)); - } else { - throw userFunctionNode.createError(new IllegalStateException("illegal tree structure")); - } + || returnType == char.class + || returnType == short.class + || returnType == int.class) { + irConstantNode.attachDecoration(new IRDConstant(0)); + } else if (returnType == long.class) { + irConstantNode.attachDecoration(new IRDConstant(0L)); + } else if (returnType == float.class) { + irConstantNode.attachDecoration(new IRDConstant(0f)); + } else if (returnType == double.class) { + irConstantNode.attachDecoration(new IRDConstant(0d)); + } else { + throw userFunctionNode.createError(new IllegalStateException("illegal tree structure")); + } irExpressionNode = irConstantNode; } else { @@ -643,7 +660,7 @@ public void visitBlock(SBlock userBlockNode, ScriptScope scriptScope) { BlockNode irBlockNode = new BlockNode(userBlockNode.getLocation()); for (AStatement userStatementNode : userBlockNode.getStatementNodes()) { - irBlockNode.addStatementNode((StatementNode)visit(userStatementNode, scriptScope)); + irBlockNode.addStatementNode((StatementNode) visit(userStatementNode, scriptScope)); } if (scriptScope.getCondition(userBlockNode, AllEscape.class)) { @@ -657,7 +674,7 @@ public void visitBlock(SBlock userBlockNode, ScriptScope scriptScope) { public void visitIf(SIf userIfNode, ScriptScope scriptScope) { IfNode irIfNode = new IfNode(userIfNode.getLocation()); irIfNode.setConditionNode(injectCast(userIfNode.getConditionNode(), scriptScope)); - irIfNode.setBlockNode((BlockNode)visit(userIfNode.getIfBlockNode(), scriptScope)); + irIfNode.setBlockNode((BlockNode) visit(userIfNode.getIfBlockNode(), scriptScope)); scriptScope.putDecoration(userIfNode, new IRNodeDecoration(irIfNode)); } @@ -666,8 +683,8 @@ public void visitIf(SIf userIfNode, ScriptScope scriptScope) { public void visitIfElse(SIfElse userIfElseNode, ScriptScope scriptScope) { IfElseNode irIfElseNode = new IfElseNode(userIfElseNode.getLocation()); irIfElseNode.setConditionNode(injectCast(userIfElseNode.getConditionNode(), scriptScope)); - irIfElseNode.setBlockNode((BlockNode)visit(userIfElseNode.getIfBlockNode(), scriptScope)); - irIfElseNode.setElseBlockNode((BlockNode)visit(userIfElseNode.getElseBlockNode(), scriptScope)); + irIfElseNode.setBlockNode((BlockNode) visit(userIfElseNode.getIfBlockNode(), scriptScope)); + irIfElseNode.setElseBlockNode((BlockNode) visit(userIfElseNode.getElseBlockNode(), scriptScope)); scriptScope.putDecoration(userIfElseNode, new IRNodeDecoration(irIfElseNode)); } @@ -676,7 +693,7 @@ public void visitIfElse(SIfElse userIfElseNode, ScriptScope scriptScope) { public void visitWhile(SWhile userWhileNode, ScriptScope scriptScope) { WhileLoopNode irWhileLoopNode = new WhileLoopNode(userWhileNode.getLocation()); irWhileLoopNode.setConditionNode(injectCast(userWhileNode.getConditionNode(), scriptScope)); - irWhileLoopNode.setBlockNode((BlockNode)visit(userWhileNode.getBlockNode(), scriptScope)); + irWhileLoopNode.setBlockNode((BlockNode) visit(userWhileNode.getBlockNode(), scriptScope)); if (scriptScope.getCondition(userWhileNode, ContinuousLoop.class)) { irWhileLoopNode.attachCondition(IRCContinuous.class); @@ -689,7 +706,7 @@ public void visitWhile(SWhile userWhileNode, ScriptScope scriptScope) { public void visitDo(SDo userDoNode, ScriptScope scriptScope) { DoWhileLoopNode irDoWhileLoopNode = new DoWhileLoopNode(userDoNode.getLocation()); irDoWhileLoopNode.setConditionNode(injectCast(userDoNode.getConditionNode(), scriptScope)); - irDoWhileLoopNode.setBlockNode((BlockNode)visit(userDoNode.getBlockNode(), scriptScope)); + irDoWhileLoopNode.setBlockNode((BlockNode) visit(userDoNode.getBlockNode(), scriptScope)); if (scriptScope.getCondition(userDoNode, ContinuousLoop.class)) { irDoWhileLoopNode.attachCondition(IRCContinuous.class); @@ -703,8 +720,8 @@ public void visitFor(SFor userForNode, ScriptScope scriptScope) { ForLoopNode irForLoopNode = new ForLoopNode(userForNode.getLocation()); irForLoopNode.setInitialzerNode(visit(userForNode.getInitializerNode(), scriptScope)); irForLoopNode.setConditionNode(injectCast(userForNode.getConditionNode(), scriptScope)); - irForLoopNode.setAfterthoughtNode((ExpressionNode)visit(userForNode.getAfterthoughtNode(), scriptScope)); - irForLoopNode.setBlockNode((BlockNode)visit(userForNode.getBlockNode(), scriptScope)); + irForLoopNode.setAfterthoughtNode((ExpressionNode) visit(userForNode.getAfterthoughtNode(), scriptScope)); + irForLoopNode.setBlockNode((BlockNode) visit(userForNode.getBlockNode(), scriptScope)); if (scriptScope.getCondition(userForNode, ContinuousLoop.class)) { irForLoopNode.attachCondition(IRCContinuous.class); @@ -716,11 +733,12 @@ public void visitFor(SFor userForNode, ScriptScope scriptScope) { @Override public void visitEach(SEach userEachNode, ScriptScope scriptScope) { Variable variable = scriptScope.getDecoration(userEachNode, SemanticVariable.class).getSemanticVariable(); - PainlessCast painlessCast = scriptScope.hasDecoration(userEachNode, ExpressionPainlessCast.class) ? - scriptScope.getDecoration(userEachNode, ExpressionPainlessCast.class).getExpressionPainlessCast() : null; - ExpressionNode irIterableNode = (ExpressionNode)visit(userEachNode.getIterableNode(), scriptScope); + PainlessCast painlessCast = scriptScope.hasDecoration(userEachNode, ExpressionPainlessCast.class) + ? scriptScope.getDecoration(userEachNode, ExpressionPainlessCast.class).getExpressionPainlessCast() + : null; + ExpressionNode irIterableNode = (ExpressionNode) visit(userEachNode.getIterableNode(), scriptScope); Class iterableValueType = scriptScope.getDecoration(userEachNode.getIterableNode(), ValueType.class).getValueType(); - BlockNode irBlockNode = (BlockNode)visit(userEachNode.getBlockNode(), scriptScope); + BlockNode irBlockNode = (BlockNode) visit(userEachNode.getBlockNode(), scriptScope); ConditionNode irConditionNode; @@ -751,8 +769,9 @@ public void visitEach(SEach userEachNode, ScriptScope scriptScope) { irForEachSubIterableNode.attachDecoration(new IRDIterableName("#itr" + userEachNode.getLocation().getOffset())); if (iterableValueType != def.class) { - irForEachSubIterableNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userEachNode, IterablePainlessMethod.class).getIterablePainlessMethod())); + irForEachSubIterableNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userEachNode, IterablePainlessMethod.class).getIterablePainlessMethod()) + ); } if (painlessCast != null) { @@ -775,7 +794,7 @@ public void visitDeclBlock(SDeclBlock userDeclBlockNode, ScriptScope scriptScope DeclarationBlockNode irDeclarationBlockNode = new DeclarationBlockNode(userDeclBlockNode.getLocation()); for (SDeclaration userDeclarationNode : userDeclBlockNode.getDeclarationNodes()) { - irDeclarationBlockNode.addDeclarationNode((DeclarationNode)visit(userDeclarationNode, scriptScope)); + irDeclarationBlockNode.addDeclarationNode((DeclarationNode) visit(userDeclarationNode, scriptScope)); } scriptScope.putDecoration(userDeclBlockNode, new IRNodeDecoration(irDeclarationBlockNode)); @@ -824,10 +843,10 @@ public void visitTry(STry userTryNode, ScriptScope scriptScope) { TryNode irTryNode = new TryNode(userTryNode.getLocation()); for (SCatch userCatchNode : userTryNode.getCatchNodes()) { - irTryNode.addCatchNode((CatchNode)visit(userCatchNode, scriptScope)); + irTryNode.addCatchNode((CatchNode) visit(userCatchNode, scriptScope)); } - irTryNode.setBlockNode((BlockNode)visit(userTryNode.getBlockNode(), scriptScope)); + irTryNode.setBlockNode((BlockNode) visit(userTryNode.getBlockNode(), scriptScope)); scriptScope.putDecoration(userTryNode, new IRNodeDecoration(irTryNode)); } @@ -839,7 +858,7 @@ public void visitCatch(SCatch userCatchNode, ScriptScope scriptScope) { CatchNode irCatchNode = new CatchNode(userCatchNode.getLocation()); irCatchNode.attachDecoration(new IRDExceptionType(variable.getType())); irCatchNode.attachDecoration(new IRDSymbol(variable.getName())); - irCatchNode.setBlockNode((BlockNode)visit(userCatchNode.getBlockNode(), scriptScope)); + irCatchNode.setBlockNode((BlockNode) visit(userCatchNode.getBlockNode(), scriptScope)); scriptScope.putDecoration(userCatchNode, new IRNodeDecoration(irCatchNode)); } @@ -869,8 +888,9 @@ public void visitBreak(SBreak userBreakNode, ScriptScope scriptScope) { @Override public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptScope) { boolean read = scriptScope.getCondition(userAssignmentNode, Read.class); - Class compoundType = scriptScope.hasDecoration(userAssignmentNode, CompoundType.class) ? - scriptScope.getDecoration(userAssignmentNode, CompoundType.class).getCompoundType() : null; + Class compoundType = scriptScope.hasDecoration(userAssignmentNode, CompoundType.class) + ? scriptScope.getDecoration(userAssignmentNode, CompoundType.class).getCompoundType() + : null; ExpressionNode irAssignmentNode; // add a cast node if necessary for the value node for the assignment @@ -880,7 +900,7 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc if (compoundType != null) { boolean concatenate = userAssignmentNode.getOperation() == Operation.ADD && compoundType == String.class; scriptScope.setCondition(userAssignmentNode.getLeftNode(), Compound.class); - UnaryNode irStoreNode = (UnaryNode)visit(userAssignmentNode.getLeftNode(), scriptScope); + UnaryNode irStoreNode = (UnaryNode) visit(userAssignmentNode.getLeftNode(), scriptScope); ExpressionNode irLoadNode = irStoreNode.getChildNode(); ExpressionNode irCompoundNode; @@ -892,9 +912,9 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc // must handle the StringBuilder case for java version <= 8 if (irLoadNode instanceof BinaryImplNode && WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) { - ((DupNode)((BinaryImplNode)irLoadNode).getLeftNode()).attachDecoration(new IRDDepth(1)); + ((DupNode) ((BinaryImplNode) irLoadNode).getLeftNode()).attachDecoration(new IRDDepth(1)); } - // handles when the operation is mathematical + // handles when the operation is mathematical } else { BinaryMathNode irBinaryMathNode = new BinaryMathNode(irStoreNode.getLocation()); irBinaryMathNode.setLeftNode(irLoadNode); @@ -906,14 +926,15 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc irCompoundNode = irBinaryMathNode; } - PainlessCast downcast = scriptScope.hasDecoration(userAssignmentNode, DowncastPainlessCast.class) ? - scriptScope.getDecoration(userAssignmentNode, DowncastPainlessCast.class).getDowncastPainlessCast() : null; + PainlessCast downcast = scriptScope.hasDecoration(userAssignmentNode, DowncastPainlessCast.class) + ? scriptScope.getDecoration(userAssignmentNode, DowncastPainlessCast.class).getDowncastPainlessCast() + : null; // no need to downcast so the binary math node is the value for the store node if (downcast == null) { irCompoundNode.attachDecoration(new IRDExpressionType(irStoreNode.getDecorationValue(IRDStoreType.class))); irStoreNode.setChildNode(irCompoundNode); - // add a cast node to do a downcast as the value for the store node + // add a cast node to do a downcast as the value for the store node } else { CastNode irCastNode = new CastNode(irCompoundNode.getLocation()); irCastNode.attachDecoration(new IRDExpressionType(downcast.targetType)); @@ -936,7 +957,7 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc irDupNode.attachDecoration(new IRDDepth(accessDepth)); irDupNode.setChildNode(irLoadNode); irLoadNode = irDupNode; - // the value is read from after the assignment (pre-increment/compound) + // the value is read from after the assignment (pre-increment/compound) } else { int size = MethodWriter.getType(irStoreNode.getDecorationValue(IRDExpressionType.class)).getSize(); irDupNode = new DupNode(irStoreNode.getLocation()); @@ -948,8 +969,9 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc } } - PainlessCast upcast = scriptScope.hasDecoration(userAssignmentNode, UpcastPainlessCast.class) ? - scriptScope.getDecoration(userAssignmentNode, UpcastPainlessCast.class).getUpcastPainlessCast() : null; + PainlessCast upcast = scriptScope.hasDecoration(userAssignmentNode, UpcastPainlessCast.class) + ? scriptScope.getDecoration(userAssignmentNode, UpcastPainlessCast.class).getUpcastPainlessCast() + : null; // upcast the stored value if necessary if (upcast != null) { @@ -961,19 +983,19 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc } if (concatenate) { - StringConcatenationNode irStringConcatenationNode = (StringConcatenationNode)irCompoundNode; + StringConcatenationNode irStringConcatenationNode = (StringConcatenationNode) irCompoundNode; irStringConcatenationNode.addArgumentNode(irLoadNode); irStringConcatenationNode.addArgumentNode(irValueNode); } else { - BinaryMathNode irBinaryMathNode = (BinaryMathNode)irCompoundNode; + BinaryMathNode irBinaryMathNode = (BinaryMathNode) irCompoundNode; irBinaryMathNode.setLeftNode(irLoadNode); irBinaryMathNode.setRightNode(irValueNode); } irAssignmentNode = irStoreNode; - // handles a standard assignment + // handles a standard assignment } else { - irAssignmentNode = (ExpressionNode)visit(userAssignmentNode.getLeftNode(), scriptScope); + irAssignmentNode = (ExpressionNode) visit(userAssignmentNode.getLeftNode(), scriptScope); // the value is read from after the assignment if (read) { @@ -989,9 +1011,9 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc } if (irAssignmentNode instanceof BinaryImplNode) { - ((UnaryNode)((BinaryImplNode)irAssignmentNode).getRightNode()).setChildNode(irValueNode); + ((UnaryNode) ((BinaryImplNode) irAssignmentNode).getRightNode()).setChildNode(irValueNode); } else { - ((UnaryNode)irAssignmentNode).setChildNode(irValueNode); + ((UnaryNode) irAssignmentNode).setChildNode(irValueNode); } } @@ -1000,8 +1022,9 @@ public void visitAssignment(EAssignment userAssignmentNode, ScriptScope scriptSc @Override public void visitUnary(EUnary userUnaryNode, ScriptScope scriptScope) { - Class unaryType = scriptScope.hasDecoration(userUnaryNode, UnaryType.class) ? - scriptScope.getDecoration(userUnaryNode, UnaryType.class).getUnaryType() : null; + Class unaryType = scriptScope.hasDecoration(userUnaryNode, UnaryType.class) + ? scriptScope.getDecoration(userUnaryNode, UnaryType.class).getUnaryType() + : null; IRNode irNode; @@ -1010,7 +1033,8 @@ public void visitUnary(EUnary userUnaryNode, ScriptScope scriptScope) { } else { UnaryMathNode irUnaryMathNode = new UnaryMathNode(userUnaryNode.getLocation()); irUnaryMathNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userUnaryNode, ValueType.class).getValueType())); + new IRDExpressionType(scriptScope.getDecoration(userUnaryNode, ValueType.class).getValueType()) + ); if (unaryType != null) { irUnaryMathNode.attachDecoration(new IRDUnaryType(unaryType)); @@ -1038,13 +1062,14 @@ public void visitBinary(EBinary userBinaryNode, ScriptScope scriptScope) { if (operation == Operation.ADD && valueType == String.class) { StringConcatenationNode stringConcatenationNode = new StringConcatenationNode(userBinaryNode.getLocation()); - stringConcatenationNode.addArgumentNode((ExpressionNode)visit(userBinaryNode.getLeftNode(), scriptScope)); - stringConcatenationNode.addArgumentNode((ExpressionNode)visit(userBinaryNode.getRightNode(), scriptScope)); + stringConcatenationNode.addArgumentNode((ExpressionNode) visit(userBinaryNode.getLeftNode(), scriptScope)); + stringConcatenationNode.addArgumentNode((ExpressionNode) visit(userBinaryNode.getRightNode(), scriptScope)); irExpressionNode = stringConcatenationNode; } else { Class binaryType = scriptScope.getDecoration(userBinaryNode, BinaryType.class).getBinaryType(); - Class shiftType = scriptScope.hasDecoration(userBinaryNode, ShiftType.class) ? - scriptScope.getDecoration(userBinaryNode, ShiftType.class).getShiftType() : null; + Class shiftType = scriptScope.hasDecoration(userBinaryNode, ShiftType.class) + ? scriptScope.getDecoration(userBinaryNode, ShiftType.class).getShiftType() + : null; BinaryMathNode irBinaryMathNode = new BinaryMathNode(userBinaryNode.getLocation()); @@ -1091,7 +1116,8 @@ public void visitComp(EComp userCompNode, ScriptScope scriptScope) { ComparisonNode irComparisonNode = new ComparisonNode(userCompNode.getLocation()); irComparisonNode.attachDecoration(new IRDExpressionType(scriptScope.getDecoration(userCompNode, ValueType.class).getValueType())); irComparisonNode.attachDecoration( - new IRDComparisonType(scriptScope.getDecoration(userCompNode, ComparisonType.class).getComparisonType())); + new IRDComparisonType(scriptScope.getDecoration(userCompNode, ComparisonType.class).getComparisonType()) + ); irComparisonNode.attachDecoration(new IRDOperation(userCompNode.getOperation())); irComparisonNode.setLeftNode(injectCast(userCompNode.getLeftNode(), scriptScope)); irComparisonNode.setRightNode(injectCast(userCompNode.getRightNode(), scriptScope)); @@ -1112,7 +1138,7 @@ public void visitInstanceof(EInstanceof userInstanceofNode, ScriptScope scriptSc InstanceofNode irInstanceofNode = new InstanceofNode(userInstanceofNode.getLocation()); irInstanceofNode.attachDecoration(new IRDExpressionType(valuetype)); irInstanceofNode.attachDecoration(new IRDInstanceType(instanceType)); - irInstanceofNode.setChildNode((ExpressionNode)visit(userInstanceofNode.getExpressionNode(), scriptScope)); + irInstanceofNode.setChildNode((ExpressionNode) visit(userInstanceofNode.getExpressionNode(), scriptScope)); scriptScope.putDecoration(userInstanceofNode, new IRNodeDecoration(irInstanceofNode)); } @@ -1121,7 +1147,8 @@ public void visitInstanceof(EInstanceof userInstanceofNode, ScriptScope scriptSc public void visitConditional(EConditional userConditionalNode, ScriptScope scriptScope) { ConditionalNode irConditionalNode = new ConditionalNode(userConditionalNode.getLocation()); irConditionalNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userConditionalNode, ValueType.class).getValueType())); + new IRDExpressionType(scriptScope.getDecoration(userConditionalNode, ValueType.class).getValueType()) + ); irConditionalNode.setConditionNode(injectCast(userConditionalNode.getConditionNode(), scriptScope)); irConditionalNode.setLeftNode(injectCast(userConditionalNode.getTrueNode(), scriptScope)); irConditionalNode.setRightNode(injectCast(userConditionalNode.getFalseNode(), scriptScope)); @@ -1144,11 +1171,16 @@ public void visitListInit(EListInit userListInitNode, ScriptScope scriptScope) { ListInitializationNode irListInitializationNode = new ListInitializationNode(userListInitNode.getLocation()); irListInitializationNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userListInitNode, ValueType.class).getValueType())); - irListInitializationNode.attachDecoration(new IRDConstructor( - scriptScope.getDecoration(userListInitNode, StandardPainlessConstructor.class).getStandardPainlessConstructor())); - irListInitializationNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userListInitNode, StandardPainlessMethod.class).getStandardPainlessMethod())); + new IRDExpressionType(scriptScope.getDecoration(userListInitNode, ValueType.class).getValueType()) + ); + irListInitializationNode.attachDecoration( + new IRDConstructor( + scriptScope.getDecoration(userListInitNode, StandardPainlessConstructor.class).getStandardPainlessConstructor() + ) + ); + irListInitializationNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userListInitNode, StandardPainlessMethod.class).getStandardPainlessMethod()) + ); for (AExpression userValueNode : userListInitNode.getValueNodes()) { irListInitializationNode.addArgumentNode(injectCast(userValueNode, scriptScope)); @@ -1162,17 +1194,22 @@ public void visitMapInit(EMapInit userMapInitNode, ScriptScope scriptScope) { MapInitializationNode irMapInitializationNode = new MapInitializationNode(userMapInitNode.getLocation()); irMapInitializationNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userMapInitNode, ValueType.class).getValueType())); - irMapInitializationNode.attachDecoration(new IRDConstructor( - scriptScope.getDecoration(userMapInitNode, StandardPainlessConstructor.class).getStandardPainlessConstructor())); - irMapInitializationNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userMapInitNode, StandardPainlessMethod.class).getStandardPainlessMethod())); - + new IRDExpressionType(scriptScope.getDecoration(userMapInitNode, ValueType.class).getValueType()) + ); + irMapInitializationNode.attachDecoration( + new IRDConstructor( + scriptScope.getDecoration(userMapInitNode, StandardPainlessConstructor.class).getStandardPainlessConstructor() + ) + ); + irMapInitializationNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userMapInitNode, StandardPainlessMethod.class).getStandardPainlessMethod()) + ); for (int i = 0; i < userMapInitNode.getKeyNodes().size(); ++i) { irMapInitializationNode.addArgumentNode( - injectCast(userMapInitNode.getKeyNodes().get(i), scriptScope), - injectCast(userMapInitNode.getValueNodes().get(i), scriptScope)); + injectCast(userMapInitNode.getKeyNodes().get(i), scriptScope), + injectCast(userMapInitNode.getValueNodes().get(i), scriptScope) + ); } scriptScope.putDecoration(userMapInitNode, new IRNodeDecoration(irMapInitializationNode)); @@ -1185,7 +1222,7 @@ public void visitNewArray(ENewArray userNewArrayNode, ScriptScope scriptScope) { irNewArrayNode.attachDecoration(new IRDExpressionType(scriptScope.getDecoration(userNewArrayNode, ValueType.class).getValueType())); if (userNewArrayNode.isInitializer()) { - irNewArrayNode.attachCondition(IRCInitialize.class); + irNewArrayNode.attachCondition(IRCInitialize.class); } for (AExpression userArgumentNode : userNewArrayNode.getValueNodes()) { @@ -1198,8 +1235,8 @@ public void visitNewArray(ENewArray userNewArrayNode, ScriptScope scriptScope) { @Override public void visitNewObj(ENewObj userNewObjectNode, ScriptScope scriptScope) { Class valueType = scriptScope.getDecoration(userNewObjectNode, ValueType.class).getValueType(); - PainlessConstructor painlessConstructor = - scriptScope.getDecoration(userNewObjectNode, StandardPainlessConstructor.class).getStandardPainlessConstructor(); + PainlessConstructor painlessConstructor = scriptScope.getDecoration(userNewObjectNode, StandardPainlessConstructor.class) + .getStandardPainlessConstructor(); NewObjectNode irNewObjectNode = new NewObjectNode(userNewObjectNode.getLocation()); irNewObjectNode.attachDecoration(new IRDExpressionType(valueType)); @@ -1224,12 +1261,12 @@ public void visitCallLocal(ECallLocal callLocalNode, ScriptScope scriptScope) { LocalFunction localFunction = scriptScope.getDecoration(callLocalNode, StandardLocalFunction.class).getLocalFunction(); irInvokeCallMemberNode.attachDecoration(new IRDFunction(localFunction)); } else if (scriptScope.hasDecoration(callLocalNode, StandardPainlessMethod.class)) { - PainlessMethod importedMethod = - scriptScope.getDecoration(callLocalNode, StandardPainlessMethod.class).getStandardPainlessMethod(); + PainlessMethod importedMethod = scriptScope.getDecoration(callLocalNode, StandardPainlessMethod.class) + .getStandardPainlessMethod(); irInvokeCallMemberNode.attachDecoration(new IRDMethod(importedMethod)); } else if (scriptScope.hasDecoration(callLocalNode, StandardPainlessClassBinding.class)) { - PainlessClassBinding painlessClassBinding = - scriptScope.getDecoration(callLocalNode, StandardPainlessClassBinding.class).getPainlessClassBinding(); + PainlessClassBinding painlessClassBinding = scriptScope.getDecoration(callLocalNode, StandardPainlessClassBinding.class) + .getPainlessClassBinding(); String bindingName = scriptScope.getNextSyntheticName("class_binding"); FieldNode irFieldNode = new FieldNode(callLocalNode.getLocation()); @@ -1240,14 +1277,16 @@ public void visitCallLocal(ECallLocal callLocalNode, ScriptScope scriptScope) { irInvokeCallMemberNode.attachDecoration(new IRDClassBinding(painlessClassBinding)); - if ((int)scriptScope.getDecoration(callLocalNode, StandardConstant.class).getStandardConstant() == 0) { + if ((int) scriptScope.getDecoration(callLocalNode, StandardConstant.class).getStandardConstant() == 0) { irInvokeCallMemberNode.attachCondition(IRCStatic.class); } irInvokeCallMemberNode.attachDecoration(new IRDName(bindingName)); } else if (scriptScope.hasDecoration(callLocalNode, StandardPainlessInstanceBinding.class)) { - PainlessInstanceBinding painlessInstanceBinding = - scriptScope.getDecoration(callLocalNode, StandardPainlessInstanceBinding.class).getPainlessInstanceBinding(); + PainlessInstanceBinding painlessInstanceBinding = scriptScope.getDecoration( + callLocalNode, + StandardPainlessInstanceBinding.class + ).getPainlessInstanceBinding(); String bindingName = scriptScope.getNextSyntheticName("instance_binding"); FieldNode irFieldNode = new FieldNode(callLocalNode.getLocation()); @@ -1368,15 +1407,16 @@ public void visitRegex(ERegex userRegexNode, ScriptScope scriptScope) { InvokeCallNode invokeCallNode = new InvokeCallNode(userRegexNode.getLocation()); invokeCallNode.attachDecoration(new IRDExpressionType(Pattern.class)); invokeCallNode.setBox(Pattern.class); - invokeCallNode.setMethod(new PainlessMethod( - Pattern.class.getMethod("compile", String.class, int.class), - Pattern.class, - Pattern.class, - Arrays.asList(String.class, int.class), - null, - null, - null - ) + invokeCallNode.setMethod( + new PainlessMethod( + Pattern.class.getMethod("compile", String.class, int.class), + Pattern.class, + Pattern.class, + Arrays.asList(String.class, int.class), + null, + null, + null + ) ); irBinaryImplNode.setRightNode(invokeCallNode); @@ -1390,7 +1430,8 @@ public void visitRegex(ERegex userRegexNode, ScriptScope scriptScope) { irConstantNode = new ConstantNode(userRegexNode.getLocation()); irConstantNode.attachDecoration(new IRDExpressionType(int.class)); irConstantNode.attachDecoration( - new IRDConstant(scriptScope.getDecoration(userRegexNode, StandardConstant.class).getStandardConstant())); + new IRDConstant(scriptScope.getDecoration(userRegexNode, StandardConstant.class).getStandardConstant()) + ); invokeCallNode.addArgumentNode(irConstantNode); } catch (Exception exception) { @@ -1411,24 +1452,28 @@ public void visitLambda(ELambda userLambdaNode, ScriptScope scriptScope) { if (scriptScope.hasDecoration(userLambdaNode, TargetType.class)) { TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode(userLambdaNode.getLocation()); - typedInterfaceReferenceNode.attachDecoration(new IRDReference( - scriptScope.getDecoration(userLambdaNode, ReferenceDecoration.class).getReference())); + typedInterfaceReferenceNode.attachDecoration( + new IRDReference(scriptScope.getDecoration(userLambdaNode, ReferenceDecoration.class).getReference()) + ); irExpressionNode = typedInterfaceReferenceNode; } else { DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode(userLambdaNode.getLocation()); - defInterfaceReferenceNode.attachDecoration(new IRDDefReferenceEncoding( - scriptScope.getDecoration(userLambdaNode, EncodingDecoration.class).getEncoding())); + defInterfaceReferenceNode.attachDecoration( + new IRDDefReferenceEncoding(scriptScope.getDecoration(userLambdaNode, EncodingDecoration.class).getEncoding()) + ); irExpressionNode = defInterfaceReferenceNode; } FunctionNode irFunctionNode = new FunctionNode(userLambdaNode.getLocation()); - irFunctionNode.setBlockNode((BlockNode)visit(userLambdaNode.getBlockNode(), scriptScope)); + irFunctionNode.setBlockNode((BlockNode) visit(userLambdaNode.getBlockNode(), scriptScope)); irFunctionNode.attachDecoration(new IRDName(scriptScope.getDecoration(userLambdaNode, MethodNameDecoration.class).getMethodName())); irFunctionNode.attachDecoration(new IRDReturnType(scriptScope.getDecoration(userLambdaNode, ReturnType.class).getReturnType())); - irFunctionNode.attachDecoration(new IRDTypeParameters( - new ArrayList<>(scriptScope.getDecoration(userLambdaNode, TypeParameters.class).getTypeParameters()))); - irFunctionNode.attachDecoration(new IRDParameterNames( - new ArrayList<>(scriptScope.getDecoration(userLambdaNode, ParameterNames.class).getParameterNames()))); + irFunctionNode.attachDecoration( + new IRDTypeParameters(new ArrayList<>(scriptScope.getDecoration(userLambdaNode, TypeParameters.class).getTypeParameters())) + ); + irFunctionNode.attachDecoration( + new IRDParameterNames(new ArrayList<>(scriptScope.getDecoration(userLambdaNode, ParameterNames.class).getParameterNames())) + ); irFunctionNode.attachCondition(IRCStatic.class); irFunctionNode.attachCondition(IRCSynthetic.class); irFunctionNode.attachDecoration(new IRDMaxLoopCounter(scriptScope.getCompilerSettings().getMaxLoopCounter())); @@ -1475,11 +1520,13 @@ public void visitFunctionRef(EFunctionRef userFunctionRefNode, ScriptScope scrip } irReferenceNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userFunctionRefNode, ValueType.class).getValueType())); + new IRDExpressionType(scriptScope.getDecoration(userFunctionRefNode, ValueType.class).getValueType()) + ); if (capturesDecoration != null) { - irReferenceNode.attachDecoration(new IRDCaptureNames( - Collections.singletonList(capturesDecoration.getCaptures().get(0).getName()))); + irReferenceNode.attachDecoration( + new IRDCaptureNames(Collections.singletonList(capturesDecoration.getCaptures().get(0).getName())) + ); } scriptScope.putDecoration(userFunctionRefNode, new IRNodeDecoration(irReferenceNode)); @@ -1490,8 +1537,9 @@ public void visitNewArrayFunctionRef(ENewArrayFunctionRef userNewArrayFunctionRe ExpressionNode irReferenceNode; if (scriptScope.hasDecoration(userNewArrayFunctionRefNode, TargetType.class)) { - TypedInterfaceReferenceNode typedInterfaceReferenceNode = - new TypedInterfaceReferenceNode(userNewArrayFunctionRefNode.getLocation()); + TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode( + userNewArrayFunctionRefNode.getLocation() + ); FunctionRef reference = scriptScope.getDecoration(userNewArrayFunctionRefNode, ReferenceDecoration.class).getReference(); typedInterfaceReferenceNode.attachDecoration(new IRDReference(reference)); irReferenceNode = typedInterfaceReferenceNode; @@ -1520,8 +1568,9 @@ public void visitNewArrayFunctionRef(ENewArrayFunctionRef userNewArrayFunctionRe irBlockNode.addStatementNode(irReturnNode); FunctionNode irFunctionNode = new FunctionNode(userNewArrayFunctionRefNode.getLocation()); - irFunctionNode.attachDecoration(new IRDName( - scriptScope.getDecoration(userNewArrayFunctionRefNode, MethodNameDecoration.class).getMethodName())); + irFunctionNode.attachDecoration( + new IRDName(scriptScope.getDecoration(userNewArrayFunctionRefNode, MethodNameDecoration.class).getMethodName()) + ); irFunctionNode.attachDecoration(new IRDReturnType(returnType)); irFunctionNode.attachDecoration(new IRDTypeParameters(Collections.singletonList(int.class))); irFunctionNode.attachDecoration(new IRDParameterNames(Collections.singletonList("size"))); @@ -1533,7 +1582,8 @@ public void visitNewArrayFunctionRef(ENewArrayFunctionRef userNewArrayFunctionRe irClassNode.addFunctionNode(irFunctionNode); irReferenceNode.attachDecoration( - new IRDExpressionType(scriptScope.getDecoration(userNewArrayFunctionRefNode, ValueType.class).getValueType())); + new IRDExpressionType(scriptScope.getDecoration(userNewArrayFunctionRefNode, ValueType.class).getValueType()) + ); scriptScope.putDecoration(userNewArrayFunctionRefNode, new IRNodeDecoration(irReferenceNode)); } @@ -1608,7 +1658,7 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { Class valueType = scriptScope.getDecoration(userDotNode, ValueType.class).getValueType(); ValueType prefixValueType = scriptScope.getDecoration(userDotNode.getPrefixNode(), ValueType.class); - ExpressionNode irPrefixNode = (ExpressionNode)visit(userDotNode.getPrefixNode(), scriptScope); + ExpressionNode irPrefixNode = (ExpressionNode) visit(userDotNode.getPrefixNode(), scriptScope); ExpressionNode irIndexNode = null; UnaryNode irStoreNode = null; ExpressionNode irLoadNode = null; @@ -1638,8 +1688,8 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { accessDepth = 1; } else if (scriptScope.hasDecoration(userDotNode, StandardPainlessField.class)) { - PainlessField painlessField = - scriptScope.getDecoration(userDotNode, StandardPainlessField.class).getStandardPainlessField(); + PainlessField painlessField = scriptScope.getDecoration(userDotNode, StandardPainlessField.class) + .getStandardPainlessField(); if (write || compound) { StoreDotNode irStoreDotNode = new StoreDotNode(location); @@ -1662,16 +1712,18 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { StoreDotShortcutNode irStoreDotShortcutNode = new StoreDotShortcutNode(location); irStoreDotShortcutNode.attachDecoration(new IRDExpressionType(read ? valueType : void.class)); irStoreDotShortcutNode.attachDecoration(new IRDStoreType(valueType)); - irStoreDotShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod())); + irStoreDotShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod()) + ); irStoreNode = irStoreDotShortcutNode; } if (write == false || compound) { LoadDotShortcutNode irLoadDotShortcutNode = new LoadDotShortcutNode(location); irLoadDotShortcutNode.attachDecoration(new IRDExpressionType(valueType)); - irLoadDotShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod())); + irLoadDotShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod()) + ); irLoadNode = irLoadDotShortcutNode; } @@ -1686,16 +1738,18 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { StoreMapShortcutNode irStoreMapShortcutNode = new StoreMapShortcutNode(location); irStoreMapShortcutNode.attachDecoration(new IRDExpressionType(read ? valueType : void.class)); irStoreMapShortcutNode.attachDecoration(new IRDStoreType(valueType)); - irStoreMapShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod())); + irStoreMapShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod()) + ); irStoreNode = irStoreMapShortcutNode; } if (write == false || compound) { LoadMapShortcutNode irLoadMapShortcutNode = new LoadMapShortcutNode(location); irLoadMapShortcutNode.attachDecoration(new IRDExpressionType(valueType)); - irLoadMapShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod())); + irLoadMapShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod()) + ); irLoadNode = irLoadMapShortcutNode; } @@ -1704,23 +1758,26 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { ConstantNode irConstantNode = new ConstantNode(location); irConstantNode.attachDecoration(new IRDExpressionType(int.class)); irConstantNode.attachDecoration( - new IRDConstant(scriptScope.getDecoration(userDotNode, StandardConstant.class).getStandardConstant())); + new IRDConstant(scriptScope.getDecoration(userDotNode, StandardConstant.class).getStandardConstant()) + ); irIndexNode = irConstantNode; if (write || compound) { StoreListShortcutNode irStoreListShortcutNode = new StoreListShortcutNode(location); irStoreListShortcutNode.attachDecoration(new IRDExpressionType(read ? valueType : void.class)); irStoreListShortcutNode.attachDecoration(new IRDStoreType(valueType)); - irStoreListShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod())); + irStoreListShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, SetterPainlessMethod.class).getSetterPainlessMethod()) + ); irStoreNode = irStoreListShortcutNode; } if (write == false || compound) { LoadListShortcutNode irLoadListShortcutNode = new LoadListShortcutNode(location); irLoadListShortcutNode.attachDecoration(new IRDExpressionType(valueType)); - irLoadListShortcutNode.attachDecoration(new IRDMethod( - scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod())); + irLoadListShortcutNode.attachDecoration( + new IRDMethod(scriptScope.getDecoration(userDotNode, GetterPainlessMethod.class).getGetterPainlessMethod()) + ); irLoadNode = irLoadListShortcutNode; } @@ -1731,7 +1788,14 @@ public void visitDot(EDot userDotNode, ScriptScope scriptScope) { scriptScope.putDecoration(userDotNode, new AccessDepth(accessDepth)); irExpressionNode = buildLoadStore( - accessDepth, location, userDotNode.isNullSafe(), irPrefixNode, irIndexNode, irLoadNode, irStoreNode); + accessDepth, + location, + userDotNode.isNullSafe(), + irPrefixNode, + irIndexNode, + irLoadNode, + irStoreNode + ); } scriptScope.putDecoration(userDotNode, new IRNodeDecoration(irExpressionNode)); @@ -1750,7 +1814,7 @@ public void visitBrace(EBrace userBraceNode, ScriptScope scriptScope) { Class valueType = scriptScope.getDecoration(userBraceNode, ValueType.class).getValueType(); Class prefixValueType = scriptScope.getDecoration(userBraceNode.getPrefixNode(), ValueType.class).getValueType(); - ExpressionNode irPrefixNode = (ExpressionNode)visit(userBraceNode.getPrefixNode(), scriptScope); + ExpressionNode irPrefixNode = (ExpressionNode) visit(userBraceNode.getPrefixNode(), scriptScope); ExpressionNode irIndexNode = injectCast(userBraceNode.getIndexNode(), scriptScope); UnaryNode irStoreNode = null; ExpressionNode irLoadNode = null; @@ -1839,8 +1903,10 @@ public void visitBrace(EBrace userBraceNode, ScriptScope scriptScope) { scriptScope.putDecoration(userBraceNode, new AccessDepth(2)); - scriptScope.putDecoration(userBraceNode, new IRNodeDecoration( - buildLoadStore(2, location, false, irPrefixNode, irIndexNode, irLoadNode, irStoreNode))); + scriptScope.putDecoration( + userBraceNode, + new IRNodeDecoration(buildLoadStore(2, location, false, irPrefixNode, irIndexNode, irLoadNode, irStoreNode)) + ); } @Override @@ -1854,11 +1920,19 @@ public void visitCall(ECall userCallNode, ScriptScope scriptScope) { InvokeCallDefNode irCallSubDefNode = new InvokeCallDefNode(userCallNode.getLocation()); for (AExpression userArgumentNode : userCallNode.getArgumentNodes()) { - irCallSubDefNode.addArgumentNode((ExpressionNode)visit(userArgumentNode, scriptScope)); + irCallSubDefNode.addArgumentNode((ExpressionNode) visit(userArgumentNode, scriptScope)); } irCallSubDefNode.attachDecoration(new IRDExpressionType(valueType)); irCallSubDefNode.attachDecoration(new IRDName(userCallNode.getMethodName())); + Class scriptTypeToInject = scriptScope.getPainlessLookup() + .typeOfScriptToInjectForMethod(userCallNode.getMethodName(), userCallNode.getArgumentNodes().size()); + if (scriptTypeToInject != null) { + irCallSubDefNode.attachCondition(IRCInjectedScript.class); + LoadScriptNode loadScript = new LoadScriptNode(userCallNode.getLocation()); + loadScript.attachDecoration(new IRDExpressionType(scriptTypeToInject)); + irCallSubDefNode.getArgumentNodes().add(0, loadScript); + } irExpressionNode = irCallSubDefNode; } else { Class boxType; @@ -1875,6 +1949,15 @@ public void visitCall(ECall userCallNode, ScriptScope scriptScope) { Class[] parameterTypes = method.javaMethod.getParameterTypes(); int augmentedOffset = method.javaMethod.getDeclaringClass() == method.targetClass ? 0 : 1; + InjectScriptAnnotation injectScript = (InjectScriptAnnotation) method.annotations.get(InjectScriptAnnotation.class); + if (injectScript != null) { + augmentedOffset++; + LoadScriptNode loadScript = new LoadScriptNode(userCallNode.getLocation()); + Class scriptTypeToInject = scriptScope.getPainlessLookup() + .typeOfScriptToInjectForMethod(userCallNode.getMethodName(), userCallNode.getArgumentNodes().size()); + loadScript.attachDecoration(new IRDExpressionType(scriptTypeToInject)); + irInvokeCallNode.addArgumentNode(loadScript); + } for (int i = 0; i < injections.length; i++) { Object injection = injections[i]; Class parameterType = parameterTypes[i + augmentedOffset]; @@ -1907,7 +1990,7 @@ public void visitCall(ECall userCallNode, ScriptScope scriptScope) { } BinaryImplNode irBinaryImplNode = new BinaryImplNode(irExpressionNode.getLocation()); - irBinaryImplNode.setLeftNode((ExpressionNode)visit(userCallNode.getPrefixNode(), scriptScope)); + irBinaryImplNode.setLeftNode((ExpressionNode) visit(userCallNode.getPrefixNode(), scriptScope)); irBinaryImplNode.setRightNode(irExpressionNode); irBinaryImplNode.attachDecoration(irExpressionNode.getDecoration(IRDExpressionType.class)); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java index 4dc894f993c65..5c93af38b7b97 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java @@ -19,8 +19,8 @@ package org.elasticsearch.painless.phase; -import org.elasticsearch.painless.ir.BinaryMathNode; import org.elasticsearch.painless.ir.BinaryImplNode; +import org.elasticsearch.painless.ir.BinaryMathNode; import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.BooleanNode; import org.elasticsearch.painless.ir.BreakNode; @@ -62,6 +62,7 @@ import org.elasticsearch.painless.ir.LoadFieldMemberNode; import org.elasticsearch.painless.ir.LoadListShortcutNode; import org.elasticsearch.painless.ir.LoadMapShortcutNode; +import org.elasticsearch.painless.ir.LoadScriptNode; import org.elasticsearch.painless.ir.LoadVariableNode; import org.elasticsearch.painless.ir.MapInitializationNode; import org.elasticsearch.painless.ir.NewArrayNode; @@ -335,6 +336,11 @@ public void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Scop irLoadMapShortcutNode.visitChildren(this, scope); } + @Override + public void visitLoadScript(LoadScriptNode irLoadThisNode, Scope scope) { + irLoadThisNode.visitChildren(this, scope); + } + @Override public void visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Scope scope) { irLoadFieldMemberNode.visitChildren(this, scope); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java index 35eb82c055472..eaf8b9ff02069 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java @@ -19,8 +19,8 @@ package org.elasticsearch.painless.phase; -import org.elasticsearch.painless.ir.BinaryMathNode; import org.elasticsearch.painless.ir.BinaryImplNode; +import org.elasticsearch.painless.ir.BinaryMathNode; import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.BooleanNode; import org.elasticsearch.painless.ir.BreakNode; @@ -62,6 +62,7 @@ import org.elasticsearch.painless.ir.LoadFieldMemberNode; import org.elasticsearch.painless.ir.LoadListShortcutNode; import org.elasticsearch.painless.ir.LoadMapShortcutNode; +import org.elasticsearch.painless.ir.LoadScriptNode; import org.elasticsearch.painless.ir.LoadVariableNode; import org.elasticsearch.painless.ir.MapInitializationNode; import org.elasticsearch.painless.ir.NewArrayNode; @@ -141,6 +142,7 @@ public interface IRTreeVisitor { void visitLoadDotShortcut(LoadDotShortcutNode irDotSubShortcutNode, Scope scope); void visitLoadListShortcut(LoadListShortcutNode irLoadListShortcutNode, Scope scope); void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Scope scope); + void visitLoadScript(LoadScriptNode irLoadThisNode, Scope scope); void visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Scope scope); void visitLoadBraceDef(LoadBraceDefNode irLoadBraceDefNode, Scope scope); void visitLoadBrace(LoadBraceNode irLoadBraceNode, Scope scope); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/PainlessUserTreeToIRTreePhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/PainlessUserTreeToIRTreePhase.java index 0b7edf44beeab..8a8919270691c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/PainlessUserTreeToIRTreePhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/PainlessUserTreeToIRTreePhase.java @@ -93,8 +93,8 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) { // the main "execute" block with several exceptions. if ("execute".equals(functionName)) { ScriptClassInfo scriptClassInfo = scriptScope.getScriptClassInfo(); - LocalFunction localFunction = - scriptScope.getFunctionTable().getFunction(functionName, scriptClassInfo.getExecuteArguments().size()); + LocalFunction localFunction = scriptScope.getFunctionTable() + .getFunction(functionName, scriptClassInfo.getExecuteArguments().size()); Class returnType = localFunction.getReturnType(); boolean methodEscape = scriptScope.getCondition(userFunctionNode, MethodEscape.class); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java index bb9c4caff8fd6..18df71f9b7e0a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/IRDecorations.java @@ -466,4 +466,9 @@ public IRDRegexLimit(Integer value) { super(value); } } + + /** Marked on def call if it injected the script into the parameters. */ + public static class IRCInjectedScript implements IRCondition { + private IRCInjectedScript() {} + } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java index cbf4349e66b64..6da5f93c5d216 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java @@ -31,15 +31,22 @@ import java.util.Map; import java.util.regex.Pattern; +import static org.hamcrest.Matchers.equalTo; + public class AugmentationTests extends ScriptTestCase { @Override protected Map, List> scriptContexts() { Map, List> contexts = super.scriptContexts(); + List digestWhitelist = new ArrayList<>(Whitelist.BASE_WHITELISTS); digestWhitelist.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.ingest.txt")); contexts.put(DigestTestScript.CONTEXT, digestWhitelist); + List injectScriptWhitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + injectScriptWhitelists.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "inject_scripts_whitelist.txt")); + contexts.put(InjectScriptTestScript.CONTEXT, injectScriptWhitelists); + return contexts; } @@ -51,7 +58,36 @@ public interface Factory { } public static final ScriptContext CONTEXT = - new ScriptContext<>("test", DigestTestScript.Factory.class); + new ScriptContext<>("digest", DigestTestScript.Factory.class); + } + + public abstract static class InjectScriptTestScript { + public static final String[] PARAMETERS = {}; + + public interface Factory { + InjectScriptTestScript newInstance(int a); + } + + private final int a; + + public InjectScriptTestScript(int a) { + this.a = a; + } + + public abstract int execute(); + + public static int zap(Integer receiver, InjectScriptTestScript script) { + return receiver * script.a; + } + + public static double zap(Double receiver) { + return receiver * 2; + } + + public static final ScriptContext CONTEXT = new ScriptContext<>( + "inject_script", + InjectScriptTestScript.Factory.class + ); } public void testStatic() { @@ -290,4 +326,71 @@ public void testToEpochMilli() { assertEquals(0L, exec("ZonedDateTime.parse('1970-01-01T00:00:00Z').toEpochMilli()")); assertEquals(1602097376782L, exec("ZonedDateTime.parse('2020-10-07T19:02:56.782Z').toEpochMilli()")); } + + public int execInjectScript(String script, int a) { + return scriptEngine.compile( + "digest_test", + script, + InjectScriptTestScript.CONTEXT, Collections.emptyMap() + ).newInstance(a).execute(); + } + + public void testInjectScriptCompiles() { + assertEquals(1, execInjectScript("1", randomInt())); + } + + public void testInjectScriptManualBoxed() { + int a = randomInt(); + assertEquals(a, execInjectScript("Integer.valueOf(1).zap()", a)); + } + + public void testInjectScriptAutoBoxed() { + int a = randomInt(); + assertEquals(a, execInjectScript("1.zap()", a)); + } + + public void testInjectScriptInFunction() { + int a = randomInt(); + Exception e = expectScriptThrows( + IllegalArgumentException.class, + () -> execInjectScript("int f() {1.zap()} f()", a) + ); + assertThat(e.getMessage(), equalTo("this function is not available in functions or lambdas")); + } + + public void testInjectScriptLambda() { + int a = randomInt(); + Exception e = expectScriptThrows( + IllegalArgumentException.class, + () -> execInjectScript("IntStream.of(new int[] {1}).map(a -> a.zap()).sum()", a) + ); + assertThat(e.getMessage(), equalTo("this function is not available in functions or lambdas")); + } + + public void testInjectScriptRef() { + int a = randomInt(); + assertEquals(a, execInjectScript("IntStream.of(new int[] {1}).map(Integer::zap).sum()", a)); + } + + public void testInjectScriptDef() { + int a = randomInt(); + assertEquals(a, execInjectScript("def z = 1; z.zap()", a)); + } + + /** + * Test for a call on {@code def} that has the same key as a method + * with {@code @inject_script} but that doesn't *itself* inject the script. + */ + public void testDefCallNotUsingInjectScript() { + assertEquals(2, execInjectScript("def z = 1.0; (int) z.zap()", randomInt())); + } + + public void testInjectScriptDefLambda() { + int a = randomInt(); + Exception e = expectScriptThrows( + IllegalArgumentException.class, + () -> execInjectScript("Stream.of(new def[] {1, 2}).map(a -> a.zap()).findFirst().get()", a) + ); + assertThat(e.getMessage(), equalTo("this function is not available in functions or lambdas")); + } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java index b43e6afcaf68b..63d19d30c5546 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java @@ -131,7 +131,7 @@ public void testNoArgs() throws Exception { scriptEngine.compile("testNoArgs3", "_score", NoArgs.CONTEXT, emptyMap())); assertEquals("cannot resolve symbol [_score]", e.getMessage()); - String debug = Debugger.toString(NoArgs.class, "int i = 0", new CompilerSettings()); + String debug = Debugger.toString(NoArgs.class, "int i = 0", new CompilerSettings(), Whitelist.BASE_WHITELISTS); assertThat(debug, containsString("ACONST_NULL")); assertThat(debug, containsString("ARETURN")); } @@ -317,7 +317,7 @@ public void testReturnsVoid() throws Exception { scriptEngine.compile("testReturnsVoid1", "map.remove('a')", ReturnsVoid.CONTEXT, emptyMap()).newInstance().execute(map); assertEquals(emptyMap(), map); - String debug = Debugger.toString(ReturnsVoid.class, "int i = 0", new CompilerSettings()); + String debug = Debugger.toString(ReturnsVoid.class, "int i = 0", new CompilerSettings(), Whitelist.BASE_WHITELISTS); // The important thing is that this contains the opcode for returning void assertThat(debug, containsString(" RETURN")); // We shouldn't contain any weird "default to null" logic @@ -358,7 +358,7 @@ public void testReturnsPrimitiveBoolean() throws Exception { scriptEngine.compile("testReturnsPrimitiveBoolean6", "true || false", ReturnsPrimitiveBoolean.CONTEXT, emptyMap()) .newInstance().execute()); - String debug = Debugger.toString(ReturnsPrimitiveBoolean.class, "false", new CompilerSettings()); + String debug = Debugger.toString(ReturnsPrimitiveBoolean.class, "false", new CompilerSettings(), Whitelist.BASE_WHITELISTS); assertThat(debug, containsString("ICONST_0")); // The important thing here is that we have the bytecode for returning an integer instead of an object. booleans are integers. assertThat(debug, containsString("IRETURN")); @@ -426,7 +426,7 @@ public void testReturnsPrimitiveInt() throws Exception { assertEquals(2, scriptEngine.compile("testReturnsPrimitiveInt7", "1 + 1", ReturnsPrimitiveInt.CONTEXT, emptyMap()).newInstance().execute()); - String debug = Debugger.toString(ReturnsPrimitiveInt.class, "1", new CompilerSettings()); + String debug = Debugger.toString(ReturnsPrimitiveInt.class, "1", new CompilerSettings(), Whitelist.BASE_WHITELISTS); assertThat(debug, containsString("ICONST_1")); // The important thing here is that we have the bytecode for returning an integer instead of an object assertThat(debug, containsString("IRETURN")); @@ -493,7 +493,7 @@ public void testReturnsPrimitiveFloat() throws Exception { "testReturnsPrimitiveFloat7", "def d = Double.valueOf(1.1); d", ReturnsPrimitiveFloat.CONTEXT, emptyMap()) .newInstance().execute()); - String debug = Debugger.toString(ReturnsPrimitiveFloat.class, "1f", new CompilerSettings()); + String debug = Debugger.toString(ReturnsPrimitiveFloat.class, "1f", new CompilerSettings(), Whitelist.BASE_WHITELISTS); assertThat(debug, containsString("FCONST_1")); // The important thing here is that we have the bytecode for returning a float instead of an object assertThat(debug, containsString("FRETURN")); @@ -556,7 +556,7 @@ public void testReturnsPrimitiveDouble() throws Exception { scriptEngine.compile("testReturnsPrimitiveDouble12", "1.1 + 6.7", ReturnsPrimitiveDouble.CONTEXT, emptyMap()) .newInstance().execute(), 0); - String debug = Debugger.toString(ReturnsPrimitiveDouble.class, "1", new CompilerSettings()); + String debug = Debugger.toString(ReturnsPrimitiveDouble.class, "1", new CompilerSettings(), Whitelist.BASE_WHITELISTS); // The important thing here is that we have the bytecode for returning a double instead of an object assertThat(debug, containsString("DRETURN")); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java index a81bd5f97ea76..76bfd2542d146 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java @@ -26,22 +26,23 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.util.List; /** quick and dirty tools for debugging */ final class Debugger { /** compiles source to bytecode, and returns debugging output */ static String toString(final String source) { - return toString(PainlessTestScript.class, source, new CompilerSettings()); + return toString(PainlessTestScript.class, source, new CompilerSettings(), Whitelist.BASE_WHITELISTS); } /** compiles to bytecode, and returns debugging output */ - static String toString(Class iface, String source, CompilerSettings settings) { + static String toString(Class iface, String source, CompilerSettings settings, List whitelists) { StringWriter output = new StringWriter(); PrintWriter outputWriter = new PrintWriter(output); Textifier textifier = new Textifier(); try { - new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS)) + new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(whitelists)) .compile("", source, settings, textifier); } catch (RuntimeException e) { textifier.print(outputWriter); diff --git a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/inject_scripts_whitelist.txt b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/inject_scripts_whitelist.txt new file mode 100644 index 0000000000000..fce7b3342c75c --- /dev/null +++ b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/inject_scripts_whitelist.txt @@ -0,0 +1,12 @@ +# whitelist for augmentation tests of the inject_script annotation + +class org.elasticsearch.painless.AugmentationTests$InjectScriptTestScript @no_import { +} + +class java.lang.Integer { + int org.elasticsearch.painless.AugmentationTests$InjectScriptTestScript zap(org.elasticsearch.painless.AugmentationTests.InjectScriptTestScript) @inject_script +} + +class java.lang.Double { + double org.elasticsearch.painless.AugmentationTests$InjectScriptTestScript zap() +} diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java index cb891b95286f8..5a59c1e53cf92 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java @@ -95,4 +95,16 @@ public long parse(Object str) { return script.formatter.parseMillis(str.toString()); } } + + public static void emit(Integer receiver, DateFieldScript script) { + script.emit(receiver); + } + + public static void emit(Long receiver, DateFieldScript script) { + script.emit(receiver); + } + + public static void emit(String receiver, DateFieldScript script) { + script.emit(script.formatter.parseMillis(receiver)); + } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/StringFieldScript.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/StringFieldScript.java index a73a5e5c4415a..67a445db51a14 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/StringFieldScript.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/StringFieldScript.java @@ -106,4 +106,16 @@ public void emit(String v) { script.emit(v); } } + + public static void emit(Boolean receiver, StringFieldScript script) { + script.emit(receiver.toString()); + } + + public static void emit(Number receiver, StringFieldScript script) { + script.emit(receiver.toString()); + } + + public static void emit(String receiver, StringFieldScript script) { + script.emit(receiver); + } } diff --git a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt index 650e5ad5c419b..032a349a1d469 100644 --- a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt +++ b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt @@ -20,4 +20,14 @@ static_import { } +class java.lang.Integer { + void org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript) @inject_script +} + +class java.lang.Long { + void org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript) @inject_script +} +class java.lang.String { + void org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript) @inject_script +} diff --git a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/string_whitelist.txt b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/string_whitelist.txt index d4ea8794be1b3..4460e4e211c02 100644 --- a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/string_whitelist.txt +++ b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/string_whitelist.txt @@ -16,3 +16,15 @@ static_import { # The `emit` callback to collect values for the field void emit(org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript, String) bound_to org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript$Emit } + +class java.lang.Boolean { + void org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript) @inject_script +} + +class java.lang.Number { + void org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript) @inject_script +} + +class java.lang.String { + void org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript) @inject_script +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml new file mode 100644 index 0000000000000..1e578b0f2e8e2 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml @@ -0,0 +1,150 @@ +--- +setup: + - do: + indices.create: + index: sensor + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + timestamp: + type: date + temperature: + type: long + voltage: + type: double + node: + type: keyword + + - do: + bulk: + index: sensor + refresh: true + body: | + {"index":{}} + {"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"} + {"index":{}} + {"timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"} + {"index":{}} + {"timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"} + {"index":{}} + {"timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"} + {"index":{}} + {"timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"} + {"index":{}} + {"timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"} + +--- +"boolean.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.over_limit: + type: keyword + script: | + (doc['voltage'].value > 4.1).emit() + fields: + - voltage.over_limit + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.over_limit: ['false']} + - match: {hits.hits.1.fields.voltage\.over_limit: ['true']} + +--- +"float.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.rounded: + type: keyword + script: | + float v = (float) doc['voltage'].value; + v.emit() + fields: + - voltage.rounded + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.rounded: ['4.0']} + - match: {hits.hits.1.fields.voltage\.rounded: ['4.2']} + +--- +"double.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.rounded: + type: keyword + script: | + double v = doc['voltage'].value; + v.emit() + fields: + - voltage.rounded + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.rounded: ['4.0']} + - match: {hits.hits.1.fields.voltage\.rounded: ['4.2']} + +--- +"int.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.rounded: + type: keyword + script: | + int v = (int) doc['voltage'].value; + v.emit() + fields: + - voltage.rounded + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.rounded: ['4']} + - match: {hits.hits.1.fields.voltage\.rounded: ['4']} + +--- +"long.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.rounded: + type: keyword + script: | + long v = ((long) doc['voltage'].value).emit(); + v.emit() + fields: + - voltage.rounded + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.rounded: ['4']} + - match: {hits.hits.1.fields.voltage\.rounded: ['4']} + +--- +"string.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.with_units: + type: keyword + script: | + String withUnits = doc['voltage'].value + 'V'; + withUnits.emit() + fields: + - voltage.with_units + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.with_units: ['4.0V']} + - match: {hits.hits.1.fields.voltage\.with_units: ['4.2V']} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml new file mode 100644 index 0000000000000..15c753565b20a --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml @@ -0,0 +1,143 @@ +--- +setup: + - do: + indices.create: + index: sensor + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + timestamp: + type: date + temperature: + type: long + voltage: + type: double + node: + type: keyword + + - do: + bulk: + index: sensor + refresh: true + body: | + {"index":{}} + {"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"} + {"index":{}} + {"timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"} + {"index":{}} + {"timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"} + {"index":{}} + {"timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"} + {"index":{}} + {"timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"} + {"index":{}} + {"timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"} + +--- +"boolean.emit": + - do: + catch: /member method \[boolean, emit/0\] not found/ + search: + index: sensor + body: + runtime_mappings: + voltage.busted: + type: date + script: | + (doc['voltage'].value > 4.1).emit() + fields: + - voltage.busted + sort: timestamp + +--- +"float.emit": + - do: + catch: /member method \[float, emit/0\] not found/ + search: + index: sensor + body: + runtime_mappings: + voltage.busted: + type: date + script: | + float v = (float) doc['voltage'].value; + v.emit() + fields: + - voltage.busted + sort: timestamp + +--- +"double.emit": + - do: + catch: /member method \[double, emit/0\] not found/ + search: + index: sensor + body: + runtime_mappings: + voltage.busted: + type: date + script: | + double v = doc['voltage'].value; + v.emit() + fields: + - voltage.busted + sort: timestamp + +--- +"int.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.as_millis_since_epoch: + type: date + script: | + int v = (int) doc['voltage'].value; + v.emit() + fields: + - voltage.as_millis_since_epoch + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.as_millis_since_epoch: ['1970-01-01T00:00:00.004Z']} + - match: {hits.hits.1.fields.voltage\.as_millis_since_epoch: ['1970-01-01T00:00:00.004Z']} + +--- +"long.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.as_millis_since_epoch: + type: date + script: | + long v = (long) doc['voltage'].value; + v.emit() + fields: + - voltage.as_millis_since_epoch + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.as_millis_since_epoch: ['1970-01-01T00:00:00.004Z']} + - match: {hits.hits.1.fields.voltage\.as_millis_since_epoch: ['1970-01-01T00:00:00.004Z']} + +--- +"string.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + voltage.hack_year: + type: date + script: | + doc['timestamp'].value.toString().replace('2018', '2020').emit() + fields: + - voltage.hack_year + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.voltage\.hack_year: ['2020-01-18T17:41:34.000Z']} + - match: {hits.hits.1.fields.voltage\.hack_year: ['2020-01-19T17:41:34.000Z']} From 24bdf4edb62fde1c25b8a4a975fbe7f03533d863 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 29 Jan 2021 13:43:34 -0500 Subject: [PATCH 2/4] ITer --- .../elasticsearch/painless/MethodWriter.java | 5 +- .../painless/lookup/PainlessLookup.java | 2 - .../phase/DefaultIRTreeToASMBytesPhase.java | 4 +- .../phase/DefaultUserTreeToIRTreePhase.java | 1 - .../painless/DefBootstrapTests.java | 73 +++++++++++++++++-- .../spi/def_bootstrap_tests_whitelist.txt | 10 +++ .../test/runtime_fields/13_keyword_emit.yml | 2 +- 7 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/def_bootstrap_tests_whitelist.txt 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 3b47a1dda872a..d6420b158457d 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 @@ -515,7 +515,8 @@ public void invokeMethodCall(PainlessMethod painlessMethod) { } public void invokeLambdaCall(FunctionRef functionRef) { - Object[] args = new Object[8 + functionRef.delegateInjections.length]; + int fixedArgs = 8; + Object[] args = new Object[fixedArgs + functionRef.delegateInjections.length]; args[0] = Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()); args[1] = functionRef.delegateClassName; args[2] = functionRef.delegateInvokeType; @@ -524,7 +525,7 @@ public void invokeLambdaCall(FunctionRef functionRef) { args[5] = functionRef.isDelegateInterface ? 1 : 0; args[6] = functionRef.isDelegateAugmented ? 1 : 0; args[7] = functionRef.injectScript ? 1 : 0; - System.arraycopy(functionRef.delegateInjections, 0, args, 7, functionRef.delegateInjections.length); + System.arraycopy(functionRef.delegateInjections, 0, args, fixedArgs, functionRef.delegateInjections.length); invokeDynamic( functionRef.interfaceMethodName, 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 98d2663d2a959..cfdfebd7926ec 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 @@ -19,8 +19,6 @@ package org.elasticsearch.painless.lookup; -import org.elasticsearch.painless.spi.annotation.InjectScriptAnnotation; - import java.lang.invoke.MethodHandle; import java.util.Map; import java.util.Objects; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java index b7d4a1a6d55b5..d6a09a0b5daa5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java @@ -1262,12 +1262,12 @@ public void visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInte MethodWriter methodWriter = writeScope.getMethodWriter(); methodWriter.writeDebugInfo(irTypedInterfaceReferenceNode.getLocation()); - List captureNames = irTypedInterfaceReferenceNode.getDecorationValue(IRDCaptureNames.class); FunctionRef ref = irTypedInterfaceReferenceNode.getDecorationValue(IRDReference.class); if (ref.injectScript) { methodWriter.loadThis(); } + List captureNames = irTypedInterfaceReferenceNode.getDecorationValue(IRDCaptureNames.class); if (captureNames != null) { for (String captureName : captureNames) { Variable captureVariable = writeScope.getVariable(captureName); @@ -1394,7 +1394,7 @@ public void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Writ @Override public void visitLoadScript(LoadScriptNode irLoadThisNode, WriteScope writeScope) { if (currentFunction.hasCondition(IRCStatic.class)) { - // NOCOMMIT keep function name around? + // TODO we'd love a better error message here, but honestly we'd love to make this work more throw irLoadThisNode.getLocation() .createError(new IllegalArgumentException("this function is not available in functions or lambdas")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java index bc2c418610d9e..51dca9b6fdc11 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java @@ -24,7 +24,6 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.Operation; -import org.elasticsearch.painless.PainlessScript; import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.ir.BinaryImplNode; import org.elasticsearch.painless.ir.BinaryMathNode; 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 a640e2b5c6a57..707fff92cc0d9 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 @@ -22,6 +22,7 @@ import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupBuilder; import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.test.ESTestCase; @@ -29,12 +30,22 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; public class DefBootstrapTests extends ESTestCase { - private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS); + private final PainlessLookup painlessLookup; + + public DefBootstrapTests() { + List whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + whitelists.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "def_bootstrap_tests_whitelist.txt")); + painlessLookup = PainlessLookupBuilder.buildFromWhitelists(whitelists); + } /** calls toString() on integers, twice */ public void testOneType() throws Throwable { @@ -46,7 +57,9 @@ public void testOneType() throws Throwable { MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, - ""); + "", // No recipe + 0 // Did not inject the script + ); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -68,7 +81,9 @@ public void testTwoTypes() throws Throwable { MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, - ""); + "", // No recipe + 0 // Did not inject the script + ); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -95,7 +110,9 @@ public void testTooManyTypes() throws Throwable { MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, - ""); + "", // No recipe + 0 // Did not inject the script + ); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); @@ -123,7 +140,9 @@ public void testMegamorphic() throws Throwable { MethodType.methodType(int.class, Object.class), 0, DefBootstrap.METHOD_CALL, - ""); + "", // No recipe + 0 // Did not inject the script + ); site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (int)handle.invokeExact((Object) Arrays.asList("1", "2"))); @@ -245,6 +264,50 @@ public void testNoNullGuardAddWhenCached() throws Throwable { }); } + public void testInjectScript() throws Throwable { + CallSite site = DefBootstrap.bootstrap(painlessLookup, + new FunctionTable(), + Collections.emptyMap(), + MethodHandles.publicLookup(), + "zap", + MethodType.methodType(Object.class, Object.class, TestScript.class), + 0, + DefBootstrap.METHOD_CALL, + "", // No recipe + 1 // Injected the script + ); + MethodHandle handle = site.dynamicInvoker(); + assertDepthEquals(site, 0); + + // Needed the script injected + assertThat(handle.invokeExact((Object) 5, new TestScript(7)), equalTo(35)); + assertDepthEquals(site, 1); + + // Did not need the script + assertThat(handle.invokeExact((Object) 5.5, new TestScript(randomInt())), equalTo(11)); + assertDepthEquals(site, 2); + + // In fact it'll take null + assertThat(handle.invokeExact((Object) 5.5, (TestScript) null), equalTo(11)); + assertDepthEquals(site, 2); // And, of course, it still caches the call site + } + + public static int zap(Integer receiver, TestScript script) { + return receiver * script.constant; + } + + public static int zap(Double receiver) { + return (int) (receiver * 2); + } + + public static class TestScript { + private final int constant; + + public TestScript(int constant) { + this.constant = constant; + } + } + static void assertDepthEquals(CallSite site, int expected) { DefBootstrap.PIC dsite = (DefBootstrap.PIC) site; assertEquals(expected, dsite.depth); diff --git a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/def_bootstrap_tests_whitelist.txt b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/def_bootstrap_tests_whitelist.txt new file mode 100644 index 0000000000000..416dc4d441c6b --- /dev/null +++ b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/def_bootstrap_tests_whitelist.txt @@ -0,0 +1,10 @@ +class org.elasticsearch.painless.DefBootstrapTests$TestScript @no_import { +} + +class java.lang.Integer { + int org.elasticsearch.painless.DefBootstrapTests zap(org.elasticsearch.painless.DefBootstrapTests.TestScript) @inject_script +} + +class java.lang.Double { + int org.elasticsearch.painless.DefBootstrapTests zap() +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml index 1e578b0f2e8e2..0c5a145211b0b 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml @@ -121,7 +121,7 @@ setup: voltage.rounded: type: keyword script: | - long v = ((long) doc['voltage'].value).emit(); + long v = (long) doc['voltage'].value; v.emit() fields: - voltage.rounded From efe2abd6ef30243f82359aa15aee30257b0c8522 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 29 Jan 2021 14:53:28 -0500 Subject: [PATCH 3/4] TemporalAccessor.emit --- .../runtimefields/mapper/DateFieldScript.java | 19 +++++++++ .../runtimefields/mapper/date_whitelist.txt | 4 ++ .../mapper/DateFieldScriptTests.java | 10 +++++ .../test/runtime_fields/13_keyword_emit.yml | 2 +- .../test/runtime_fields/43_date_emit.yml | 41 ++++++++++++++++--- 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java index 5a59c1e53cf92..ccecae24843cb 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScript.java @@ -14,6 +14,9 @@ import org.elasticsearch.script.ScriptFactory; import org.elasticsearch.search.lookup.SearchLookup; +import java.time.Instant; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; import java.util.List; import java.util.Map; import java.util.Objects; @@ -107,4 +110,20 @@ public static void emit(Long receiver, DateFieldScript script) { public static void emit(String receiver, DateFieldScript script) { script.emit(script.formatter.parseMillis(receiver)); } + + public static void emit(TemporalAccessor receiver, DateFieldScript script) { + long millis = millis(receiver); + assert millis == Instant.from(receiver).toEpochMilli(); + script.emit(millis); + } + + /** + * Extract millis since epoch from a {@link TemporalAccessor}. + *

+ * Should return the same thing as calling {@code Instant.from(accessor).toEpochMilli()} + * but without quite as much ceremony. + */ + static long millis(TemporalAccessor accessor) { + return accessor.getLong(ChronoField.INSTANT_SECONDS) * 1000 + accessor.get(ChronoField.NANO_OF_SECOND) / 1_000_000; + } } diff --git a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt index 032a349a1d469..98138aa50e918 100644 --- a/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt +++ b/x-pack/plugin/runtime-fields/src/main/resources/org/elasticsearch/xpack/runtimefields/mapper/date_whitelist.txt @@ -31,3 +31,7 @@ class java.lang.Long { class java.lang.String { void org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript) @inject_script } + +class java.time.temporal.TemporalAccessor { + void org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript emit(org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript) @inject_script +} diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScriptTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScriptTests.java index 1d78f91075866..3f285f4eef17e 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScriptTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/DateFieldScriptTests.java @@ -12,10 +12,12 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.time.DateFormatter; +import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -71,4 +73,12 @@ public void execute() { } } } + + public void testEpochMillis() { + long instant = randomLongBetween( + DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parseMillis("-3000-01-01T00:00:00Z"), + DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parseMillis("3000-01-01T00:00:00Z") + ); + assertThat(DateFieldScript.millis(Instant.ofEpochMilli(instant)), equalTo(instant)); + } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml index 0c5a145211b0b..adbfa61d74f56 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/13_keyword_emit.yml @@ -131,7 +131,7 @@ setup: - match: {hits.hits.1.fields.voltage\.rounded: ['4']} --- -"string.emit": +"String.emit": - do: search: index: sensor diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml index 15c753565b20a..1a18a26f6da5a 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml @@ -125,19 +125,50 @@ setup: - match: {hits.hits.1.fields.voltage\.as_millis_since_epoch: ['1970-01-01T00:00:00.004Z']} --- -"string.emit": +"String.emit": - do: search: index: sensor body: runtime_mappings: - voltage.hack_year: + timestamp.hack_year: type: date script: | doc['timestamp'].value.toString().replace('2018', '2020').emit() fields: - - voltage.hack_year + - timestamp.hack_year sort: timestamp - match: {hits.total.value: 6} - - match: {hits.hits.0.fields.voltage\.hack_year: ['2020-01-18T17:41:34.000Z']} - - match: {hits.hits.1.fields.voltage\.hack_year: ['2020-01-19T17:41:34.000Z']} + - match: {hits.hits.0.fields.timestamp\.hack_year: ['2020-01-18T17:41:34.000Z']} + - match: {hits.hits.1.fields.timestamp\.hack_year: ['2020-01-19T17:41:34.000Z']} + + - do: + catch: /failed to parse date field \[not a date 2018-01-23T17:41:34.000Z\] with format \[strict_date_optional_time\|\|epoch_millis\]/ + search: + index: sensor + body: + runtime_mappings: + timestamp.broken: + type: date + script: | + ("not a date " + doc['timestamp'].value.toString()).emit() + fields: + - timestamp.broken + +--- +"TemporalAccessor.emit": + - do: + search: + index: sensor + body: + runtime_mappings: + timestamp.two_years_later: + type: date + script: | + doc['timestamp'].value.plusYears(2).emit() + fields: + - timestamp.two_years_later + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.timestamp\.two_years_later: ['2020-01-18T17:41:34.000Z']} + - match: {hits.hits.1.fields.timestamp\.two_years_later: ['2020-01-19T17:41:34.000Z']} From ddca7a50848c1b7c73e2fbf377f2c516c4126e32 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 29 Jan 2021 15:04:41 -0500 Subject: [PATCH 4/4] Example of truncating --- .../test/runtime_fields/43_date_emit.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml index 1a18a26f6da5a..e5e96eae397c9 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/43_date_emit.yml @@ -172,3 +172,19 @@ setup: - match: {hits.total.value: 6} - match: {hits.hits.0.fields.timestamp\.two_years_later: ['2020-01-18T17:41:34.000Z']} - match: {hits.hits.1.fields.timestamp\.two_years_later: ['2020-01-19T17:41:34.000Z']} + + - do: + search: + index: sensor + body: + runtime_mappings: + timestamp.date: + type: date + script: | + doc['timestamp'].value.truncatedTo(ChronoUnit.DAYS).emit() + fields: + - timestamp.date + sort: timestamp + - match: {hits.total.value: 6} + - match: {hits.hits.0.fields.timestamp\.date: ['2018-01-18T00:00:00.000Z']} + - match: {hits.hits.1.fields.timestamp\.date: ['2018-01-19T00:00:00.000Z']}