[Painless] Add instance bindings (#34410)
This change adds instance bindings to Painless. This binding allows a whitelisted method to be called on an instance instantiated prior to script compilation. Whitelisting must be done in code as there is no practical way to instantiate a useful instance from a text file (see the tests for an example). Since an instance can be shared by multiple scripts, each method called must be thread-safe.
This commit is contained in:
parent
5f588180f9
commit
1b085252c3
|
@ -66,13 +66,17 @@ public final class Whitelist {
|
||||||
/** The {@link List} of all the whitelisted Painless class bindings. */
|
/** The {@link List} of all the whitelisted Painless class bindings. */
|
||||||
public final List<WhitelistClassBinding> whitelistClassBindings;
|
public final List<WhitelistClassBinding> whitelistClassBindings;
|
||||||
|
|
||||||
|
/** The {@link List} of all the whitelisted Painless instance bindings. */
|
||||||
|
public final List<WhitelistInstanceBinding> whitelistInstanceBindings;
|
||||||
|
|
||||||
/** Standard constructor. All values must be not {@code null}. */
|
/** Standard constructor. All values must be not {@code null}. */
|
||||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses,
|
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses, List<WhitelistMethod> whitelistImportedMethods,
|
||||||
List<WhitelistMethod> whitelistImportedMethods, List<WhitelistClassBinding> whitelistClassBindings) {
|
List<WhitelistClassBinding> whitelistClassBindings, List<WhitelistInstanceBinding> whitelistInstanceBindings) {
|
||||||
|
|
||||||
this.classLoader = Objects.requireNonNull(classLoader);
|
this.classLoader = Objects.requireNonNull(classLoader);
|
||||||
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
||||||
this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
|
this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
|
||||||
this.whitelistClassBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistClassBindings));
|
this.whitelistClassBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistClassBindings));
|
||||||
|
this.whitelistInstanceBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistInstanceBindings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,7 @@ public class WhitelistClassBinding {
|
||||||
/** The method name for this class binding. */
|
/** The method name for this class binding. */
|
||||||
public final String methodName;
|
public final String methodName;
|
||||||
|
|
||||||
/**
|
/** The canonical type name for the return type. */
|
||||||
* The canonical type name for the return type.
|
|
||||||
*/
|
|
||||||
public final String returnCanonicalTypeName;
|
public final String returnCanonicalTypeName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance binding represents a method call that stores state. Each instance binding must provide
|
||||||
|
* exactly one public method name. The canonical type name parameters provided must match those of the
|
||||||
|
* method. The method for an instance binding will target the specified Java instance.
|
||||||
|
*/
|
||||||
|
public class WhitelistInstanceBinding {
|
||||||
|
|
||||||
|
/** Information about where this constructor was whitelisted from. */
|
||||||
|
public final String origin;
|
||||||
|
|
||||||
|
/** The Java instance this instance binding targets. */
|
||||||
|
public final Object targetInstance;
|
||||||
|
|
||||||
|
/** The method name for this class binding. */
|
||||||
|
public final String methodName;
|
||||||
|
|
||||||
|
/** The canonical type name for the return type. */
|
||||||
|
public final String returnCanonicalTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link List} of {@link String}s that are the Painless type names for the parameters of the
|
||||||
|
* constructor which can be used to look up the Java constructor through reflection.
|
||||||
|
*/
|
||||||
|
public final List<String> canonicalTypeNameParameters;
|
||||||
|
|
||||||
|
/** Standard constructor. All values must be not {@code null}. */
|
||||||
|
public WhitelistInstanceBinding(String origin, Object targetInstance,
|
||||||
|
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
|
this.origin = Objects.requireNonNull(origin);
|
||||||
|
this.targetInstance = Objects.requireNonNull(targetInstance);
|
||||||
|
|
||||||
|
this.methodName = Objects.requireNonNull(methodName);
|
||||||
|
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||||
|
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Loads and creates a {@link Whitelist} from one to many text files. */
|
/** Loads and creates a {@link Whitelist} from one to many text files. */
|
||||||
|
@ -392,7 +393,7 @@ public final class WhitelistLoader {
|
||||||
|
|
||||||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
||||||
|
|
||||||
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistClassBindings);
|
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistClassBindings, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhitelistLoader() {}
|
private WhitelistLoader() {}
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.elasticsearch.bootstrap.BootstrapInfo;
|
import org.elasticsearch.bootstrap.BootstrapInfo;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
|
||||||
import org.elasticsearch.painless.antlr.Walker;
|
import org.elasticsearch.painless.antlr.Walker;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookup;
|
import org.elasticsearch.painless.lookup.PainlessLookup;
|
||||||
import org.elasticsearch.painless.node.SSource;
|
import org.elasticsearch.painless.node.SSource;
|
||||||
|
@ -222,8 +221,8 @@ final class Compiler {
|
||||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
|
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
|
||||||
SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup,
|
SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup,
|
||||||
null);
|
null);
|
||||||
Map<String, LocalMethod> localMethods = root.analyze(painlessLookup);
|
root.analyze(painlessLookup);
|
||||||
root.write();
|
Map<String, Object> statics = root.write();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
|
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
|
||||||
|
@ -231,7 +230,10 @@ final class Compiler {
|
||||||
clazz.getField("$SOURCE").set(null, source);
|
clazz.getField("$SOURCE").set(null, source);
|
||||||
clazz.getField("$STATEMENTS").set(null, root.getStatements());
|
clazz.getField("$STATEMENTS").set(null, root.getStatements());
|
||||||
clazz.getField("$DEFINITION").set(null, painlessLookup);
|
clazz.getField("$DEFINITION").set(null, painlessLookup);
|
||||||
clazz.getField("$LOCALS").set(null, localMethods);
|
|
||||||
|
for (Map.Entry<String, Object> statik : statics.entrySet()) {
|
||||||
|
clazz.getField(statik.getKey()).set(null, statik.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
return clazz.getConstructors()[0];
|
return clazz.getConstructors()[0];
|
||||||
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
||||||
|
|
|
@ -31,7 +31,8 @@ import java.util.Map;
|
||||||
public class Globals {
|
public class Globals {
|
||||||
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
||||||
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
||||||
private final Map<String,Class<?>> bindings = new HashMap<>();
|
private final Map<String,Class<?>> classBindings = new HashMap<>();
|
||||||
|
private final Map<Object,String> instanceBindings = new HashMap<>();
|
||||||
private final BitSet statements;
|
private final BitSet statements;
|
||||||
|
|
||||||
/** Create a new Globals from the set of statement boundaries */
|
/** Create a new Globals from the set of statement boundaries */
|
||||||
|
@ -56,14 +57,19 @@ public class Globals {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds a new binding to be written as a local variable */
|
/** Adds a new class binding to be written as a local variable */
|
||||||
public String addBinding(Class<?> type) {
|
public String addClassBinding(Class<?> type) {
|
||||||
String name = "$binding$" + bindings.size();
|
String name = "$class_binding$" + classBindings.size();
|
||||||
bindings.put(name, type);
|
classBindings.put(name, type);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Adds a new binding to be written as a local variable */
|
||||||
|
public String addInstanceBinding(Object instance) {
|
||||||
|
return instanceBindings.computeIfAbsent(instance, key -> "$instance_binding$" + instanceBindings.size());
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the current synthetic methods */
|
/** Returns the current synthetic methods */
|
||||||
public Map<String,SFunction> getSyntheticMethods() {
|
public Map<String,SFunction> getSyntheticMethods() {
|
||||||
return syntheticMethods;
|
return syntheticMethods;
|
||||||
|
@ -75,8 +81,13 @@ public class Globals {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the current bindings */
|
/** Returns the current bindings */
|
||||||
public Map<String,Class<?>> getBindings() {
|
public Map<String,Class<?>> getClassBindings() {
|
||||||
return bindings;
|
return classBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the current bindings */
|
||||||
|
public Map<Object,String> getInstanceBindings() {
|
||||||
|
return instanceBindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the set of statement boundaries */
|
/** Returns the set of statement boundaries */
|
||||||
|
|
|
@ -60,7 +60,6 @@ public class PainlessClassBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|
||||||
return Objects.hash(javaConstructor, javaMethod, returnType, typeParameters);
|
return Objects.hash(javaConstructor, javaMethod, returnType, typeParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.lookup;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class PainlessInstanceBinding {
|
||||||
|
|
||||||
|
public final Object targetInstance;
|
||||||
|
public final Method javaMethod;
|
||||||
|
|
||||||
|
public final Class<?> returnType;
|
||||||
|
public final List<Class<?>> typeParameters;
|
||||||
|
|
||||||
|
PainlessInstanceBinding(Object targetInstance, Method javaMethod, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
|
this.targetInstance = targetInstance;
|
||||||
|
this.javaMethod = javaMethod;
|
||||||
|
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.typeParameters = typeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object object) {
|
||||||
|
if (this == object) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object == null || getClass() != object.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessInstanceBinding that = (PainlessInstanceBinding)object;
|
||||||
|
|
||||||
|
return targetInstance == that.targetInstance &&
|
||||||
|
Objects.equals(javaMethod, that.javaMethod) &&
|
||||||
|
Objects.equals(returnType, that.returnType) &&
|
||||||
|
Objects.equals(typeParameters, that.typeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(targetInstance, javaMethod, returnType, typeParameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,13 +40,15 @@ public final class PainlessLookup {
|
||||||
|
|
||||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||||
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
||||||
|
private final Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings;
|
||||||
|
|
||||||
PainlessLookup(
|
PainlessLookup(
|
||||||
Map<String, Class<?>> javaClassNamesToClasses,
|
Map<String, Class<?>> javaClassNamesToClasses,
|
||||||
Map<String, Class<?>> canonicalClassNamesToClasses,
|
Map<String, Class<?>> canonicalClassNamesToClasses,
|
||||||
Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
||||||
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
|
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
|
||||||
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings) {
|
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings,
|
||||||
|
Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings) {
|
||||||
|
|
||||||
Objects.requireNonNull(javaClassNamesToClasses);
|
Objects.requireNonNull(javaClassNamesToClasses);
|
||||||
Objects.requireNonNull(canonicalClassNamesToClasses);
|
Objects.requireNonNull(canonicalClassNamesToClasses);
|
||||||
|
@ -54,6 +56,7 @@ public final class PainlessLookup {
|
||||||
|
|
||||||
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
|
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
|
||||||
Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings);
|
Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings);
|
||||||
|
Objects.requireNonNull(painlessMethodKeysToPainlessInstanceBindings);
|
||||||
|
|
||||||
this.javaClassNamesToClasses = javaClassNamesToClasses;
|
this.javaClassNamesToClasses = javaClassNamesToClasses;
|
||||||
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
||||||
|
@ -61,6 +64,7 @@ public final class PainlessLookup {
|
||||||
|
|
||||||
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
|
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
|
||||||
this.painlessMethodKeysToPainlessClassBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessClassBindings);
|
this.painlessMethodKeysToPainlessClassBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessClassBindings);
|
||||||
|
this.painlessMethodKeysToPainlessInstanceBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessInstanceBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?> javaClassNameToClass(String javaClassName) {
|
public Class<?> javaClassNameToClass(String javaClassName) {
|
||||||
|
@ -200,6 +204,14 @@ public final class PainlessLookup {
|
||||||
return painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
return painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessInstanceBinding lookupPainlessInstanceBinding(String methodName, int arity) {
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
|
||||||
|
String painlessMethodKey = buildPainlessMethodKey(methodName, arity);
|
||||||
|
|
||||||
|
return painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
||||||
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.painless.spi.WhitelistClass;
|
||||||
import org.elasticsearch.painless.spi.WhitelistClassBinding;
|
import org.elasticsearch.painless.spi.WhitelistClassBinding;
|
||||||
import org.elasticsearch.painless.spi.WhitelistConstructor;
|
import org.elasticsearch.painless.spi.WhitelistConstructor;
|
||||||
import org.elasticsearch.painless.spi.WhitelistField;
|
import org.elasticsearch.painless.spi.WhitelistField;
|
||||||
|
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||||
import org.elasticsearch.painless.spi.WhitelistMethod;
|
import org.elasticsearch.painless.spi.WhitelistMethod;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
@ -50,10 +51,11 @@ import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCan
|
||||||
|
|
||||||
public final class PainlessLookupBuilder {
|
public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
private static final Map<PainlessConstructor , PainlessConstructor> painlessConstructorCache = new HashMap<>();
|
private static final Map<PainlessConstructor , PainlessConstructor> painlessConstructorCache = new HashMap<>();
|
||||||
private static final Map<PainlessMethod , PainlessMethod> painlessMethodCache = new HashMap<>();
|
private static final Map<PainlessMethod , PainlessMethod> painlessMethodCache = new HashMap<>();
|
||||||
private static final Map<PainlessField , PainlessField> painlessFieldCache = new HashMap<>();
|
private static final Map<PainlessField , PainlessField> painlessFieldCache = new HashMap<>();
|
||||||
private static final Map<PainlessClassBinding, PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
|
private static final Map<PainlessClassBinding , PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
|
||||||
|
private static final Map<PainlessInstanceBinding, PainlessInstanceBinding> painlessInstanceBindingCache = new HashMap<>();
|
||||||
|
|
||||||
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
|
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
|
||||||
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
|
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
|
||||||
|
@ -108,9 +110,15 @@ public final class PainlessLookupBuilder {
|
||||||
for (WhitelistClassBinding whitelistClassBinding : whitelist.whitelistClassBindings) {
|
for (WhitelistClassBinding whitelistClassBinding : whitelist.whitelistClassBindings) {
|
||||||
origin = whitelistClassBinding.origin;
|
origin = whitelistClassBinding.origin;
|
||||||
painlessLookupBuilder.addPainlessClassBinding(
|
painlessLookupBuilder.addPainlessClassBinding(
|
||||||
whitelist.classLoader, whitelistClassBinding.targetJavaClassName,
|
whitelist.classLoader, whitelistClassBinding.targetJavaClassName, whitelistClassBinding.methodName,
|
||||||
whitelistClassBinding.methodName, whitelistClassBinding.returnCanonicalTypeName,
|
whitelistClassBinding.returnCanonicalTypeName, whitelistClassBinding.canonicalTypeNameParameters);
|
||||||
whitelistClassBinding.canonicalTypeNameParameters);
|
}
|
||||||
|
|
||||||
|
for (WhitelistInstanceBinding whitelistInstanceBinding : whitelist.whitelistInstanceBindings) {
|
||||||
|
origin = whitelistInstanceBinding.origin;
|
||||||
|
painlessLookupBuilder.addPainlessInstanceBinding(
|
||||||
|
whitelistInstanceBinding.targetInstance, whitelistInstanceBinding.methodName,
|
||||||
|
whitelistInstanceBinding.returnCanonicalTypeName, whitelistInstanceBinding.canonicalTypeNameParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
|
@ -134,6 +142,7 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||||
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
||||||
|
private final Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings;
|
||||||
|
|
||||||
public PainlessLookupBuilder() {
|
public PainlessLookupBuilder() {
|
||||||
javaClassNamesToClasses = new HashMap<>();
|
javaClassNamesToClasses = new HashMap<>();
|
||||||
|
@ -142,6 +151,7 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
|
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
|
||||||
painlessMethodKeysToPainlessClassBindings = new HashMap<>();
|
painlessMethodKeysToPainlessClassBindings = new HashMap<>();
|
||||||
|
painlessMethodKeysToPainlessInstanceBindings = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> canonicalTypeNameToType(String canonicalTypeName) {
|
private Class<?> canonicalTypeNameToType(String canonicalTypeName) {
|
||||||
|
@ -763,6 +773,10 @@ public final class PainlessLookupBuilder {
|
||||||
throw new IllegalArgumentException("imported method and class binding cannot have the same name [" + methodName + "]");
|
throw new IllegalArgumentException("imported method and class binding cannot have the same name [" + methodName + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
|
||||||
|
throw new IllegalArgumentException("imported method and instance binding cannot have the same name [" + methodName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
MethodHandle methodHandle;
|
MethodHandle methodHandle;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -783,7 +797,7 @@ public final class PainlessLookupBuilder {
|
||||||
painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey, newImportedPainlessMethod);
|
painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey, newImportedPainlessMethod);
|
||||||
} else if (newImportedPainlessMethod.equals(existingImportedPainlessMethod) == false) {
|
} else if (newImportedPainlessMethod.equals(existingImportedPainlessMethod) == false) {
|
||||||
throw new IllegalArgumentException("cannot add imported methods with the same name and arity " +
|
throw new IllegalArgumentException("cannot add imported methods with the same name and arity " +
|
||||||
"but are not equivalent for methods " +
|
"but do not have equivalent methods " +
|
||||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||||
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||||
|
@ -942,6 +956,11 @@ public final class PainlessLookupBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isValidType(returnType) == false) {
|
||||||
|
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for class binding " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||||
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||||
|
@ -955,6 +974,15 @@ public final class PainlessLookupBuilder {
|
||||||
throw new IllegalArgumentException("class binding and imported method cannot have the same name [" + methodName + "]");
|
throw new IllegalArgumentException("class binding and imported method cannot have the same name [" + methodName + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
|
||||||
|
throw new IllegalArgumentException("class binding and instance binding cannot have the same name [" + methodName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Modifier.isStatic(javaMethod.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("class binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
|
||||||
|
}
|
||||||
|
|
||||||
PainlessClassBinding existingPainlessClassBinding = painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
PainlessClassBinding existingPainlessClassBinding = painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
||||||
PainlessClassBinding newPainlessClassBinding =
|
PainlessClassBinding newPainlessClassBinding =
|
||||||
new PainlessClassBinding(javaConstructor, javaMethod, returnType, typeParameters);
|
new PainlessClassBinding(javaConstructor, javaMethod, returnType, typeParameters);
|
||||||
|
@ -962,9 +990,9 @@ public final class PainlessLookupBuilder {
|
||||||
if (existingPainlessClassBinding == null) {
|
if (existingPainlessClassBinding == null) {
|
||||||
newPainlessClassBinding = painlessClassBindingCache.computeIfAbsent(newPainlessClassBinding, key -> key);
|
newPainlessClassBinding = painlessClassBindingCache.computeIfAbsent(newPainlessClassBinding, key -> key);
|
||||||
painlessMethodKeysToPainlessClassBindings.put(painlessMethodKey, newPainlessClassBinding);
|
painlessMethodKeysToPainlessClassBindings.put(painlessMethodKey, newPainlessClassBinding);
|
||||||
} else if (newPainlessClassBinding.equals(existingPainlessClassBinding)) {
|
} else if (newPainlessClassBinding.equals(existingPainlessClassBinding) == false) {
|
||||||
throw new IllegalArgumentException("cannot add class bindings with the same name and arity " +
|
throw new IllegalArgumentException("cannot add class bindings with the same name and arity " +
|
||||||
"but are not equivalent for methods " +
|
"but do not have equivalent methods " +
|
||||||
"[[" + targetCanonicalClassName + "], " +
|
"[[" + targetCanonicalClassName + "], " +
|
||||||
"[" + methodName + "], " +
|
"[" + methodName + "], " +
|
||||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||||
|
@ -976,6 +1004,136 @@ public final class PainlessLookupBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addPainlessInstanceBinding(Object targetInstance,
|
||||||
|
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(targetInstance);
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
Objects.requireNonNull(returnCanonicalTypeName);
|
||||||
|
Objects.requireNonNull(canonicalTypeNameParameters);
|
||||||
|
|
||||||
|
Class<?> targetClass = targetInstance.getClass();
|
||||||
|
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||||
|
List<Class<?>> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());
|
||||||
|
|
||||||
|
for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
|
||||||
|
Class<?> typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);
|
||||||
|
|
||||||
|
if (typeParameter == null) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for instance binding " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
typeParameters.add(typeParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
||||||
|
|
||||||
|
if (returnType == null) {
|
||||||
|
throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for class binding " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPainlessInstanceBinding(Object targetInstance, String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
|
Objects.requireNonNull(targetInstance);
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
Objects.requireNonNull(returnType);
|
||||||
|
Objects.requireNonNull(typeParameters);
|
||||||
|
|
||||||
|
Class<?> targetClass = targetInstance.getClass();
|
||||||
|
|
||||||
|
if (targetClass == def.class) {
|
||||||
|
throw new IllegalArgumentException("cannot add instance binding as reserved class [" + DEF_CLASS_NAME + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||||
|
Class<?> existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());
|
||||||
|
|
||||||
|
if (existingTargetClass == null) {
|
||||||
|
javaClassNamesToClasses.put(targetClass.getName(), targetClass);
|
||||||
|
} else if (existingTargetClass != targetClass) {
|
||||||
|
throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
|
||||||
|
"cannot represent multiple java classes with the same name from different class loaders");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"invalid method name [" + methodName + "] for instance binding [" + targetCanonicalClassName + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
int typeParametersSize = typeParameters.size();
|
||||||
|
List<Class<?>> javaTypeParameters = new ArrayList<>(typeParametersSize);
|
||||||
|
|
||||||
|
for (Class<?> typeParameter : typeParameters) {
|
||||||
|
if (isValidType(typeParameter) == false) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
|
||||||
|
"not found for instance binding [[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
javaTypeParameters.add(typeToJavaType(typeParameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidType(returnType) == false) {
|
||||||
|
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for imported method " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Method javaMethod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
|
||||||
|
} catch (NoSuchMethodException nsme) {
|
||||||
|
throw new IllegalArgumentException("instance binding reflection object [[" + targetCanonicalClassName + "], " +
|
||||||
|
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||||
|
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||||
|
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||||
|
"for instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Modifier.isStatic(javaMethod.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
|
||||||
|
}
|
||||||
|
|
||||||
|
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
|
||||||
|
|
||||||
|
if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) {
|
||||||
|
throw new IllegalArgumentException("instance binding and imported method cannot have the same name [" + methodName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (painlessMethodKeysToPainlessClassBindings.containsKey(painlessMethodKey)) {
|
||||||
|
throw new IllegalArgumentException("instance binding and class binding cannot have the same name [" + methodName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessInstanceBinding existingPainlessInstanceBinding = painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
|
||||||
|
PainlessInstanceBinding newPainlessInstanceBinding =
|
||||||
|
new PainlessInstanceBinding(targetInstance, javaMethod, returnType, typeParameters);
|
||||||
|
|
||||||
|
if (existingPainlessInstanceBinding == null) {
|
||||||
|
newPainlessInstanceBinding = painlessInstanceBindingCache.computeIfAbsent(newPainlessInstanceBinding, key -> key);
|
||||||
|
painlessMethodKeysToPainlessInstanceBindings.put(painlessMethodKey, newPainlessInstanceBinding);
|
||||||
|
} else if (newPainlessInstanceBinding.equals(existingPainlessInstanceBinding) == false) {
|
||||||
|
throw new IllegalArgumentException("cannot add instances bindings with the same name and arity " +
|
||||||
|
"but do not have equivalent methods " +
|
||||||
|
"[[" + targetCanonicalClassName + "], " +
|
||||||
|
"[" + methodName + "], " +
|
||||||
|
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||||
|
"[[" + targetCanonicalClassName + "], " +
|
||||||
|
"[" + methodName + "], " +
|
||||||
|
"[" + typeToCanonicalTypeName(existingPainlessInstanceBinding.returnType) + "], " +
|
||||||
|
typesToCanonicalTypeNames(existingPainlessInstanceBinding.typeParameters) + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessLookup build() {
|
public PainlessLookup build() {
|
||||||
copyPainlessClassMembers();
|
copyPainlessClassMembers();
|
||||||
cacheRuntimeHandles();
|
cacheRuntimeHandles();
|
||||||
|
@ -1003,8 +1161,13 @@ public final class PainlessLookupBuilder {
|
||||||
"must have the same classes as the keys of classes to painless classes");
|
"must have the same classes as the keys of classes to painless classes");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PainlessLookup(javaClassNamesToClasses, canonicalClassNamesToClasses, classesToPainlessClasses,
|
return new PainlessLookup(
|
||||||
painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessClassBindings);
|
javaClassNamesToClasses,
|
||||||
|
canonicalClassNamesToClasses,
|
||||||
|
classesToPainlessClasses,
|
||||||
|
painlessMethodKeysToImportedPainlessMethods,
|
||||||
|
painlessMethodKeysToPainlessClassBindings,
|
||||||
|
painlessMethodKeysToPainlessInstanceBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyPainlessClassMembers() {
|
private void copyPainlessClassMembers() {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.painless.Locals.LocalMethod;
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
import org.elasticsearch.painless.lookup.PainlessClassBinding;
|
import org.elasticsearch.painless.lookup.PainlessClassBinding;
|
||||||
|
import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
|
||||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||||
import org.objectweb.asm.Label;
|
import org.objectweb.asm.Label;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
@ -48,6 +49,7 @@ public final class ECallLocal extends AExpression {
|
||||||
private LocalMethod localMethod = null;
|
private LocalMethod localMethod = null;
|
||||||
private PainlessMethod importedMethod = null;
|
private PainlessMethod importedMethod = null;
|
||||||
private PainlessClassBinding classBinding = null;
|
private PainlessClassBinding classBinding = null;
|
||||||
|
private PainlessInstanceBinding instanceBinding = null;
|
||||||
|
|
||||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||||
super(location);
|
super(location);
|
||||||
|
@ -74,8 +76,12 @@ public final class ECallLocal extends AExpression {
|
||||||
classBinding = locals.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size());
|
classBinding = locals.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size());
|
||||||
|
|
||||||
if (classBinding == null) {
|
if (classBinding == null) {
|
||||||
throw createError(
|
instanceBinding = locals.getPainlessLookup().lookupPainlessInstanceBinding(name, arguments.size());
|
||||||
new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
|
||||||
|
if (instanceBinding == null) {
|
||||||
|
throw createError(
|
||||||
|
new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +97,9 @@ public final class ECallLocal extends AExpression {
|
||||||
} else if (classBinding != null) {
|
} else if (classBinding != null) {
|
||||||
typeParameters = new ArrayList<>(classBinding.typeParameters);
|
typeParameters = new ArrayList<>(classBinding.typeParameters);
|
||||||
actual = classBinding.returnType;
|
actual = classBinding.returnType;
|
||||||
|
} else if (instanceBinding != null) {
|
||||||
|
typeParameters = new ArrayList<>(instanceBinding.typeParameters);
|
||||||
|
actual = instanceBinding.returnType;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Illegal tree structure.");
|
throw new IllegalStateException("Illegal tree structure.");
|
||||||
}
|
}
|
||||||
|
@ -125,7 +134,7 @@ public final class ECallLocal extends AExpression {
|
||||||
writer.invokeStatic(Type.getType(importedMethod.targetClass),
|
writer.invokeStatic(Type.getType(importedMethod.targetClass),
|
||||||
new Method(importedMethod.javaMethod.getName(), importedMethod.methodType.toMethodDescriptorString()));
|
new Method(importedMethod.javaMethod.getName(), importedMethod.methodType.toMethodDescriptorString()));
|
||||||
} else if (classBinding != null) {
|
} else if (classBinding != null) {
|
||||||
String name = globals.addBinding(classBinding.javaConstructor.getDeclaringClass());
|
String name = globals.addClassBinding(classBinding.javaConstructor.getDeclaringClass());
|
||||||
Type type = Type.getType(classBinding.javaConstructor.getDeclaringClass());
|
Type type = Type.getType(classBinding.javaConstructor.getDeclaringClass());
|
||||||
int javaConstructorParameterCount = classBinding.javaConstructor.getParameterCount();
|
int javaConstructorParameterCount = classBinding.javaConstructor.getParameterCount();
|
||||||
|
|
||||||
|
@ -154,6 +163,18 @@ public final class ECallLocal extends AExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
|
writer.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
|
||||||
|
} else if (instanceBinding != null) {
|
||||||
|
String name = globals.addInstanceBinding(instanceBinding.targetInstance);
|
||||||
|
Type type = Type.getType(instanceBinding.targetInstance.getClass());
|
||||||
|
|
||||||
|
writer.loadThis();
|
||||||
|
writer.getStatic(CLASS_TYPE, name, type);
|
||||||
|
|
||||||
|
for (int argument = 0; argument < instanceBinding.javaMethod.getParameterCount(); ++argument) {
|
||||||
|
arguments.get(argument).write(writer, globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.invokeVirtual(type, Method.getMethod(instanceBinding.javaMethod));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Illegal tree structure.");
|
throw new IllegalStateException("Illegal tree structure.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ public final class SSource extends AStatement {
|
||||||
throw new IllegalStateException("Illegal tree structure.");
|
throw new IllegalStateException("Illegal tree structure.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, LocalMethod> analyze(PainlessLookup painlessLookup) {
|
public void analyze(PainlessLookup painlessLookup) {
|
||||||
Map<String, LocalMethod> methods = new HashMap<>();
|
Map<String, LocalMethod> methods = new HashMap<>();
|
||||||
|
|
||||||
for (SFunction function : functions) {
|
for (SFunction function : functions) {
|
||||||
|
@ -180,8 +180,6 @@ public final class SSource extends AStatement {
|
||||||
|
|
||||||
Locals locals = Locals.newProgramScope(painlessLookup, methods.values());
|
Locals locals = Locals.newProgramScope(painlessLookup, methods.values());
|
||||||
analyze(locals);
|
analyze(locals);
|
||||||
|
|
||||||
return locals.getMethods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -228,7 +226,7 @@ public final class SSource extends AStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write() {
|
public Map<String, Object> write() {
|
||||||
// Create the ClassWriter.
|
// Create the ClassWriter.
|
||||||
|
|
||||||
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
||||||
|
@ -359,13 +357,20 @@ public final class SSource extends AStatement {
|
||||||
clinit.endMethod();
|
clinit.endMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write binding variables
|
// Write class binding variables
|
||||||
for (Map.Entry<String, Class<?>> binding : globals.getBindings().entrySet()) {
|
for (Map.Entry<String, Class<?>> classBinding : globals.getClassBindings().entrySet()) {
|
||||||
String name = binding.getKey();
|
String name = classBinding.getKey();
|
||||||
String descriptor = Type.getType(binding.getValue()).getDescriptor();
|
String descriptor = Type.getType(classBinding.getValue()).getDescriptor();
|
||||||
visitor.visitField(Opcodes.ACC_PRIVATE, name, descriptor, null, null).visitEnd();
|
visitor.visitField(Opcodes.ACC_PRIVATE, name, descriptor, null, null).visitEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write instance binding variables
|
||||||
|
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
|
||||||
|
String name = instanceBinding.getValue();
|
||||||
|
String descriptor = Type.getType(instanceBinding.getKey().getClass()).getDescriptor();
|
||||||
|
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, name, descriptor, null, null).visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
// Write any needsVarName methods for used variables
|
// Write any needsVarName methods for used variables
|
||||||
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
|
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
|
||||||
String name = needsMethod.getName();
|
String name = needsMethod.getName();
|
||||||
|
@ -382,6 +387,15 @@ public final class SSource extends AStatement {
|
||||||
|
|
||||||
visitor.visitEnd();
|
visitor.visitEnd();
|
||||||
bytes = writer.toByteArray();
|
bytes = writer.toByteArray();
|
||||||
|
|
||||||
|
Map<String, Object> statics = new HashMap<>();
|
||||||
|
statics.put("$LOCALS", mainMethod.getMethods());
|
||||||
|
|
||||||
|
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
|
||||||
|
statics.put(instanceBinding.getValue(), instanceBinding.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return statics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,14 +20,32 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.elasticsearch.painless.spi.Whitelist;
|
import org.elasticsearch.painless.spi.Whitelist;
|
||||||
|
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||||
import org.elasticsearch.script.ScriptContext;
|
import org.elasticsearch.script.ScriptContext;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class BindingsTests extends ScriptTestCase {
|
public class BindingsTests extends ScriptTestCase {
|
||||||
|
|
||||||
|
public static class InstanceBindingTestClass {
|
||||||
|
private int value;
|
||||||
|
|
||||||
|
public InstanceBindingTestClass(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstanceBindingValue(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInstanceBindingValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class BindingsTestScript {
|
public abstract static class BindingsTestScript {
|
||||||
public static final String[] PARAMETERS = { "test", "bound" };
|
public static final String[] PARAMETERS = { "test", "bound" };
|
||||||
public abstract int execute(int test, int bound);
|
public abstract int execute(int test, int bound);
|
||||||
|
@ -40,15 +58,29 @@ public class BindingsTests extends ScriptTestCase {
|
||||||
@Override
|
@Override
|
||||||
protected Map<ScriptContext<?>, List<Whitelist>> scriptContexts() {
|
protected Map<ScriptContext<?>, List<Whitelist>> scriptContexts() {
|
||||||
Map<ScriptContext<?>, List<Whitelist>> contexts = super.scriptContexts();
|
Map<ScriptContext<?>, List<Whitelist>> contexts = super.scriptContexts();
|
||||||
contexts.put(BindingsTestScript.CONTEXT, Whitelist.BASE_WHITELISTS);
|
List<Whitelist> whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS);
|
||||||
|
|
||||||
|
InstanceBindingTestClass instanceBindingTestClass = new InstanceBindingTestClass(1);
|
||||||
|
WhitelistInstanceBinding getter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||||
|
"setInstanceBindingValue", "void", Collections.singletonList("int"));
|
||||||
|
WhitelistInstanceBinding setter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||||
|
"getInstanceBindingValue", "int", Collections.emptyList());
|
||||||
|
List<WhitelistInstanceBinding> instanceBindingsList = new ArrayList<>();
|
||||||
|
instanceBindingsList.add(getter);
|
||||||
|
instanceBindingsList.add(setter);
|
||||||
|
Whitelist instanceBindingsWhitelist = new Whitelist(instanceBindingTestClass.getClass().getClassLoader(),
|
||||||
|
Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), instanceBindingsList);
|
||||||
|
whitelists.add(instanceBindingsWhitelist);
|
||||||
|
|
||||||
|
contexts.put(BindingsTestScript.CONTEXT, whitelists);
|
||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBasicBinding() {
|
public void testBasicClassBinding() {
|
||||||
assertEquals(15, exec("testAddWithState(4, 5, 6, 0.0)"));
|
assertEquals(15, exec("testAddWithState(4, 5, 6, 0.0)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRepeatedBinding() {
|
public void testRepeatedClassBinding() {
|
||||||
String script = "testAddWithState(4, 5, test, 0.0)";
|
String script = "testAddWithState(4, 5, test, 0.0)";
|
||||||
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||||
BindingsTestScript executableScript = factory.newInstance();
|
BindingsTestScript executableScript = factory.newInstance();
|
||||||
|
@ -58,7 +90,7 @@ public class BindingsTests extends ScriptTestCase {
|
||||||
assertEquals(16, executableScript.execute(7, 0));
|
assertEquals(16, executableScript.execute(7, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBoundBinding() {
|
public void testBoundClassBinding() {
|
||||||
String script = "testAddWithState(4, bound, test, 0.0)";
|
String script = "testAddWithState(4, bound, test, 0.0)";
|
||||||
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||||
BindingsTestScript executableScript = factory.newInstance();
|
BindingsTestScript executableScript = factory.newInstance();
|
||||||
|
@ -66,4 +98,21 @@ public class BindingsTests extends ScriptTestCase {
|
||||||
assertEquals(10, executableScript.execute(5, 1));
|
assertEquals(10, executableScript.execute(5, 1));
|
||||||
assertEquals(9, executableScript.execute(4, 2));
|
assertEquals(9, executableScript.execute(4, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testInstanceBinding() {
|
||||||
|
String script = "getInstanceBindingValue() + test + bound";
|
||||||
|
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||||
|
BindingsTestScript executableScript = factory.newInstance();
|
||||||
|
assertEquals(3, executableScript.execute(1, 1));
|
||||||
|
|
||||||
|
script = "setInstanceBindingValue(test + bound); getInstanceBindingValue()";
|
||||||
|
factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||||
|
executableScript = factory.newInstance();
|
||||||
|
assertEquals(4, executableScript.execute(-2, 6));
|
||||||
|
|
||||||
|
script = "getInstanceBindingValue() + test + bound";
|
||||||
|
factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||||
|
executableScript = factory.newInstance();
|
||||||
|
assertEquals(8, executableScript.execute(-2, 6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,12 @@ package org.elasticsearch.example.painlesswhitelist;
|
||||||
|
|
||||||
import org.elasticsearch.painless.spi.PainlessExtension;
|
import org.elasticsearch.painless.spi.PainlessExtension;
|
||||||
import org.elasticsearch.painless.spi.Whitelist;
|
import org.elasticsearch.painless.spi.Whitelist;
|
||||||
|
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||||
import org.elasticsearch.painless.spi.WhitelistLoader;
|
import org.elasticsearch.painless.spi.WhitelistLoader;
|
||||||
import org.elasticsearch.script.FieldScript;
|
import org.elasticsearch.script.FieldScript;
|
||||||
import org.elasticsearch.script.ScriptContext;
|
import org.elasticsearch.script.ScriptContext;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -37,6 +39,14 @@ public class ExampleWhitelistExtension implements PainlessExtension {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
|
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
|
||||||
return Collections.singletonMap(FieldScript.CONTEXT, Collections.singletonList(WHITELIST));
|
ExampleWhitelistedInstance ewi = new ExampleWhitelistedInstance(1);
|
||||||
|
WhitelistInstanceBinding addValue = new WhitelistInstanceBinding("example addValue", ewi,
|
||||||
|
"addValue", "int", Collections.singletonList("int"));
|
||||||
|
WhitelistInstanceBinding getValue = new WhitelistInstanceBinding("example getValue", ewi,
|
||||||
|
"getValue", "int", Collections.emptyList());
|
||||||
|
Whitelist instanceWhitelist = new Whitelist(ewi.getClass().getClassLoader(), Collections.emptyList(),
|
||||||
|
Collections.emptyList(), Collections.emptyList(), Arrays.asList(addValue, getValue));
|
||||||
|
|
||||||
|
return Collections.singletonMap(FieldScript.CONTEXT, Arrays.asList(WHITELIST, instanceWhitelist));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.example.painlesswhitelist;
|
||||||
|
|
||||||
|
public class ExampleWhitelistedInstance {
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
public ExampleWhitelistedInstance(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int addValue(int value) {
|
||||||
|
return this.value + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Example tests using an instance binding
|
||||||
|
|
||||||
|
"custom instance binding":
|
||||||
|
- do:
|
||||||
|
index:
|
||||||
|
index: test
|
||||||
|
type: test
|
||||||
|
id: 1
|
||||||
|
body: { "num1": 1 }
|
||||||
|
- do:
|
||||||
|
indices.refresh: {}
|
||||||
|
|
||||||
|
- do:
|
||||||
|
index: test
|
||||||
|
search:
|
||||||
|
body:
|
||||||
|
query:
|
||||||
|
match_all: {}
|
||||||
|
script_fields:
|
||||||
|
sNum1:
|
||||||
|
script:
|
||||||
|
source: "addValue((int)doc['num1'][0])"
|
||||||
|
lang: painless
|
||||||
|
|
||||||
|
- match: { hits.total: 1 }
|
||||||
|
- match: { hits.hits.0.fields.sNum1.0: 2 }
|
||||||
|
|
||||||
|
- do:
|
||||||
|
index: test
|
||||||
|
search:
|
||||||
|
body:
|
||||||
|
query:
|
||||||
|
match_all: {}
|
||||||
|
script_fields:
|
||||||
|
sNum1:
|
||||||
|
script:
|
||||||
|
source: "getValue() + doc['num1'][0]"
|
||||||
|
lang: painless
|
||||||
|
|
||||||
|
- match: { hits.total: 1 }
|
||||||
|
- match: { hits.hits.0.fields.sNum1.0: 2 }
|
Loading…
Reference in New Issue