Update Painless to Use New Script Contexts (#25015)
* All public methods starting with get will be added as local variables to the execute method. * The execute method on a ScriptContext must be both public and abstract. This method will be implemented by the Painless compiler. * A static list of parameter names for the execute method must be provided since the names will be eliminated at runtime. * The uses$ methods will still be implemented as before. * A single constructor may be provided by the ScriptContext. This constructor will be overridden by the Painless compiler to include the exact same arguments. This allows instances of a Painless script to potentially contain state. If a constructor is not provided it is assumed the default constructor with no arguments will be used.
This commit is contained in:
parent
4ed0abe72d
commit
a926ace2e1
|
@ -133,15 +133,15 @@ final class Compiler {
|
|||
* @param settings The CompilerSettings to be used during the compilation.
|
||||
* @return An executable script that implements both a specified interface and is a subclass of {@link PainlessScript}
|
||||
*/
|
||||
Constructor<? extends PainlessScript> compile(Loader loader, String name, String source, CompilerSettings settings) {
|
||||
Constructor<?> compile(Loader loader, String name, String source, CompilerSettings settings) {
|
||||
if (source.length() > MAXIMUM_SOURCE_LENGTH) {
|
||||
throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH +
|
||||
" characters. The passed in script is " + source.length() + " characters. Consider using a" +
|
||||
" plugin if a script longer than this length is a requirement.");
|
||||
}
|
||||
|
||||
ScriptInterface scriptInterface = new ScriptInterface(definition, base);
|
||||
SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition,
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, base);
|
||||
SSource root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, definition,
|
||||
null);
|
||||
root.analyze(definition);
|
||||
root.write();
|
||||
|
@ -153,7 +153,7 @@ final class Compiler {
|
|||
clazz.getField("$STATEMENTS").set(null, root.getStatements());
|
||||
clazz.getField("$DEFINITION").set(null, definition);
|
||||
|
||||
return clazz.getConstructor();
|
||||
return clazz.getConstructors()[0];
|
||||
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
||||
throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception);
|
||||
}
|
||||
|
@ -172,8 +172,8 @@ final class Compiler {
|
|||
" plugin if a script longer than this length is a requirement.");
|
||||
}
|
||||
|
||||
ScriptInterface scriptInterface = new ScriptInterface(definition, base);
|
||||
SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition,
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, base);
|
||||
SSource root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, definition,
|
||||
debugStream);
|
||||
root.analyze(definition);
|
||||
root.write();
|
||||
|
|
|
@ -26,10 +26,14 @@ import java.util.Map;
|
|||
/**
|
||||
* Generic script interface that Painless implements for all Elasticsearch scripts.
|
||||
*/
|
||||
public interface GenericElasticsearchScript {
|
||||
String[] ARGUMENTS = new String[] {"params", "_score", "doc", "_value", "ctx"};
|
||||
Object execute(Map<String, Object> params, double _score, Map<String, ScriptDocValues<?>> doc, Object _value, Map<?, ?> ctx);
|
||||
public abstract class GenericElasticsearchScript {
|
||||
|
||||
boolean uses$_score();
|
||||
boolean uses$ctx();
|
||||
public GenericElasticsearchScript() {}
|
||||
|
||||
public static final String[] PARAMETERS = new String[] {"params", "_score", "doc", "_value", "ctx"};
|
||||
public abstract Object execute(
|
||||
Map<String, Object> params, double _score, Map<String, ScriptDocValues<?>> doc, Object _value, Map<?, ?> ctx);
|
||||
|
||||
public abstract boolean uses$_score();
|
||||
public abstract boolean uses$ctx();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ package org.elasticsearch.painless;
|
|||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.ScriptInterface.MethodArgument;
|
||||
import org.elasticsearch.painless.ScriptClassInfo.MethodArgument;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -91,14 +91,14 @@ public final class Locals {
|
|||
}
|
||||
|
||||
/** Creates a new main method scope */
|
||||
public static Locals newMainMethodScope(ScriptInterface scriptInterface, Locals programScope, int maxLoopCounter) {
|
||||
public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals programScope, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, programScope.definition,
|
||||
scriptInterface.getExecuteMethodReturnType(), KEYWORDS);
|
||||
scriptClassInfo.getExecuteMethodReturnType(), KEYWORDS);
|
||||
// This reference. Internal use only.
|
||||
locals.defineVariable(null, programScope.definition.getType("Object"), THIS, true);
|
||||
|
||||
// Method arguments
|
||||
for (MethodArgument arg : scriptInterface.getExecuteArguments()) {
|
||||
for (MethodArgument arg : scriptClassInfo.getExecuteArguments()) {
|
||||
locals.defineVariable(null, arg.getType(), arg.getName(), true);
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ public final class Locals {
|
|||
|
||||
///// private impl
|
||||
|
||||
/** Whitelist against which thhis script is being compiled. */
|
||||
/** Whitelist against which this script is being compiled. */
|
||||
private final Definition definition;
|
||||
// parent scope
|
||||
private final Locals parent;
|
||||
|
@ -275,6 +275,7 @@ public final class Locals {
|
|||
public final Type type;
|
||||
public final boolean readonly;
|
||||
private final int slot;
|
||||
private boolean used;
|
||||
|
||||
public Variable(Location location, String name, Type type, int slot, boolean readonly) {
|
||||
this.location = location;
|
||||
|
|
|
@ -136,7 +136,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||
throw new IllegalArgumentException("painless does not know how to handle context [" + context.name + "]");
|
||||
}
|
||||
|
||||
PainlessScript compile(Compiler compiler, String scriptName, final String scriptSource, final Map<String, String> params) {
|
||||
Object compile(Compiler compiler, String scriptName, String source, Map<String, String> params, Object... args) {
|
||||
final CompilerSettings compilerSettings;
|
||||
|
||||
if (params.isEmpty()) {
|
||||
|
@ -189,14 +189,14 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||
|
||||
try {
|
||||
// Drop all permissions to actually compile the code itself.
|
||||
return AccessController.doPrivileged(new PrivilegedAction<PainlessScript>() {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||
@Override
|
||||
public PainlessScript run() {
|
||||
public Object run() {
|
||||
String name = scriptName == null ? INLINE_NAME : scriptName;
|
||||
Constructor<? extends PainlessScript> constructor = compiler.compile(loader, name, scriptSource, compilerSettings);
|
||||
Constructor<?> constructor = compiler.compile(loader, name, source, compilerSettings);
|
||||
|
||||
try {
|
||||
return constructor.newInstance();
|
||||
return constructor.newInstance(args);
|
||||
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
||||
throw new IllegalStateException(
|
||||
"An internal error occurred attempting to define the script [" + name + "].", exception);
|
||||
|
@ -205,7 +205,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||
}, COMPILATION_CONTEXT);
|
||||
// Note that it is safe to catch any of the following errors since Painless is stateless.
|
||||
} catch (OutOfMemoryError | StackOverflowError | VerifyError | Exception e) {
|
||||
throw convertToScriptException(scriptName == null ? scriptSource : scriptName, scriptSource, e);
|
||||
throw convertToScriptException(scriptName == null ? source : scriptName, source, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless;
|
|||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -33,20 +34,25 @@ import static org.elasticsearch.painless.WriterConstants.USES_PARAMETER_METHOD_T
|
|||
/**
|
||||
* Information about the interface being implemented by the painless script.
|
||||
*/
|
||||
public class ScriptInterface {
|
||||
private final Class<?> iface;
|
||||
public class ScriptClassInfo {
|
||||
|
||||
private final Class<?> baseClass;
|
||||
private final org.objectweb.asm.commons.Method executeMethod;
|
||||
private final Definition.Type executeMethodReturnType;
|
||||
private final List<MethodArgument> executeArguments;
|
||||
private final List<org.objectweb.asm.commons.Method> usesMethods;
|
||||
private final List<org.objectweb.asm.commons.Method> getMethods;
|
||||
private final List<Definition.Type> getReturns;
|
||||
|
||||
public ScriptInterface(Definition definition, Class<?> iface) {
|
||||
this.iface = iface;
|
||||
public ScriptClassInfo(Definition definition, Class<?> baseClass) {
|
||||
this.baseClass = baseClass;
|
||||
|
||||
// Find the main method and the uses$argName methods
|
||||
java.lang.reflect.Method executeMethod = null;
|
||||
List<org.objectweb.asm.commons.Method> usesMethods = new ArrayList<>();
|
||||
for (java.lang.reflect.Method m : iface.getMethods()) {
|
||||
List<org.objectweb.asm.commons.Method> getMethods = new ArrayList<>();
|
||||
List<Definition.Type> getReturns = new ArrayList<>();
|
||||
for (java.lang.reflect.Method m : baseClass.getMethods()) {
|
||||
if (m.isDefault()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -55,40 +61,45 @@ public class ScriptInterface {
|
|||
executeMethod = m;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Painless can only implement interfaces that have a single method named [execute] but [" + iface.getName()
|
||||
"Painless can only implement interfaces that have a single method named [execute] but [" + baseClass.getName()
|
||||
+ "] has more than one.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (m.getName().startsWith("uses$")) {
|
||||
if (false == m.getReturnType().equals(boolean.class)) {
|
||||
throw new IllegalArgumentException("Painless can only implement uses$ methods that return boolean but ["
|
||||
+ iface.getName() + "#" + m.getName() + "] returns [" + m.getReturnType().getName() + "].");
|
||||
+ baseClass.getName() + "#" + m.getName() + "] returns [" + m.getReturnType().getName() + "].");
|
||||
}
|
||||
if (m.getParameterTypes().length > 0) {
|
||||
throw new IllegalArgumentException("Painless can only implement uses$ methods that do not take parameters but ["
|
||||
+ iface.getName() + "#" + m.getName() + "] does.");
|
||||
+ baseClass.getName() + "#" + m.getName() + "] does.");
|
||||
}
|
||||
usesMethods.add(new org.objectweb.asm.commons.Method(m.getName(), USES_PARAMETER_METHOD_TYPE.toMethodDescriptorString()));
|
||||
continue;
|
||||
}
|
||||
throw new IllegalArgumentException("Painless can only implement methods named [execute] and [uses$argName] but ["
|
||||
+ iface.getName() + "] contains a method named [" + m.getName() + "]");
|
||||
if (m.getName().startsWith("get") && m.getName().equals("getClass") == false && Modifier.isStatic(m.getModifiers()) == false) {
|
||||
getReturns.add(
|
||||
definitionTypeForClass(definition, m.getReturnType(), componentType -> "[" + m.getName() + "] has unknown return type ["
|
||||
+ componentType.getName() + "]. Painless can only support getters with return types that are whitelisted."));
|
||||
|
||||
getMethods.add(new org.objectweb.asm.commons.Method(m.getName(),
|
||||
MethodType.methodType(m.getReturnType()).toMethodDescriptorString()));
|
||||
|
||||
}
|
||||
}
|
||||
MethodType methodType = MethodType.methodType(executeMethod.getReturnType(), executeMethod.getParameterTypes());
|
||||
this.executeMethod = new org.objectweb.asm.commons.Method(executeMethod.getName(), methodType.toMethodDescriptorString());
|
||||
executeMethodReturnType = definitionTypeForClass(definition, executeMethod.getReturnType(),
|
||||
componentType -> "Painless can only implement execute methods returning a whitelisted type but [" + iface.getName()
|
||||
componentType -> "Painless can only implement execute methods returning a whitelisted type but [" + baseClass.getName()
|
||||
+ "#execute] returns [" + componentType.getName() + "] which isn't whitelisted.");
|
||||
|
||||
// Look up the argument names
|
||||
Set<String> argumentNames = new LinkedHashSet<>();
|
||||
List<MethodArgument> arguments = new ArrayList<>();
|
||||
String[] argumentNamesConstant = readArgumentNamesConstant(iface);
|
||||
String[] argumentNamesConstant = readArgumentNamesConstant(baseClass);
|
||||
Class<?>[] types = executeMethod.getParameterTypes();
|
||||
if (argumentNamesConstant.length != types.length) {
|
||||
throw new IllegalArgumentException("[" + iface.getName() + "#ARGUMENTS] has length [2] but ["
|
||||
+ iface.getName() + "#execute] takes [1] argument.");
|
||||
throw new IllegalArgumentException("[" + baseClass.getName() + "#ARGUMENTS] has length [2] but ["
|
||||
+ baseClass.getName() + "#execute] takes [1] argument.");
|
||||
}
|
||||
for (int arg = 0; arg < types.length; arg++) {
|
||||
arguments.add(methodArgument(definition, types[arg], argumentNamesConstant[arg]));
|
||||
|
@ -100,17 +111,19 @@ public class ScriptInterface {
|
|||
for (org.objectweb.asm.commons.Method usesMethod : usesMethods) {
|
||||
if (false == argumentNames.contains(usesMethod.getName().substring("uses$".length()))) {
|
||||
throw new IllegalArgumentException("Painless can only implement uses$ methods that match a parameter name but ["
|
||||
+ iface.getName() + "#" + usesMethod.getName() + "] doesn't match any of " + argumentNames + ".");
|
||||
+ baseClass.getName() + "#" + usesMethod.getName() + "] doesn't match any of " + argumentNames + ".");
|
||||
}
|
||||
}
|
||||
this.usesMethods = unmodifiableList(usesMethods);
|
||||
this.getMethods = unmodifiableList(getMethods);
|
||||
this.getReturns = unmodifiableList(getReturns);
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface that the Painless script should implement.
|
||||
*/
|
||||
public Class<?> getInterface() {
|
||||
return iface;
|
||||
public Class<?> getBaseClass() {
|
||||
return baseClass;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,6 +156,20 @@ public class ScriptInterface {
|
|||
return usesMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code getVarName} methods that must be implemented by Painless to complete implementing the interface.
|
||||
*/
|
||||
public List<org.objectweb.asm.commons.Method> getGetMethods() {
|
||||
return getMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code getVarName} methods return types.
|
||||
*/
|
||||
public List<Definition.Type> getGetReturns() {
|
||||
return getReturns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Painless {@link Definition.Type}s and name of the argument to the {@code execute} method.
|
||||
*/
|
||||
|
@ -194,13 +221,13 @@ public class ScriptInterface {
|
|||
private static String[] readArgumentNamesConstant(Class<?> iface) {
|
||||
Field argumentNamesField;
|
||||
try {
|
||||
argumentNamesField = iface.getField("ARGUMENTS");
|
||||
argumentNamesField = iface.getField("PARAMETERS");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new IllegalArgumentException("Painless needs a constant [String[] ARGUMENTS] on all interfaces it implements with the "
|
||||
throw new IllegalArgumentException("Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
|
||||
+ "names of the method arguments but [" + iface.getName() + "] doesn't have one.", e);
|
||||
}
|
||||
if (false == argumentNamesField.getType().equals(String[].class)) {
|
||||
throw new IllegalArgumentException("Painless needs a constant [String[] ARGUMENTS] on all interfaces it implements with the "
|
||||
throw new IllegalArgumentException("Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
|
||||
+ "names of the method arguments but [" + iface.getName() + "] doesn't have one.");
|
||||
}
|
||||
try {
|
|
@ -56,7 +56,6 @@ public final class WriterConstants {
|
|||
|
||||
public static final String CTOR_METHOD_NAME = "<init>";
|
||||
|
||||
public static final Method CONSTRUCTOR = getAsmMethod(void.class, CTOR_METHOD_NAME);
|
||||
public static final Method CLINIT = getAsmMethod(void.class, "<clinit>");
|
||||
|
||||
public static final String GET_NAME_NAME = "getName";
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.ScriptInterface;
|
||||
import org.elasticsearch.painless.ScriptClassInfo;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext;
|
||||
|
@ -174,14 +174,14 @@ import java.util.List;
|
|||
*/
|
||||
public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
||||
|
||||
public static SSource buildPainlessTree(ScriptInterface mainMethod, String sourceName,
|
||||
String sourceText, CompilerSettings settings, Definition definition,
|
||||
Printer debugStream) {
|
||||
public static SSource buildPainlessTree(ScriptClassInfo mainMethod, String sourceName,
|
||||
String sourceText, CompilerSettings settings, Definition definition,
|
||||
Printer debugStream) {
|
||||
return new Walker(mainMethod, sourceName, sourceText, settings, definition,
|
||||
debugStream).source;
|
||||
}
|
||||
|
||||
private final ScriptInterface scriptInterface;
|
||||
private final ScriptClassInfo scriptClassInfo;
|
||||
private final SSource source;
|
||||
private final CompilerSettings settings;
|
||||
private final Printer debugStream;
|
||||
|
@ -193,9 +193,9 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
private final Globals globals;
|
||||
private int syntheticCounter = 0;
|
||||
|
||||
private Walker(ScriptInterface scriptInterface, String sourceName, String sourceText,
|
||||
CompilerSettings settings, Definition definition, Printer debugStream) {
|
||||
this.scriptInterface = scriptInterface;
|
||||
private Walker(ScriptClassInfo scriptClassInfo, String sourceName, String sourceText,
|
||||
CompilerSettings settings, Definition definition, Printer debugStream) {
|
||||
this.scriptClassInfo = scriptClassInfo;
|
||||
this.debugStream = debugStream;
|
||||
this.settings = settings;
|
||||
this.sourceName = Location.computeSourceName(sourceName, sourceText);
|
||||
|
@ -266,7 +266,7 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
statements.add((AStatement)visit(statement));
|
||||
}
|
||||
|
||||
return new SSource(scriptInterface, settings, sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
|
||||
return new SSource(scriptClassInfo, settings, sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
|
||||
location(ctx), functions, globals, statements);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.ScriptInterface;
|
||||
import org.elasticsearch.painless.ScriptClassInfo;
|
||||
import org.elasticsearch.painless.SimpleChecksAdapter;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
|
@ -40,6 +40,7 @@ import org.objectweb.asm.Type;
|
|||
import org.objectweb.asm.util.Printer;
|
||||
import org.objectweb.asm.util.TraceClassVisitor;
|
||||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
|
@ -58,7 +59,6 @@ import static org.elasticsearch.painless.WriterConstants.BITSET_TYPE;
|
|||
import static org.elasticsearch.painless.WriterConstants.BOOTSTRAP_METHOD_ERROR_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.COLLECTIONS_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.CONSTRUCTOR;
|
||||
import static org.elasticsearch.painless.WriterConstants.CONVERT_TO_SCRIPT_EXCEPTION_METHOD;
|
||||
import static org.elasticsearch.painless.WriterConstants.DEFINITION_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_METHOD;
|
||||
|
@ -69,7 +69,6 @@ import static org.elasticsearch.painless.WriterConstants.EXCEPTION_TYPE;
|
|||
import static org.elasticsearch.painless.WriterConstants.GET_NAME_METHOD;
|
||||
import static org.elasticsearch.painless.WriterConstants.GET_SOURCE_METHOD;
|
||||
import static org.elasticsearch.painless.WriterConstants.GET_STATEMENTS_METHOD;
|
||||
import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.OUT_OF_MEMORY_ERROR_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD;
|
||||
|
@ -119,7 +118,7 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
}
|
||||
|
||||
private final ScriptInterface scriptInterface;
|
||||
private final ScriptClassInfo scriptClassInfo;
|
||||
private final CompilerSettings settings;
|
||||
private final String name;
|
||||
private final String source;
|
||||
|
@ -130,13 +129,14 @@ public final class SSource extends AStatement {
|
|||
private final List<AStatement> statements;
|
||||
|
||||
private Locals mainMethod;
|
||||
private final List<org.objectweb.asm.commons.Method> getMethods;
|
||||
private byte[] bytes;
|
||||
|
||||
public SSource(ScriptInterface scriptInterface, CompilerSettings settings, String name, String source, Printer debugStream,
|
||||
public SSource(ScriptClassInfo scriptClassInfo, CompilerSettings settings, String name, String source, Printer debugStream,
|
||||
MainMethodReserved reserved, Location location,
|
||||
List<SFunction> functions, Globals globals, List<AStatement> statements) {
|
||||
super(location);
|
||||
this.scriptInterface = Objects.requireNonNull(scriptInterface);
|
||||
this.scriptClassInfo = Objects.requireNonNull(scriptClassInfo);
|
||||
this.settings = Objects.requireNonNull(settings);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.source = Objects.requireNonNull(source);
|
||||
|
@ -148,6 +148,8 @@ public final class SSource extends AStatement {
|
|||
this.functions = Collections.unmodifiableList(functions);
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.globals = globals;
|
||||
|
||||
this.getMethods = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,7 +186,19 @@ public final class SSource extends AStatement {
|
|||
throw createError(new IllegalArgumentException("Cannot generate an empty script."));
|
||||
}
|
||||
|
||||
mainMethod = Locals.newMainMethodScope(scriptInterface, program, reserved.getMaxLoopCounter());
|
||||
mainMethod = Locals.newMainMethodScope(scriptClassInfo, program, reserved.getMaxLoopCounter());
|
||||
|
||||
for (int get = 0; get < scriptClassInfo.getGetMethods().size(); ++get) {
|
||||
org.objectweb.asm.commons.Method method = scriptClassInfo.getGetMethods().get(get);
|
||||
String name = method.getName().substring(3);
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
|
||||
if (reserved.getUsedVariables().contains(name)) {
|
||||
Definition.Type rtn = scriptClassInfo.getGetReturns().get(get);
|
||||
mainMethod.addVariable(new Location("getter [" + name + "]", 0), rtn, name, true);
|
||||
getMethods.add(method);
|
||||
}
|
||||
}
|
||||
|
||||
AStatement last = statements.get(statements.size() - 1);
|
||||
|
||||
|
@ -211,7 +225,7 @@ public final class SSource extends AStatement {
|
|||
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
|
||||
String interfaceBase = BASE_INTERFACE_TYPE.getInternalName();
|
||||
String className = CLASS_TYPE.getInternalName();
|
||||
String classInterfaces[] = new String[] { interfaceBase, Type.getType(scriptInterface.getInterface()).getInternalName() };
|
||||
String classInterfaces[] = new String[] { interfaceBase };
|
||||
|
||||
ClassWriter writer = new ClassWriter(classFrames);
|
||||
ClassVisitor visitor = writer;
|
||||
|
@ -224,7 +238,8 @@ public final class SSource extends AStatement {
|
|||
if (debugStream != null) {
|
||||
visitor = new TraceClassVisitor(visitor, debugStream, null);
|
||||
}
|
||||
visitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, OBJECT_TYPE.getInternalName(), classInterfaces);
|
||||
visitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null,
|
||||
Type.getType(scriptClassInfo.getBaseClass()).getInternalName(), classInterfaces);
|
||||
visitor.visitSource(Location.computeSourceName(name, source), null);
|
||||
|
||||
// Write the a method to bootstrap def calls
|
||||
|
@ -245,12 +260,21 @@ public final class SSource extends AStatement {
|
|||
// Write the static variable used by the method to bootstrap def calls
|
||||
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$DEFINITION", DEFINITION_TYPE.getDescriptor(), null, null).visitEnd();
|
||||
|
||||
org.objectweb.asm.commons.Method init;
|
||||
|
||||
if (scriptClassInfo.getBaseClass().getConstructors().length == 0) {
|
||||
init = new org.objectweb.asm.commons.Method("<init>", MethodType.methodType(void.class).toMethodDescriptorString());
|
||||
} else {
|
||||
init = new org.objectweb.asm.commons.Method("<init>", MethodType.methodType(void.class,
|
||||
scriptClassInfo.getBaseClass().getConstructors()[0].getParameterTypes()).toMethodDescriptorString());
|
||||
}
|
||||
|
||||
// Write the constructor:
|
||||
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements(), settings);
|
||||
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, init, visitor, globals.getStatements(), settings);
|
||||
constructor.visitCode();
|
||||
constructor.loadThis();
|
||||
constructor.loadArgs();
|
||||
constructor.invokeConstructor(OBJECT_TYPE, CONSTRUCTOR);
|
||||
constructor.invokeConstructor(Type.getType(scriptClassInfo.getBaseClass()), init);
|
||||
constructor.returnValue();
|
||||
constructor.endMethod();
|
||||
|
||||
|
@ -277,7 +301,7 @@ public final class SSource extends AStatement {
|
|||
statementsMethod.endMethod();
|
||||
|
||||
// Write the method defined in the interface:
|
||||
MethodWriter executeMethod = new MethodWriter(Opcodes.ACC_PUBLIC, scriptInterface.getExecuteMethod(), visitor,
|
||||
MethodWriter executeMethod = new MethodWriter(Opcodes.ACC_PUBLIC, scriptClassInfo.getExecuteMethod(), visitor,
|
||||
globals.getStatements(), settings);
|
||||
executeMethod.visitCode();
|
||||
write(executeMethod, globals);
|
||||
|
@ -324,7 +348,7 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
|
||||
// Write any uses$varName methods for used variables
|
||||
for (org.objectweb.asm.commons.Method usesMethod : scriptInterface.getUsesMethods()) {
|
||||
for (org.objectweb.asm.commons.Method usesMethod : scriptClassInfo.getUsesMethods()) {
|
||||
MethodWriter ifaceMethod = new MethodWriter(Opcodes.ACC_PUBLIC, usesMethod, visitor, globals.getStatements(), settings);
|
||||
ifaceMethod.visitCode();
|
||||
ifaceMethod.push(reserved.getUsedVariables().contains(usesMethod.getName().substring("uses$".length())));
|
||||
|
@ -358,12 +382,22 @@ public final class SSource extends AStatement {
|
|||
writer.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
|
||||
}
|
||||
|
||||
for (org.objectweb.asm.commons.Method method : getMethods) {
|
||||
String name = method.getName().substring(3);
|
||||
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
Variable variable = mainMethod.getVariable(null, name);
|
||||
|
||||
writer.loadThis();
|
||||
writer.invokeVirtual(Type.getType(scriptClassInfo.getBaseClass()), method);
|
||||
writer.visitVarInsn(method.getReturnType().getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
}
|
||||
|
||||
for (AStatement statement : statements) {
|
||||
statement.write(writer, globals);
|
||||
}
|
||||
|
||||
if (!methodEscape) {
|
||||
switch (scriptInterface.getExecuteMethod().getReturnType().getSort()) {
|
||||
switch (scriptClassInfo.getExecuteMethod().getReturnType().getSort()) {
|
||||
case org.objectweb.asm.Type.VOID: break;
|
||||
case org.objectweb.asm.Type.BOOLEAN: writer.push(false); break;
|
||||
case org.objectweb.asm.Type.BYTE: writer.push(0); break;
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.painless;
|
|||
import org.elasticsearch.script.ScriptContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -34,10 +35,53 @@ import static org.hamcrest.Matchers.startsWith;
|
|||
/**
|
||||
* Tests for Painless implementing different interfaces.
|
||||
*/
|
||||
public class ImplementInterfacesTests extends ScriptTestCase {
|
||||
public interface NoArgs {
|
||||
String[] ARGUMENTS = new String[] {};
|
||||
Object execute();
|
||||
public class BaseClassTests extends ScriptTestCase {
|
||||
|
||||
public abstract static class Gets {
|
||||
|
||||
private final String testString;
|
||||
private final int testInt;
|
||||
private final Map<String, Object> testMap;
|
||||
|
||||
public Gets(String testString, int testInt, Map<String, Object> testMap) {
|
||||
this.testString = testString;
|
||||
this.testInt = testInt;
|
||||
this.testMap = testMap;
|
||||
}
|
||||
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract Object execute();
|
||||
|
||||
public String getTestString() {
|
||||
return testString;
|
||||
}
|
||||
|
||||
public int getTestInt() {
|
||||
return Math.abs(testInt);
|
||||
}
|
||||
|
||||
public Map<String, Object> getTestMap() {
|
||||
return testMap == null ? new HashMap<>() : testMap;
|
||||
}
|
||||
}
|
||||
|
||||
public void testGets() {
|
||||
Compiler compiler = new Compiler(Gets.class, Definition.BUILTINS);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("s", 1);
|
||||
|
||||
assertEquals(1, ((Gets)scriptEngine.compile(compiler, null, "testInt", emptyMap(), "s", -1, null)).execute());
|
||||
assertEquals(Collections.emptyMap(), ((Gets)scriptEngine.compile(compiler, null, "testMap", emptyMap(), "s", -1, null)).execute());
|
||||
assertEquals(Collections.singletonMap("1", "1"),
|
||||
((Gets)scriptEngine.compile(compiler, null, "testMap", emptyMap(), "s", -1, Collections.singletonMap("1", "1"))).execute());
|
||||
assertEquals("s", ((Gets)scriptEngine.compile(compiler, null, "testString", emptyMap(), "s", -1, null)).execute());
|
||||
assertEquals(map,
|
||||
((Gets)scriptEngine.compile(compiler, null, "testMap.put(testString, testInt); testMap", emptyMap(), "s", -1, null)).execute());
|
||||
}
|
||||
|
||||
public abstract static class NoArgs {
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract Object execute();
|
||||
}
|
||||
public void testNoArgs() {
|
||||
Compiler compiler = new Compiler(NoArgs.class, Definition.BUILTINS);
|
||||
|
@ -59,9 +103,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertThat(debug, containsString("ARETURN"));
|
||||
}
|
||||
|
||||
public interface OneArg {
|
||||
String[] ARGUMENTS = new String[] {"arg"};
|
||||
Object execute(Object arg);
|
||||
public abstract static class OneArg {
|
||||
public static final String[] PARAMETERS = new String[] {"arg"};
|
||||
public abstract Object execute(Object arg);
|
||||
}
|
||||
public void testOneArg() {
|
||||
Compiler compiler = new Compiler(OneArg.class, Definition.BUILTINS);
|
||||
|
@ -80,9 +124,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals("Variable [_score] is not defined.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface ArrayArg {
|
||||
String[] ARGUMENTS = new String[] {"arg"};
|
||||
Object execute(String[] arg);
|
||||
public abstract static class ArrayArg {
|
||||
public static final String[] PARAMETERS = new String[] {"arg"};
|
||||
public abstract Object execute(String[] arg);
|
||||
}
|
||||
public void testArrayArg() {
|
||||
Compiler compiler = new Compiler(ArrayArg.class, Definition.BUILTINS);
|
||||
|
@ -90,9 +134,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(rando, ((ArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new String[] {rando, "foo"}));
|
||||
}
|
||||
|
||||
public interface PrimitiveArrayArg {
|
||||
String[] ARGUMENTS = new String[] {"arg"};
|
||||
Object execute(int[] arg);
|
||||
public abstract static class PrimitiveArrayArg {
|
||||
public static final String[] PARAMETERS = new String[] {"arg"};
|
||||
public abstract Object execute(int[] arg);
|
||||
}
|
||||
public void testPrimitiveArrayArg() {
|
||||
Compiler compiler = new Compiler(PrimitiveArrayArg.class, Definition.BUILTINS);
|
||||
|
@ -100,9 +144,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(rando, ((PrimitiveArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new int[] {rando, 10}));
|
||||
}
|
||||
|
||||
public interface DefArrayArg {
|
||||
String[] ARGUMENTS = new String[] {"arg"};
|
||||
Object execute(Object[] arg);
|
||||
public abstract static class DefArrayArg {
|
||||
public static final String[] PARAMETERS = new String[] {"arg"};
|
||||
public abstract Object execute(Object[] arg);
|
||||
}
|
||||
public void testDefArrayArg() {
|
||||
Compiler compiler = new Compiler(DefArrayArg.class, Definition.BUILTINS);
|
||||
|
@ -114,13 +158,13 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
((DefArrayArg)scriptEngine.compile(compiler, null, "arg[0].length()", emptyMap())).execute(new Object[] {rando, 10}));
|
||||
}
|
||||
|
||||
public interface ManyArgs {
|
||||
String[] ARGUMENTS = new String[] {"a", "b", "c", "d"};
|
||||
Object execute(int a, int b, int c, int d);
|
||||
boolean uses$a();
|
||||
boolean uses$b();
|
||||
boolean uses$c();
|
||||
boolean uses$d();
|
||||
public abstract static class ManyArgs {
|
||||
public static final String[] PARAMETERS = new String[] {"a", "b", "c", "d"};
|
||||
public abstract Object execute(int a, int b, int c, int d);
|
||||
public abstract boolean uses$a();
|
||||
public abstract boolean uses$b();
|
||||
public abstract boolean uses$c();
|
||||
public abstract boolean uses$d();
|
||||
}
|
||||
public void testManyArgs() {
|
||||
Compiler compiler = new Compiler(ManyArgs.class, Definition.BUILTINS);
|
||||
|
@ -146,9 +190,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertTrue(script.uses$d());
|
||||
}
|
||||
|
||||
public interface VarargTest {
|
||||
String[] ARGUMENTS = new String[] {"arg"};
|
||||
Object execute(String... arg);
|
||||
public abstract static class VarargTest {
|
||||
public static final String[] PARAMETERS = new String[] {"arg"};
|
||||
public abstract Object execute(String... arg);
|
||||
}
|
||||
public void testVararg() {
|
||||
Compiler compiler = new Compiler(VarargTest.class, Definition.BUILTINS);
|
||||
|
@ -156,13 +200,13 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
.execute("foo", "bar", "baz"));
|
||||
}
|
||||
|
||||
public interface DefaultMethods {
|
||||
String[] ARGUMENTS = new String[] {"a", "b", "c", "d"};
|
||||
Object execute(int a, int b, int c, int d);
|
||||
default Object executeWithOne() {
|
||||
public abstract static class DefaultMethods {
|
||||
public static final String[] PARAMETERS = new String[] {"a", "b", "c", "d"};
|
||||
public abstract Object execute(int a, int b, int c, int d);
|
||||
public Object executeWithOne() {
|
||||
return execute(1, 1, 1, 1);
|
||||
}
|
||||
default Object executeWithASingleOne(int a, int b, int c) {
|
||||
public Object executeWithASingleOne(int a, int b, int c) {
|
||||
return execute(a, b, c, 1);
|
||||
}
|
||||
}
|
||||
|
@ -176,9 +220,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(7, ((DefaultMethods)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap())).executeWithASingleOne(1, 2, 3));
|
||||
}
|
||||
|
||||
public interface ReturnsVoid {
|
||||
String[] ARGUMENTS = new String[] {"map"};
|
||||
void execute(Map<String, Object> map);
|
||||
public abstract static class ReturnsVoid {
|
||||
public static final String[] PARAMETERS = new String[] {"map"};
|
||||
public abstract void execute(Map<String, Object> map);
|
||||
}
|
||||
public void testReturnsVoid() {
|
||||
Compiler compiler = new Compiler(ReturnsVoid.class, Definition.BUILTINS);
|
||||
|
@ -195,9 +239,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertThat(debug, not(containsString("ACONST_NULL")));
|
||||
}
|
||||
|
||||
public interface ReturnsPrimitiveBoolean {
|
||||
String[] ARGUMENTS = new String[] {};
|
||||
boolean execute();
|
||||
public abstract static class ReturnsPrimitiveBoolean {
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract boolean execute();
|
||||
}
|
||||
public void testReturnsPrimitiveBoolean() {
|
||||
Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, Definition.BUILTINS);
|
||||
|
@ -237,9 +281,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(false, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "int i = 0", emptyMap())).execute());
|
||||
}
|
||||
|
||||
public interface ReturnsPrimitiveInt {
|
||||
String[] ARGUMENTS = new String[] {};
|
||||
int execute();
|
||||
public abstract static class ReturnsPrimitiveInt {
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract int execute();
|
||||
}
|
||||
public void testReturnsPrimitiveInt() {
|
||||
Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, Definition.BUILTINS);
|
||||
|
@ -279,9 +323,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(0, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "int i = 0", emptyMap())).execute());
|
||||
}
|
||||
|
||||
public interface ReturnsPrimitiveFloat {
|
||||
String[] ARGUMENTS = new String[] {};
|
||||
float execute();
|
||||
public abstract static class ReturnsPrimitiveFloat {
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract float execute();
|
||||
}
|
||||
public void testReturnsPrimitiveFloat() {
|
||||
Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, Definition.BUILTINS);
|
||||
|
@ -310,9 +354,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(0.0f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "int i = 0", emptyMap())).execute(), 0);
|
||||
}
|
||||
|
||||
public interface ReturnsPrimitiveDouble {
|
||||
String[] ARGUMENTS = new String[] {};
|
||||
double execute();
|
||||
public abstract static class ReturnsPrimitiveDouble {
|
||||
public static final String[] PARAMETERS = new String[] {};
|
||||
public abstract double execute();
|
||||
}
|
||||
public void testReturnsPrimitiveDouble() {
|
||||
Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, Definition.BUILTINS);
|
||||
|
@ -345,32 +389,34 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
assertEquals(0.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "int i = 0", emptyMap())).execute(), 0);
|
||||
}
|
||||
|
||||
public interface NoArgumentsConstant {
|
||||
Object execute(String foo);
|
||||
public abstract static class NoArgumentsConstant {
|
||||
public abstract Object execute(String foo);
|
||||
}
|
||||
public void testNoArgumentsConstant() {
|
||||
Compiler compiler = new Compiler(NoArgumentsConstant.class, Definition.BUILTINS);
|
||||
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
|
||||
scriptEngine.compile(compiler, null, "1", emptyMap()));
|
||||
assertThat(e.getMessage(), startsWith("Painless needs a constant [String[] ARGUMENTS] on all interfaces it implements with the "
|
||||
assertThat(e.getMessage(), startsWith(
|
||||
"Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
|
||||
+ "names of the method arguments but [" + NoArgumentsConstant.class.getName() + "] doesn't have one."));
|
||||
}
|
||||
|
||||
public interface WrongArgumentsConstant {
|
||||
boolean[] ARGUMENTS = new boolean[] {false};
|
||||
Object execute(String foo);
|
||||
public abstract static class WrongArgumentsConstant {
|
||||
boolean[] PARAMETERS = new boolean[] {false};
|
||||
public abstract Object execute(String foo);
|
||||
}
|
||||
public void testWrongArgumentsConstant() {
|
||||
Compiler compiler = new Compiler(WrongArgumentsConstant.class, Definition.BUILTINS);
|
||||
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
|
||||
scriptEngine.compile(compiler, null, "1", emptyMap()));
|
||||
assertThat(e.getMessage(), startsWith("Painless needs a constant [String[] ARGUMENTS] on all interfaces it implements with the "
|
||||
assertThat(e.getMessage(), startsWith(
|
||||
"Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
|
||||
+ "names of the method arguments but [" + WrongArgumentsConstant.class.getName() + "] doesn't have one."));
|
||||
}
|
||||
|
||||
public interface WrongLengthOfArgumentConstant {
|
||||
String[] ARGUMENTS = new String[] {"foo", "bar"};
|
||||
Object execute(String foo);
|
||||
public abstract static class WrongLengthOfArgumentConstant {
|
||||
public static final String[] PARAMETERS = new String[] {"foo", "bar"};
|
||||
public abstract Object execute(String foo);
|
||||
}
|
||||
public void testWrongLengthOfArgumentConstant() {
|
||||
Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, Definition.BUILTINS);
|
||||
|
@ -380,9 +426,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ WrongLengthOfArgumentConstant.class.getName() + "#execute] takes [1] argument."));
|
||||
}
|
||||
|
||||
public interface UnknownArgType {
|
||||
String[] ARGUMENTS = new String[] {"foo"};
|
||||
Object execute(UnknownArgType foo);
|
||||
public abstract static class UnknownArgType {
|
||||
public static final String[] PARAMETERS = new String[] {"foo"};
|
||||
public abstract Object execute(UnknownArgType foo);
|
||||
}
|
||||
public void testUnknownArgType() {
|
||||
Compiler compiler = new Compiler(UnknownArgType.class, Definition.BUILTINS);
|
||||
|
@ -392,9 +438,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ "that are of whitelisted types.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface UnknownReturnType {
|
||||
String[] ARGUMENTS = new String[] {"foo"};
|
||||
UnknownReturnType execute(String foo);
|
||||
public abstract static class UnknownReturnType {
|
||||
public static final String[] PARAMETERS = new String[] {"foo"};
|
||||
public abstract UnknownReturnType execute(String foo);
|
||||
}
|
||||
public void testUnknownReturnType() {
|
||||
Compiler compiler = new Compiler(UnknownReturnType.class, Definition.BUILTINS);
|
||||
|
@ -404,9 +450,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ "#execute] returns [" + UnknownReturnType.class.getName() + "] which isn't whitelisted.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface UnknownArgTypeInArray {
|
||||
String[] ARGUMENTS = new String[] {"foo"};
|
||||
Object execute(UnknownArgTypeInArray[] foo);
|
||||
public abstract static class UnknownArgTypeInArray {
|
||||
public static final String[] PARAMETERS = new String[] {"foo"};
|
||||
public abstract Object execute(UnknownArgTypeInArray[] foo);
|
||||
}
|
||||
public void testUnknownArgTypeInArray() {
|
||||
Compiler compiler = new Compiler(UnknownArgTypeInArray.class, Definition.BUILTINS);
|
||||
|
@ -416,9 +462,9 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ "arguments that are of whitelisted types.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface TwoExecuteMethods {
|
||||
Object execute();
|
||||
Object execute(boolean foo);
|
||||
public abstract static class TwoExecuteMethods {
|
||||
public abstract Object execute();
|
||||
public abstract Object execute(boolean foo);
|
||||
}
|
||||
public void testTwoExecuteMethods() {
|
||||
Compiler compiler = new Compiler(TwoExecuteMethods.class, Definition.BUILTINS);
|
||||
|
@ -428,21 +474,10 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ TwoExecuteMethods.class.getName() + "] has more than one.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface BadMethod {
|
||||
Object something();
|
||||
}
|
||||
public void testBadMethod() {
|
||||
Compiler compiler = new Compiler(BadMethod.class, Definition.BUILTINS);
|
||||
Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
|
||||
scriptEngine.compile(compiler, null, "null", emptyMap()));
|
||||
assertEquals("Painless can only implement methods named [execute] and [uses$argName] but [" + BadMethod.class.getName()
|
||||
+ "] contains a method named [something]", e.getMessage());
|
||||
}
|
||||
|
||||
public interface BadUsesReturn {
|
||||
String[] ARGUMENTS = new String[] {"foo"};
|
||||
Object execute(String foo);
|
||||
Object uses$foo();
|
||||
public abstract static class BadUsesReturn {
|
||||
public static final String[] PARAMETERS = new String[] {"foo"};
|
||||
public abstract Object execute(String foo);
|
||||
public abstract Object uses$foo();
|
||||
}
|
||||
public void testBadUsesReturn() {
|
||||
Compiler compiler = new Compiler(BadUsesReturn.class, Definition.BUILTINS);
|
||||
|
@ -452,10 +487,10 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ "#uses$foo] returns [java.lang.Object].", e.getMessage());
|
||||
}
|
||||
|
||||
public interface BadUsesParameter {
|
||||
String[] ARGUMENTS = new String[] {"foo", "bar"};
|
||||
Object execute(String foo, String bar);
|
||||
boolean uses$bar(boolean foo);
|
||||
public abstract static class BadUsesParameter {
|
||||
public static final String[] PARAMETERS = new String[] {"foo", "bar"};
|
||||
public abstract Object execute(String foo, String bar);
|
||||
public abstract boolean uses$bar(boolean foo);
|
||||
}
|
||||
public void testBadUsesParameter() {
|
||||
Compiler compiler = new Compiler(BadUsesParameter.class, Definition.BUILTINS);
|
||||
|
@ -465,10 +500,10 @@ public class ImplementInterfacesTests extends ScriptTestCase {
|
|||
+ "#uses$bar] does.", e.getMessage());
|
||||
}
|
||||
|
||||
public interface BadUsesName {
|
||||
String[] ARGUMENTS = new String[] {"foo", "bar"};
|
||||
Object execute(String foo, String bar);
|
||||
boolean uses$baz();
|
||||
public abstract static class BadUsesName {
|
||||
public static final String[] PARAMETERS = new String[] {"foo", "bar"};
|
||||
public abstract Object execute(String foo, String bar);
|
||||
public abstract boolean uses$baz();
|
||||
}
|
||||
public void testBadUsesName() {
|
||||
Compiler compiler = new Compiler(BadUsesName.class, Definition.BUILTINS);
|
|
@ -34,9 +34,7 @@ import org.junit.Before;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
@ -94,11 +92,11 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
// test for ambiguity errors before running the actual script if picky is true
|
||||
if (picky) {
|
||||
Definition definition = Definition.BUILTINS;
|
||||
ScriptInterface scriptInterface = new ScriptInterface(definition, GenericElasticsearchScript.class);
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, GenericElasticsearchScript.class);
|
||||
CompilerSettings pickySettings = new CompilerSettings();
|
||||
pickySettings.setPicky(true);
|
||||
pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings()));
|
||||
Walker.buildPainlessTree(scriptInterface, getTestName(), script, pickySettings,
|
||||
Walker.buildPainlessTree(scriptClassInfo, getTestName(), script, pickySettings,
|
||||
definition, null);
|
||||
}
|
||||
// test actual script execution
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.elasticsearch.painless.FeatureTest;
|
|||
import org.elasticsearch.painless.GenericElasticsearchScript;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.ScriptInterface;
|
||||
import org.elasticsearch.painless.ScriptClassInfo;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.antlr.Walker;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
@ -898,11 +898,11 @@ public class NodeToStringTests extends ESTestCase {
|
|||
}
|
||||
|
||||
private SSource walk(String code) {
|
||||
ScriptInterface scriptInterface = new ScriptInterface(definition, GenericElasticsearchScript.class);
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, GenericElasticsearchScript.class);
|
||||
CompilerSettings compilerSettings = new CompilerSettings();
|
||||
compilerSettings.setRegexesEnabled(true);
|
||||
try {
|
||||
return Walker.buildPainlessTree(scriptInterface, getTestName(), code, compilerSettings,
|
||||
return Walker.buildPainlessTree(scriptClassInfo, getTestName(), code, compilerSettings,
|
||||
definition, null);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Failed to compile: " + code, e);
|
||||
|
|
Loading…
Reference in New Issue