diff --git a/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectKey.java b/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectKey.java
index 5459850c521f0..03a6823b9503f 100644
--- a/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectKey.java
+++ b/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectKey.java
@@ -102,7 +102,7 @@ public final class DissectKey {
}
if (name == null || (name.isEmpty() && !skip)) {
- throw new DissectException.KeyParse(key, "The key name could be determined");
+ throw new DissectException.KeyParse(key, "The key name could not be determined");
}
}
diff --git a/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectParser.java b/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectParser.java
index 26e11cc9e1733..0ff3f724a0bd2 100644
--- a/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectParser.java
+++ b/libs/dissect/src/main/java/org/elasticsearch/dissect/DissectParser.java
@@ -34,7 +34,8 @@
import java.util.stream.Collectors;
/**
- *
Splits (dissects) a string into its parts based on a pattern.
A dissect pattern is composed of a set of keys and delimiters.
+ * Splits (dissects) a string into its parts based on a pattern.
+ *
A dissect pattern is composed of a set of keys and delimiters.
* For example the dissect pattern:
%{a} %{b},%{c}
has 3 keys (a,b,c) and two delimiters (space and comma). This pattern will
* match a string of the form:
foo bar,baz
and will result a key/value pairing of
a=foo, b=bar, and c=baz.
*
Matches are all or nothing. For example, the same pattern will NOT match
foo bar baz
since all of the delimiters did not
@@ -280,7 +281,19 @@ public Map parse(String inputString) {
}
Map results = dissectMatch.getResults();
- if (dissectMatch.isValid(results) == false) {
+ return dissectMatch.isValid(results) ? results : null;
+ }
+
+ /**
+ *
Entry point to dissect a string into it's parts.
+ *
+ * @param inputString The string to dissect
+ * @return the key/value Map of the results
+ * @throws DissectException if unable to dissect a pair into it's parts.
+ */
+ public Map forceParse(String inputString) {
+ Map results = parse(inputString);
+ if (results == null) {
throw new DissectException.FindMatch(pattern, inputString);
}
return results;
diff --git a/libs/dissect/src/test/java/org/elasticsearch/dissect/DissectParserTests.java b/libs/dissect/src/test/java/org/elasticsearch/dissect/DissectParserTests.java
index ff16937a5ab1f..d209c9a3b69eb 100644
--- a/libs/dissect/src/test/java/org/elasticsearch/dissect/DissectParserTests.java
+++ b/libs/dissect/src/test/java/org/elasticsearch/dissect/DissectParserTests.java
@@ -349,11 +349,12 @@ public void testJsonSpecification() throws Exception {
}
}
- private DissectException assertFail(String pattern, String input){
- return expectThrows(DissectException.class, () -> new DissectParser(pattern, null).parse(input));
+ private DissectException assertFail(String pattern, String input) {
+ return expectThrows(DissectException.class, () -> new DissectParser(pattern, null).forceParse(input));
}
private void assertMiss(String pattern, String input) {
+ assertNull(new DissectParser(pattern, null).parse(input));
DissectException e = assertFail(pattern, input);
assertThat(e.getMessage(), CoreMatchers.containsString("Unable to find match for dissect pattern"));
assertThat(e.getMessage(), CoreMatchers.containsString(pattern));
diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DissectProcessor.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DissectProcessor.java
index c9d5a3059d1fe..ddd922cc1f11f 100644
--- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DissectProcessor.java
+++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DissectProcessor.java
@@ -54,7 +54,7 @@ public IngestDocument execute(IngestDocument ingestDocument) {
} else if (input == null) {
throw new IllegalArgumentException("field [" + field + "] is null, cannot process it.");
}
- dissectParser.parse(input).forEach(ingestDocument::setFieldValue);
+ dissectParser.forceParse(input).forEach(ingestDocument::setFieldValue);
return ingestDocument;
}
diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotation.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotation.java
new file mode 100644
index 0000000000000..1eb9370a7a7f6
--- /dev/null
+++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotation.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Methods annotated with this must be run at compile time so their arguments
+ * must all be constant and they produce a constant.
+ */
+public class CompileTimeOnlyAnnotation {
+ public static final String NAME = "compile_time_only";
+
+ public static final CompileTimeOnlyAnnotation INSTANCE = new CompileTimeOnlyAnnotation();
+
+ private CompileTimeOnlyAnnotation() {}
+}
diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotationParser.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotationParser.java
new file mode 100644
index 0000000000000..df8b3df3446c6
--- /dev/null
+++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/CompileTimeOnlyAnnotationParser.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.spi.annotation;
+
+import java.util.Map;
+
+/**
+ * Methods annotated with {@link CompileTimeOnlyAnnotation} must be run at
+ * compile time so their arguments must all be constant and they produce a
+ * constant.
+ */
+public class CompileTimeOnlyAnnotationParser implements WhitelistAnnotationParser {
+
+ public static final CompileTimeOnlyAnnotationParser INSTANCE = new CompileTimeOnlyAnnotationParser();
+
+ private CompileTimeOnlyAnnotationParser() {}
+
+ @Override
+ public Object parse(Map arguments) {
+ if (arguments.isEmpty() == false) {
+ throw new IllegalArgumentException(
+ "unexpected parameters for [@" + CompileTimeOnlyAnnotation.NAME + "] annotation, found " + arguments
+ );
+ }
+
+ return CompileTimeOnlyAnnotation.INSTANCE;
+ }
+}
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..f210dc6251a10 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<>(CompileTimeOnlyAnnotation.NAME, CompileTimeOnlyAnnotationParser.INSTANCE)
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
index 29b47f9289bb7..52e4a91ee9c91 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
@@ -26,6 +26,7 @@
import org.elasticsearch.painless.node.SClass;
import org.elasticsearch.painless.phase.DefaultConstantFoldingOptimizationPhase;
import org.elasticsearch.painless.phase.DefaultIRTreeToASMBytesPhase;
+import org.elasticsearch.painless.phase.DefaultStaticConstantExtractionPhase;
import org.elasticsearch.painless.phase.DefaultStringConcatenationOptimizationPhase;
import org.elasticsearch.painless.phase.DocFieldsPhase;
import org.elasticsearch.painless.phase.PainlessSemanticAnalysisPhase;
@@ -227,6 +228,7 @@ ScriptScope compile(Loader loader, String name, String source, CompilerSettings
ClassNode classNode = (ClassNode)scriptScope.getDecoration(root, IRNodeDecoration.class).getIRNode();
new DefaultStringConcatenationOptimizationPhase().visitClass(classNode, null);
new DefaultConstantFoldingOptimizationPhase().visitClass(classNode, null);
+ new DefaultStaticConstantExtractionPhase().visitClass(classNode, scriptScope);
new DefaultIRTreeToASMBytesPhase().visitScript(classNode);
byte[] bytes = classNode.getBytes();
@@ -263,6 +265,7 @@ byte[] compile(String name, String source, CompilerSettings settings, Printer de
ClassNode classNode = (ClassNode)scriptScope.getDecoration(root, IRNodeDecoration.class).getIRNode();
new DefaultStringConcatenationOptimizationPhase().visitClass(classNode, null);
new DefaultConstantFoldingOptimizationPhase().visitClass(classNode, null);
+ new DefaultStaticConstantExtractionPhase().visitClass(classNode, scriptScope);
classNode.setDebugStream(debugStream);
new DefaultIRTreeToASMBytesPhase().visitScript(classNode);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessInstanceBinding.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessInstanceBinding.java
index 6952a3f05fb64..bd4640eb3d5c3 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessInstanceBinding.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessInstanceBinding.java
@@ -21,6 +21,7 @@
import java.lang.reflect.Method;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
public class PainlessInstanceBinding {
@@ -30,13 +31,21 @@ public class PainlessInstanceBinding {
public final Class> returnType;
public final List> typeParameters;
+ public final Map, Object> annotations;
- PainlessInstanceBinding(Object targetInstance, Method javaMethod, Class> returnType, List> typeParameters) {
+ PainlessInstanceBinding(
+ Object targetInstance,
+ Method javaMethod,
+ Class> returnType,
+ List> typeParameters,
+ Map, Object> annotations
+ ) {
this.targetInstance = targetInstance;
this.javaMethod = javaMethod;
this.returnType = returnType;
this.typeParameters = typeParameters;
+ this.annotations = annotations;
}
@Override
@@ -54,7 +63,8 @@ public boolean equals(Object object) {
return targetInstance == that.targetInstance &&
Objects.equals(javaMethod, that.javaMethod) &&
Objects.equals(returnType, that.returnType) &&
- Objects.equals(typeParameters, that.typeParameters);
+ Objects.equals(typeParameters, that.typeParameters) &&
+ Objects.equals(annotations, that.annotations);
}
@Override
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..d0324986c1c1c 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
@@ -30,6 +30,7 @@
import org.elasticsearch.painless.spi.WhitelistField;
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
import org.elasticsearch.painless.spi.WhitelistMethod;
+import org.elasticsearch.painless.spi.annotation.CompileTimeOnlyAnnotation;
import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation;
import org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
import org.objectweb.asm.ClassWriter;
@@ -174,7 +175,8 @@ public static PainlessLookup buildFromWhitelists(List whitelists) {
origin = whitelistInstanceBinding.origin;
painlessLookupBuilder.addPainlessInstanceBinding(
whitelistInstanceBinding.targetInstance, whitelistInstanceBinding.methodName,
- whitelistInstanceBinding.returnCanonicalTypeName, whitelistInstanceBinding.canonicalTypeNameParameters);
+ whitelistInstanceBinding.returnCanonicalTypeName, whitelistInstanceBinding.canonicalTypeNameParameters,
+ whitelistInstanceBinding.painlessAnnotations);
}
}
} catch (Exception exception) {
@@ -393,6 +395,10 @@ public void addPainlessConstructor(Class> targetClass, List> typePara
"[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae);
}
+ if (annotations.containsKey(CompileTimeOnlyAnnotation.class)) {
+ throw new IllegalArgumentException("constructors can't have @" + CompileTimeOnlyAnnotation.NAME);
+ }
+
MethodType methodType = methodHandle.type();
String painlessConstructorKey = buildPainlessConstructorKey(typeParametersSize);
@@ -574,6 +580,10 @@ public void addPainlessMethod(Class> targetClass, Class> augmentedClass,
}
}
+ if (annotations.containsKey(CompileTimeOnlyAnnotation.class)) {
+ throw new IllegalArgumentException("regular methods can't have @" + CompileTimeOnlyAnnotation.NAME);
+ }
+
MethodType methodType = methodHandle.type();
boolean isStatic = augmentedClass == null && Modifier.isStatic(javaMethod.getModifiers());
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
@@ -989,6 +999,10 @@ public void addPainlessClassBinding(Class> targetClass, String methodName, Cla
"invalid method name [" + methodName + "] for class binding [" + targetCanonicalClassName + "].");
}
+ if (annotations.containsKey(CompileTimeOnlyAnnotation.class)) {
+ throw new IllegalArgumentException("class bindings can't have @" + CompileTimeOnlyAnnotation.NAME);
+ }
+
Method[] javaMethods = targetClass.getMethods();
Method javaMethod = null;
@@ -1079,7 +1093,8 @@ public void addPainlessClassBinding(Class> targetClass, String methodName, Cla
}
public void addPainlessInstanceBinding(Object targetInstance,
- String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters) {
+ String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters,
+ Map, Object> painlessAnnotations) {
Objects.requireNonNull(targetInstance);
Objects.requireNonNull(methodName);
@@ -1108,10 +1123,16 @@ public void addPainlessInstanceBinding(Object targetInstance,
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
}
- addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters);
+ addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters, painlessAnnotations);
}
- public void addPainlessInstanceBinding(Object targetInstance, String methodName, Class> returnType, List> typeParameters) {
+ public void addPainlessInstanceBinding(
+ Object targetInstance,
+ String methodName,
+ Class> returnType,
+ List> typeParameters,
+ Map, Object> painlessAnnotations
+ ) {
Objects.requireNonNull(targetInstance);
Objects.requireNonNull(methodName);
Objects.requireNonNull(returnType);
@@ -1189,7 +1210,7 @@ public void addPainlessInstanceBinding(Object targetInstance, String methodName,
PainlessInstanceBinding existingPainlessInstanceBinding = painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
PainlessInstanceBinding newPainlessInstanceBinding =
- new PainlessInstanceBinding(targetInstance, javaMethod, returnType, typeParameters);
+ new PainlessInstanceBinding(targetInstance, javaMethod, returnType, typeParameters, painlessAnnotations);
if (existingPainlessInstanceBinding == null) {
newPainlessInstanceBinding = painlessInstanceBindingCache.computeIfAbsent(newPainlessInstanceBinding, key -> key);
@@ -1200,11 +1221,13 @@ public void addPainlessInstanceBinding(Object targetInstance, String methodName,
"[[" + targetCanonicalClassName + "], " +
"[" + methodName + "], " +
"[" + typeToCanonicalTypeName(returnType) + "], " +
- typesToCanonicalTypeNames(typeParameters) + "] and " +
+ typesToCanonicalTypeNames(typeParameters) + "], " +
+ painlessAnnotations + " and " +
"[[" + targetCanonicalClassName + "], " +
"[" + methodName + "], " +
"[" + typeToCanonicalTypeName(existingPainlessInstanceBinding.returnType) + "], " +
- typesToCanonicalTypeNames(existingPainlessInstanceBinding.typeParameters) + "]");
+ typesToCanonicalTypeNames(existingPainlessInstanceBinding.typeParameters) + "], " +
+ existingPainlessInstanceBinding.annotations);
}
}
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultConstantFoldingOptimizationPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultConstantFoldingOptimizationPhase.java
index be6c2e681aca8..c3efa32bdb854 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultConstantFoldingOptimizationPhase.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultConstantFoldingOptimizationPhase.java
@@ -21,8 +21,8 @@
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.ir.BinaryMathNode;
import org.elasticsearch.painless.ir.BinaryImplNode;
+import org.elasticsearch.painless.ir.BinaryMathNode;
import org.elasticsearch.painless.ir.BooleanNode;
import org.elasticsearch.painless.ir.CastNode;
import org.elasticsearch.painless.ir.ComparisonNode;
@@ -66,13 +66,20 @@
import org.elasticsearch.painless.ir.ThrowNode;
import org.elasticsearch.painless.ir.UnaryMathNode;
import org.elasticsearch.painless.ir.WhileLoopNode;
+import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.spi.annotation.CompileTimeOnlyAnnotation;
import org.elasticsearch.painless.symbol.IRDecorations.IRDCast;
import org.elasticsearch.painless.symbol.IRDecorations.IRDComparisonType;
import org.elasticsearch.painless.symbol.IRDecorations.IRDConstant;
import org.elasticsearch.painless.symbol.IRDecorations.IRDExpressionType;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDInstanceBinding;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDMethod;
import org.elasticsearch.painless.symbol.IRDecorations.IRDOperation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.function.Consumer;
/**
@@ -824,6 +831,48 @@ public void visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, C
int j = i;
irInvokeCallMemberNode.getArgumentNodes().get(i).visit(this, (e) -> irInvokeCallMemberNode.getArgumentNodes().set(j, e));
}
+ PainlessMethod method = irInvokeCallMemberNode.getDecorationValue(IRDMethod.class);
+ if (method != null && method.annotations.containsKey(CompileTimeOnlyAnnotation.class)) {
+ replaceCallWithConstant(irInvokeCallMemberNode, scope, method.javaMethod, null);
+ return;
+ }
+ PainlessInstanceBinding instanceBinding = irInvokeCallMemberNode.getDecorationValue(IRDInstanceBinding.class);
+ if (instanceBinding != null && instanceBinding.annotations.containsKey(CompileTimeOnlyAnnotation.class)) {
+ replaceCallWithConstant(irInvokeCallMemberNode, scope, instanceBinding.javaMethod, instanceBinding.targetInstance);
+ return;
+ }
+ }
+
+ private void replaceCallWithConstant(
+ InvokeCallMemberNode irInvokeCallMemberNode,
+ Consumer scope,
+ Method javaMethod,
+ Object receiver
+ ) {
+ Object[] args = new Object[irInvokeCallMemberNode.getArgumentNodes().size()];
+ for (int i = 0; i < irInvokeCallMemberNode.getArgumentNodes().size(); i++) {
+ ExpressionNode argNode = irInvokeCallMemberNode.getArgumentNodes().get(i);
+ if (argNode instanceof ConstantNode == false) {
+ // TODO find a better string to output
+ throw irInvokeCallMemberNode.getLocation()
+ .createError(new IllegalArgumentException("all arguments must be constant but the [" + (i + 1) + "] argument isn't"));
+ }
+ args[i] = ((ConstantNode) argNode).getDecorationValue(IRDConstant.class);
+ }
+ Object result;
+ try {
+ result = javaMethod.invoke(receiver, args);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ throw irInvokeCallMemberNode.getLocation()
+ .createError(new IllegalArgumentException("error invoking [" + irInvokeCallMemberNode + "] at compile time", e));
+ } catch (InvocationTargetException e) {
+ throw irInvokeCallMemberNode.getLocation()
+ .createError(new IllegalArgumentException("error invoking [" + irInvokeCallMemberNode + "] at compile time", e.getCause()));
+ }
+ ConstantNode replacement = new ConstantNode(irInvokeCallMemberNode.getLocation());
+ replacement.attachDecoration(new IRDConstant(result));
+ replacement.attachDecoration(irInvokeCallMemberNode.getDecoration(IRDExpressionType.class));
+ scope.accept(replacement);
}
@Override
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..1abc5b4aa65b2 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
@@ -121,10 +121,11 @@
import org.elasticsearch.painless.symbol.IRDecorations.IRDClassBinding;
import org.elasticsearch.painless.symbol.IRDecorations.IRDComparisonType;
import org.elasticsearch.painless.symbol.IRDecorations.IRDConstant;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDConstantFieldName;
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;
@@ -1220,7 +1221,13 @@ public void visitConstant(ConstantNode irConstantNode, WriteScope writeScope) {
else if (constant instanceof Byte) methodWriter.push((byte)constant);
else if (constant instanceof Boolean) methodWriter.push((boolean)constant);
else {
- throw new IllegalStateException("unexpected constant [" + constant + "]");
+ /*
+ * The constant doesn't properly fit into the constant pool so
+ * we should have made a static field for it.
+ */
+ String fieldName = irConstantNode.getDecorationValue(IRDConstantFieldName.class);
+ Type asmFieldType = MethodWriter.getType(irConstantNode.getDecorationValue(IRDExpressionType.class));
+ methodWriter.getStatic(CLASS_TYPE, fieldName, asmFieldType);
}
}
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultStaticConstantExtractionPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultStaticConstantExtractionPhase.java
new file mode 100644
index 0000000000000..4849e25c0a21b
--- /dev/null
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultStaticConstantExtractionPhase.java
@@ -0,0 +1,86 @@
+/*
+ * 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.phase;
+
+import org.elasticsearch.painless.ir.ClassNode;
+import org.elasticsearch.painless.ir.ConstantNode;
+import org.elasticsearch.painless.ir.FieldNode;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDConstant;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDConstantFieldName;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDExpressionType;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDFieldType;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDModifiers;
+import org.elasticsearch.painless.symbol.IRDecorations.IRDName;
+import org.elasticsearch.painless.symbol.ScriptScope;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * Looks for {@link ConstantNode}s that can't be pushed into the constant pool
+ * and creates a {@code static} constant member that is injected using reflection
+ * on construction.
+ */
+public class DefaultStaticConstantExtractionPhase extends IRTreeBaseVisitor {
+ private ClassNode classNode;
+
+ @Override
+ public void visitClass(ClassNode irClassNode, ScriptScope scope) {
+ this.classNode = irClassNode;
+ super.visitClass(irClassNode, scope);
+ }
+
+ @Override
+ public void visitConstant(ConstantNode irConstantNode, ScriptScope scope) {
+ super.visitConstant(irConstantNode, scope);
+ Object constant = irConstantNode.getDecorationValue(IRDConstant.class);
+ if (constant instanceof String
+ || constant instanceof Double
+ || constant instanceof Float
+ || constant instanceof Long
+ || constant instanceof Integer
+ || constant instanceof Character
+ || constant instanceof Short
+ || constant instanceof Byte
+ || constant instanceof Boolean) {
+ /*
+ * Constant can be loaded into the constant pool so we let the byte
+ * code generation phase do that.
+ */
+ return;
+ }
+ /*
+ * The constant *can't* be loaded into the constant pool so we make it
+ * a static constant and register the value with ScriptScope. The byte
+ * code generation will load the static constant.
+ */
+ String fieldName = scope.getNextSyntheticName("constant");
+ scope.addStaticConstant(fieldName, constant);
+
+ FieldNode constantField = new FieldNode(irConstantNode.getLocation());
+ constantField.attachDecoration(new IRDModifiers(Modifier.PUBLIC | Modifier.STATIC));
+ constantField.attachDecoration(irConstantNode.getDecoration(IRDConstant.class));
+ Class> type = irConstantNode.getDecorationValue(IRDExpressionType.class);
+ constantField.attachDecoration(new IRDFieldType(type));
+ constantField.attachDecoration(new IRDName(fieldName));
+ classNode.addFieldNode(constantField);
+
+ irConstantNode.attachDecoration(new IRDConstantFieldName(fieldName));
+ }
+}
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..ab6ac4b670de8 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
@@ -148,6 +148,15 @@ public IRDConstant(Object value) {
}
}
+ /**
+ * describes the field name holding a constant value.
+ */
+ public static class IRDConstantFieldName extends IRDecoration {
+ public IRDConstantFieldName(String value) {
+ super(value);
+ }
+ }
+
/**
* describes the type for a declaration
*/
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/BindingsTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BindingsTests.java
index 171880abd7907..687cf4657f86c 100644
--- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BindingsTests.java
+++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BindingsTests.java
@@ -22,6 +22,7 @@
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
import org.elasticsearch.painless.spi.WhitelistLoader;
+import org.elasticsearch.painless.spi.annotation.CompileTimeOnlyAnnotation;
import org.elasticsearch.script.ScriptContext;
import java.util.ArrayList;
@@ -29,8 +30,24 @@
import java.util.List;
import java.util.Map;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.startsWith;
+
public class BindingsTests extends ScriptTestCase {
+ public static int classMul(int i, int j) {
+ return i * j;
+ }
+
+ public static int compileTimeBlowUp(int i, int j) {
+ throw new RuntimeException("Boom");
+ }
+
+ public static List