* Setting `script.painless.regex.enabled` has a new option, `use-factor`, the default. This defaults to using regular expressions but limiting the complexity of the regular expressions. In addition to `use-factor`, the setting can be `true`, as before, which enables regular expressions without limiting them. `false` totally disables regular expressions, which was the old default. * New setting `script.painless.regex.limit-factor`. This limits regular expression complexity by limiting the number characters a regular expression can consider based on input length. The default is `6`, so a regular expression can consider `6` * input length number of characters. With input `foobarbaz` (length `9`), for example, the regular expression can consider `54` (`6 * 9`) characters. This reduces the impact of exponential backtracking in Java's regular expression engine. * add `@inject_constant` annotation to whitelist. This annotation signals that a compiler settings will be injected at the beginning of a whitelisted method. The format is `argnum=settingname`: `1=foo_setting 2=bar_setting`. Argument numbers must start at one and must be sequential. * Augment `Pattern.split(CharSequence)` `Pattern.split(CharSequence, int)`, `Pattern.splitAsStream(CharSequence)` `Pattern.matcher(CharSequence)` to take the value of `script.painless.regex.limit-factor` as a an injected parameter, limiting as explained above when this setting is in use. Fixes: #49873 Backport of: 93f29a4
This commit is contained in:
parent
d134b4f70b
commit
791a9d5102
|
@ -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.painless.spi.annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Inject compiler setting constants.
|
||||
* Format: {@code inject_constant["1=foo_compiler_setting", 2="bar_compiler_setting"]} injects "foo_compiler_setting and
|
||||
* "bar_compiler_setting" as the first two arguments (other than receiver reference for instance methods) to the annotated method.
|
||||
*/
|
||||
public class InjectConstantAnnotation {
|
||||
public static final String NAME = "inject_constant";
|
||||
public final List<String> injects;
|
||||
public InjectConstantAnnotation(List<String> injects) {
|
||||
this.injects = Collections.unmodifiableList(injects);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.painless.spi.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
public class InjectConstantAnnotationParser implements WhitelistAnnotationParser {
|
||||
|
||||
public static final InjectConstantAnnotationParser INSTANCE = new InjectConstantAnnotationParser();
|
||||
|
||||
private InjectConstantAnnotationParser() {}
|
||||
|
||||
@Override
|
||||
public Object parse(Map<String, String> arguments) {
|
||||
if (arguments.isEmpty()) {
|
||||
throw new IllegalArgumentException("[@inject_constant] requires at least one name to inject");
|
||||
}
|
||||
ArrayList<String> argList = new ArrayList<>(arguments.size());
|
||||
for (int i = 1; i <= arguments.size(); i++) {
|
||||
String argNum = Integer.toString(i);
|
||||
if (arguments.containsKey(argNum) == false) {
|
||||
throw new IllegalArgumentException("[@inject_constant] missing argument number [" + argNum + "]");
|
||||
}
|
||||
argList.add(arguments.get(argNum));
|
||||
}
|
||||
|
||||
return new InjectConstantAnnotation(argList);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,8 @@ public interface WhitelistAnnotationParser {
|
|||
Stream.of(
|
||||
new AbstractMap.SimpleEntry<>(NoImportAnnotation.NAME, NoImportAnnotationParser.INSTANCE),
|
||||
new AbstractMap.SimpleEntry<>(DeprecatedAnnotation.NAME, DeprecatedAnnotationParser.INSTANCE),
|
||||
new AbstractMap.SimpleEntry<>(NonDeterministicAnnotation.NAME, NonDeterministicAnnotationParser.INSTANCE)
|
||||
new AbstractMap.SimpleEntry<>(NonDeterministicAnnotation.NAME, NonDeterministicAnnotationParser.INSTANCE),
|
||||
new AbstractMap.SimpleEntry<>(InjectConstantAnnotation.NAME, InjectConstantAnnotationParser.INSTANCE)
|
||||
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
);
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ final class Compiler {
|
|||
ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source, root.getIdentifier() + 1);
|
||||
new PainlessSemanticHeaderPhase().visitClass(root, scriptScope);
|
||||
new PainlessSemanticAnalysisPhase().visitClass(root, scriptScope);
|
||||
// TODO(stu): Make this phase optional #60156
|
||||
// TODO: Make this phase optional #60156
|
||||
new DocFieldsPhase().visitClass(root, scriptScope);
|
||||
new PainlessUserTreeToIRTreePhase().visitClass(root, scriptScope);
|
||||
ClassNode classNode = (ClassNode)scriptScope.getDecoration(root, IRNodeDecoration.class).getIRNode();
|
||||
|
@ -255,7 +255,7 @@ final class Compiler {
|
|||
ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source, root.getIdentifier() + 1);
|
||||
new PainlessSemanticHeaderPhase().visitClass(root, scriptScope);
|
||||
new PainlessSemanticAnalysisPhase().visitClass(root, scriptScope);
|
||||
// TODO(stu): Make this phase optional #60156
|
||||
// TODO: Make this phase optional #60156
|
||||
new DocFieldsPhase().visitClass(root, scriptScope);
|
||||
new PainlessUserTreeToIRTreePhase().visitClass(root, scriptScope);
|
||||
ClassNode classNode = (ClassNode)scriptScope.getDecoration(root, IRNodeDecoration.class).getIRNode();
|
||||
|
|
|
@ -21,16 +21,28 @@ package org.elasticsearch.painless;
|
|||
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.painless.api.Augmentation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Settings to use when compiling a script.
|
||||
*/
|
||||
public final class CompilerSettings {
|
||||
/**
|
||||
* Are regexes enabled? This is a node level setting because regexes break out of painless's lovely sandbox and can cause stack
|
||||
* overflows and we can't analyze the regex to be sure it won't.
|
||||
* Are regexes enabled? If {@code true}, regexes are enabled and unlimited by the limit factor. If {@code false}, they are completely
|
||||
* disabled. If {@code use-limit}, the default, regexes are enabled but limited in complexity according to the
|
||||
* {@code script.painless.regex.limit-factor} setting.
|
||||
*/
|
||||
public static final Setting<Boolean> REGEX_ENABLED = Setting.boolSetting("script.painless.regex.enabled", false, Property.NodeScope);
|
||||
public static final Setting<RegexEnabled> REGEX_ENABLED =
|
||||
new Setting<>("script.painless.regex.enabled", RegexEnabled.LIMITED.value, RegexEnabled::parse, Property.NodeScope);
|
||||
|
||||
/**
|
||||
* How complex can a regex be? This is the number of characters that can be considered expressed as a multiple of string length.
|
||||
*/
|
||||
public static final Setting<Integer> REGEX_LIMIT_FACTOR =
|
||||
Setting.intSetting("script.painless.regex.limit-factor", 6, 1, Property.NodeScope);
|
||||
|
||||
/**
|
||||
* Constant to be used when specifying the maximum loop counter when compiling a script.
|
||||
|
@ -65,12 +77,20 @@ public final class CompilerSettings {
|
|||
* For testing. Do not use.
|
||||
*/
|
||||
private int initialCallSiteDepth = 0;
|
||||
private int testInject0 = 2;
|
||||
private int testInject1 = 4;
|
||||
private int testInject2 = 6;
|
||||
|
||||
/**
|
||||
* Are regexes enabled? They are currently disabled by default because they break out of the loop counter and even fairly simple
|
||||
* <strong>looking</strong> regexes can cause stack overflows.
|
||||
* Are regexes enabled? Defaults to using the factor setting.
|
||||
*/
|
||||
private boolean regexesEnabled = false;
|
||||
private RegexEnabled regexesEnabled = RegexEnabled.LIMITED;
|
||||
|
||||
|
||||
/**
|
||||
* How complex can regexes be? Expressed as a multiple of the input string.
|
||||
*/
|
||||
private int regexLimitFactor = 0;
|
||||
|
||||
/**
|
||||
* Returns the value for the cumulative total number of statements that can be made in all loops
|
||||
|
@ -123,18 +143,82 @@ public final class CompilerSettings {
|
|||
}
|
||||
|
||||
/**
|
||||
* Are regexes enabled? They are currently disabled by default because they break out of the loop counter and even fairly simple
|
||||
* <strong>looking</strong> regexes can cause stack overflows.
|
||||
* Are regexes enabled?
|
||||
*/
|
||||
public boolean areRegexesEnabled() {
|
||||
public RegexEnabled areRegexesEnabled() {
|
||||
return regexesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are regexes enabled? They are currently disabled by default because they break out of the loop counter and even fairly simple
|
||||
* <strong>looking</strong> regexes can cause stack overflows.
|
||||
* Are regexes enabled or limited?
|
||||
*/
|
||||
public void setRegexesEnabled(boolean regexesEnabled) {
|
||||
public void setRegexesEnabled(RegexEnabled regexesEnabled) {
|
||||
this.regexesEnabled = regexesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the limitation on regex complexity? How many multiples of input length can a regular expression consider?
|
||||
*/
|
||||
public void setRegexLimitFactor(int regexLimitFactor) {
|
||||
this.regexLimitFactor = regexLimitFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the limit factor for regexes?
|
||||
*/
|
||||
public int getRegexLimitFactor() {
|
||||
return regexLimitFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compiler settings as a map. This is used to inject compiler settings into augmented methods with the {@code @inject_constant}
|
||||
* annotation.
|
||||
*/
|
||||
public Map<String, Object> asMap() {
|
||||
int regexLimitFactor = this.regexLimitFactor;
|
||||
if (regexesEnabled == RegexEnabled.TRUE) {
|
||||
regexLimitFactor = Augmentation.UNLIMITED_PATTERN_FACTOR;
|
||||
} else if (regexesEnabled == RegexEnabled.FALSE) {
|
||||
regexLimitFactor = Augmentation.DISABLED_PATTERN_FACTOR;
|
||||
}
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("regex_limit_factor", regexLimitFactor);
|
||||
|
||||
// for testing only
|
||||
map.put("testInject0", testInject0);
|
||||
map.put("testInject1", testInject1);
|
||||
map.put("testInject2", testInject2);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@code script.painless.regex.enabled} setting.
|
||||
*/
|
||||
public enum RegexEnabled {
|
||||
TRUE("true"),
|
||||
FALSE("false"),
|
||||
LIMITED("limited");
|
||||
final String value;
|
||||
|
||||
RegexEnabled(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse string value, necessary because `valueOf` would require strings to be upper case.
|
||||
*/
|
||||
public static RegexEnabled parse(String value) {
|
||||
if (TRUE.value.equals(value)) {
|
||||
return TRUE;
|
||||
} else if (FALSE.value.equals(value)) {
|
||||
return FALSE;
|
||||
} else if (LIMITED.value.equals(value)) {
|
||||
return LIMITED;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"invalid value [" + value + "] must be one of [" + TRUE.value + "," + FALSE.value + "," + LIMITED.value + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,6 +182,8 @@ public final class Def {
|
|||
* Otherwise it returns a handle to the matching method.
|
||||
* <p>
|
||||
* @param painlessLookup the whitelist
|
||||
* @param functions user defined functions and lambdas
|
||||
* @param constants available constants to be used if the method has the {@code InjectConstantAnnotation}
|
||||
* @param methodHandlesLookup caller's lookup
|
||||
* @param callSiteType callsite's type
|
||||
* @param receiverClass Class of the object to invoke the method on.
|
||||
|
@ -191,8 +193,8 @@ public final class Def {
|
|||
* @throws IllegalArgumentException if no matching whitelisted method was found.
|
||||
* @throws Throwable if a method reference cannot be converted to an functional interface
|
||||
*/
|
||||
static MethodHandle lookupMethod(PainlessLookup painlessLookup, FunctionTable functions,
|
||||
MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, Class<?> receiverClass, String name, Object args[])
|
||||
static MethodHandle lookupMethod(PainlessLookup painlessLookup, FunctionTable functions, Map<String, Object> constants,
|
||||
MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, Class<?> receiverClass, String name, Object[] args)
|
||||
throws Throwable {
|
||||
|
||||
String recipeString = (String) args[0];
|
||||
|
@ -206,7 +208,15 @@ public final class Def {
|
|||
"[" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + (numArguments - 1) + "] not found");
|
||||
}
|
||||
|
||||
return painlessMethod.methodHandle;
|
||||
MethodHandle handle = painlessMethod.methodHandle;
|
||||
Object[] injections = PainlessLookupUtility.buildInjections(painlessMethod, constants);
|
||||
|
||||
if (injections.length > 0) {
|
||||
// method handle contains the "this" pointer so start injections at 1
|
||||
handle = MethodHandles.insertArguments(handle, 1, injections);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// convert recipe string to a bitset for convenience (the code below should be refactored...)
|
||||
|
@ -236,7 +246,13 @@ public final class Def {
|
|||
"dynamic method [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + arity + "] not found");
|
||||
}
|
||||
|
||||
MethodHandle handle = method.methodHandle;
|
||||
MethodHandle handle = method.methodHandle;
|
||||
Object[] injections = PainlessLookupUtility.buildInjections(method, constants);
|
||||
|
||||
if (injections.length > 0) {
|
||||
// method handle contains the "this" pointer so start injections at 1
|
||||
handle = MethodHandles.insertArguments(handle, 1, injections);
|
||||
}
|
||||
|
||||
int replaced = 0;
|
||||
upTo = 1;
|
||||
|
@ -257,22 +273,25 @@ public final class Def {
|
|||
// we have everything.
|
||||
filter = lookupReferenceInternal(painlessLookup,
|
||||
functions,
|
||||
constants,
|
||||
methodHandlesLookup,
|
||||
interfaceType,
|
||||
type,
|
||||
call,
|
||||
numCaptures);
|
||||
numCaptures
|
||||
);
|
||||
} else if (signature.charAt(0) == 'D') {
|
||||
// the interface type is now known, but we need to get the implementation.
|
||||
// this is dynamically based on the receiver type (and cached separately, underneath
|
||||
// this cache). It won't blow up since we never nest here (just references)
|
||||
Class<?> captures[] = new Class<?>[numCaptures];
|
||||
Class<?>[] captures = new Class<?>[numCaptures];
|
||||
for (int capture = 0; capture < captures.length; capture++) {
|
||||
captures[capture] = callSiteType.parameterType(i + 1 + capture);
|
||||
}
|
||||
MethodType nestedType = MethodType.methodType(interfaceType, captures);
|
||||
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
||||
functions,
|
||||
constants,
|
||||
methodHandlesLookup,
|
||||
call,
|
||||
nestedType,
|
||||
|
@ -300,8 +319,10 @@ public final class Def {
|
|||
* This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known,
|
||||
* so we simply need to lookup the matching implementation method based on receiver type.
|
||||
*/
|
||||
static MethodHandle lookupReference(PainlessLookup painlessLookup, FunctionTable functions,
|
||||
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
||||
static MethodHandle lookupReference(PainlessLookup painlessLookup, FunctionTable functions, Map<String, Object> constants,
|
||||
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name)
|
||||
throws Throwable {
|
||||
|
||||
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
||||
if (interfaceType == null) {
|
||||
throw new IllegalArgumentException("type [" + interfaceClass + "] not found");
|
||||
|
@ -317,25 +338,30 @@ public final class Def {
|
|||
"dynamic method [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + arity + "] not found");
|
||||
}
|
||||
|
||||
return lookupReferenceInternal(painlessLookup, functions, methodHandlesLookup,
|
||||
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
||||
implMethod.javaMethod.getName(), 1);
|
||||
return lookupReferenceInternal(painlessLookup, functions, constants,
|
||||
methodHandlesLookup, interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
||||
implMethod.javaMethod.getName(), 1);
|
||||
}
|
||||
|
||||
/** Returns a method handle to an implementation of clazz, given method reference signature. */
|
||||
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, FunctionTable functions,
|
||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures) throws Throwable {
|
||||
final FunctionRef ref = FunctionRef.create(painlessLookup, functions, null, clazz, type, call, captures);
|
||||
private static MethodHandle lookupReferenceInternal(
|
||||
PainlessLookup painlessLookup, FunctionTable functions, Map<String, Object> constants,
|
||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures
|
||||
) throws Throwable {
|
||||
|
||||
final FunctionRef ref = FunctionRef.create(painlessLookup, functions, null, clazz, type, call, captures, constants);
|
||||
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
|
||||
methodHandlesLookup,
|
||||
ref.interfaceMethodName,
|
||||
ref.factoryMethodType,
|
||||
ref.interfaceMethodType,
|
||||
ref.delegateClassName,
|
||||
ref.delegateInvokeType,
|
||||
ref.delegateMethodName,
|
||||
ref.delegateMethodType,
|
||||
ref.isDelegateInterface ? 1 : 0
|
||||
methodHandlesLookup,
|
||||
ref.interfaceMethodName,
|
||||
ref.factoryMethodType,
|
||||
ref.interfaceMethodType,
|
||||
ref.delegateClassName,
|
||||
ref.delegateInvokeType,
|
||||
ref.delegateMethodName,
|
||||
ref.delegateMethodType,
|
||||
ref.isDelegateInterface ? 1 : 0,
|
||||
ref.isDelegateAugmented ? 1 : 0,
|
||||
ref.delegateInjections
|
||||
);
|
||||
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray()));
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.MutableCallSite;
|
||||
import java.lang.invoke.WrongMethodTypeException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Painless invokedynamic bootstrap for the call site.
|
||||
|
@ -107,13 +108,14 @@ public final class DefBootstrap {
|
|||
|
||||
private final PainlessLookup painlessLookup;
|
||||
private final FunctionTable functions;
|
||||
private final Map<String, Object> constants;
|
||||
private final MethodHandles.Lookup methodHandlesLookup;
|
||||
private final String name;
|
||||
private final int flavor;
|
||||
private final Object[] args;
|
||||
int depth; // pkg-protected for testing
|
||||
|
||||
PIC(PainlessLookup painlessLookup, FunctionTable functions,
|
||||
PIC(PainlessLookup painlessLookup, FunctionTable functions, Map<String, Object> constants,
|
||||
MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) {
|
||||
super(type);
|
||||
if (type.parameterType(0) != Object.class) {
|
||||
|
@ -121,6 +123,7 @@ public final class DefBootstrap {
|
|||
}
|
||||
this.painlessLookup = painlessLookup;
|
||||
this.functions = functions;
|
||||
this.constants = constants;
|
||||
this.methodHandlesLookup = methodHandlesLookup;
|
||||
this.name = name;
|
||||
this.flavor = flavor;
|
||||
|
@ -148,7 +151,7 @@ public final class DefBootstrap {
|
|||
private MethodHandle lookup(int flavor, String name, Class<?> receiver) throws Throwable {
|
||||
switch(flavor) {
|
||||
case METHOD_CALL:
|
||||
return Def.lookupMethod(painlessLookup, functions, methodHandlesLookup, type(), receiver, name, args);
|
||||
return Def.lookupMethod(painlessLookup, functions, constants, methodHandlesLookup, type(), receiver, name, args);
|
||||
case LOAD:
|
||||
return Def.lookupGetter(painlessLookup, receiver, name);
|
||||
case STORE:
|
||||
|
@ -160,7 +163,7 @@ public final class DefBootstrap {
|
|||
case ITERATOR:
|
||||
return Def.lookupIterator(receiver);
|
||||
case REFERENCE:
|
||||
return Def.lookupReference(painlessLookup, functions, methodHandlesLookup, (String) args[0], receiver, name);
|
||||
return Def.lookupReference(painlessLookup, functions, constants, methodHandlesLookup, (String) args[0], receiver, name);
|
||||
case INDEX_NORMALIZE:
|
||||
return Def.lookupIndexNormalize(receiver);
|
||||
default: throw new AssertionError();
|
||||
|
@ -436,7 +439,7 @@ public final class DefBootstrap {
|
|||
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static CallSite bootstrap(PainlessLookup painlessLookup, FunctionTable functions,
|
||||
public static CallSite bootstrap(PainlessLookup painlessLookup, FunctionTable functions, Map<String, Object> constants,
|
||||
MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object... args) {
|
||||
// validate arguments
|
||||
switch(flavor) {
|
||||
|
@ -456,7 +459,7 @@ public final class DefBootstrap {
|
|||
if (args.length != numLambdas + 1) {
|
||||
throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references");
|
||||
}
|
||||
return new PIC(painlessLookup, functions, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
return new PIC(painlessLookup, functions, constants, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
case LOAD:
|
||||
case STORE:
|
||||
case ARRAY_LOAD:
|
||||
|
@ -466,7 +469,7 @@ public final class DefBootstrap {
|
|||
if (args.length > 0) {
|
||||
throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor);
|
||||
}
|
||||
return new PIC(painlessLookup, functions, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
return new PIC(painlessLookup, functions, constants, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
case REFERENCE:
|
||||
if (args.length != 1) {
|
||||
throw new BootstrapMethodError("Invalid number of parameters for reference call");
|
||||
|
@ -474,7 +477,7 @@ public final class DefBootstrap {
|
|||
if (args[0] instanceof String == false) {
|
||||
throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]);
|
||||
}
|
||||
return new PIC(painlessLookup, functions, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
return new PIC(painlessLookup, functions, constants, methodHandlesLookup, name, type, initialDepth, flavor, args);
|
||||
|
||||
// operators get monomorphic cache, with a generic impl for a fallback
|
||||
case UNARY_OPERATOR:
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.lang.invoke.MethodType;
|
|||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
||||
|
@ -44,7 +45,6 @@ import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
|
|||
* lambda function.
|
||||
*/
|
||||
public class FunctionRef {
|
||||
|
||||
/**
|
||||
* Creates a new FunctionRef which will resolve {@code type::call} from the whitelist.
|
||||
* @param painlessLookup the whitelist against which this script is being compiled
|
||||
|
@ -54,9 +54,10 @@ public class FunctionRef {
|
|||
* @param typeName the left hand side of a method reference expression
|
||||
* @param methodName the right hand side of a method reference expression
|
||||
* @param numberOfCaptures number of captured arguments
|
||||
* @param constants constants used for injection when necessary
|
||||
*/
|
||||
public static FunctionRef create(PainlessLookup painlessLookup, FunctionTable functionTable, Location location,
|
||||
Class<?> targetClass, String typeName, String methodName, int numberOfCaptures) {
|
||||
Class<?> targetClass, String typeName, String methodName, int numberOfCaptures, Map<String, Object> constants) {
|
||||
|
||||
Objects.requireNonNull(painlessLookup);
|
||||
Objects.requireNonNull(targetClass);
|
||||
|
@ -78,9 +79,11 @@ public class FunctionRef {
|
|||
MethodType interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
||||
String delegateClassName;
|
||||
boolean isDelegateInterface;
|
||||
boolean isDelegateAugmented;
|
||||
int delegateInvokeType;
|
||||
String delegateMethodName;
|
||||
MethodType delegateMethodType;
|
||||
Object[] delegateInjections;
|
||||
|
||||
Class<?> delegateMethodReturnType;
|
||||
List<Class<?>> delegateMethodParameters;
|
||||
|
@ -105,9 +108,11 @@ public class FunctionRef {
|
|||
|
||||
delegateClassName = CLASS_NAME;
|
||||
isDelegateInterface = false;
|
||||
isDelegateAugmented = false;
|
||||
delegateInvokeType = H_INVOKESTATIC;
|
||||
delegateMethodName = localFunction.getFunctionName();
|
||||
delegateMethodType = localFunction.getMethodType();
|
||||
delegateInjections = new Object[0];
|
||||
|
||||
delegateMethodReturnType = localFunction.getReturnType();
|
||||
delegateMethodParameters = localFunction.getTypeParameters();
|
||||
|
@ -126,9 +131,11 @@ public class FunctionRef {
|
|||
|
||||
delegateClassName = painlessConstructor.javaConstructor.getDeclaringClass().getName();
|
||||
isDelegateInterface = false;
|
||||
isDelegateAugmented = false;
|
||||
delegateInvokeType = H_NEWINVOKESPECIAL;
|
||||
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
|
||||
delegateMethodType = painlessConstructor.methodType;
|
||||
delegateInjections = new Object[0];
|
||||
|
||||
delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass();
|
||||
delegateMethodParameters = painlessConstructor.typeParameters;
|
||||
|
@ -157,6 +164,7 @@ public class FunctionRef {
|
|||
|
||||
delegateClassName = painlessMethod.javaMethod.getDeclaringClass().getName();
|
||||
isDelegateInterface = painlessMethod.javaMethod.getDeclaringClass().isInterface();
|
||||
isDelegateAugmented = painlessMethod.javaMethod.getDeclaringClass() != painlessMethod.targetClass;
|
||||
|
||||
if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) {
|
||||
delegateInvokeType = H_INVOKESTATIC;
|
||||
|
@ -168,6 +176,7 @@ public class FunctionRef {
|
|||
|
||||
delegateMethodName = painlessMethod.javaMethod.getName();
|
||||
delegateMethodType = painlessMethod.methodType;
|
||||
delegateInjections = PainlessLookupUtility.buildInjections(painlessMethod, constants);
|
||||
|
||||
delegateMethodReturnType = painlessMethod.returnType;
|
||||
|
||||
|
@ -196,7 +205,8 @@ public class FunctionRef {
|
|||
delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures);
|
||||
|
||||
return new FunctionRef(interfaceMethodName, interfaceMethodType,
|
||||
delegateClassName, isDelegateInterface, delegateInvokeType, delegateMethodName, delegateMethodType,
|
||||
delegateClassName, isDelegateInterface, isDelegateAugmented,
|
||||
delegateInvokeType, delegateMethodName, delegateMethodType, delegateInjections,
|
||||
factoryMethodType
|
||||
);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
|
@ -216,28 +226,34 @@ public class FunctionRef {
|
|||
public final String delegateClassName;
|
||||
/** whether a call is made on a delegate interface */
|
||||
public final boolean isDelegateInterface;
|
||||
/** if delegate method is augmented */
|
||||
public final boolean isDelegateAugmented;
|
||||
/** the invocation type of the delegate method */
|
||||
public final int delegateInvokeType;
|
||||
/** the name of the delegate method */
|
||||
public final String delegateMethodName;
|
||||
/** delegate method signature */
|
||||
public final MethodType delegateMethodType;
|
||||
/** injected constants */
|
||||
public final Object[] delegateInjections;
|
||||
/** factory (CallSite) method signature */
|
||||
public final MethodType factoryMethodType;
|
||||
|
||||
private FunctionRef(
|
||||
String interfaceMethodName, MethodType interfaceMethodType,
|
||||
String delegateClassName, boolean isDelegateInterface,
|
||||
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
|
||||
String delegateClassName, boolean isDelegateInterface, boolean isDelegateAugmented,
|
||||
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType, Object[] delegateInjections,
|
||||
MethodType factoryMethodType) {
|
||||
|
||||
this.interfaceMethodName = interfaceMethodName;
|
||||
this.interfaceMethodType = interfaceMethodType;
|
||||
this.delegateClassName = delegateClassName;
|
||||
this.isDelegateInterface = isDelegateInterface;
|
||||
this.isDelegateAugmented = isDelegateAugmented;
|
||||
this.delegateInvokeType = delegateInvokeType;
|
||||
this.delegateMethodName = delegateMethodName;
|
||||
this.delegateMethodType = delegateMethodType;
|
||||
this.delegateInjections = delegateInjections;
|
||||
this.factoryMethodType = factoryMethodType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,6 +194,7 @@ public final class LambdaBootstrap {
|
|||
* if the value is '1' if the delegate is an interface and '0'
|
||||
* otherwise; note this is an int because the bootstrap method
|
||||
* cannot convert constants to boolean
|
||||
* @param injections Optionally add injectable constants into a method reference
|
||||
* @return A {@link CallSite} linked to a factory method for creating a lambda class
|
||||
* that implements the expected functional interface
|
||||
* @throws LambdaConversionException Thrown when an illegal type conversion occurs at link time
|
||||
|
@ -207,7 +208,9 @@ public final class LambdaBootstrap {
|
|||
int delegateInvokeType,
|
||||
String delegateMethodName,
|
||||
MethodType delegateMethodType,
|
||||
int isDelegateInterface)
|
||||
int isDelegateInterface,
|
||||
int isDelegateAugmented,
|
||||
Object... injections)
|
||||
throws LambdaConversionException {
|
||||
Compiler.Loader loader = (Compiler.Loader)lookup.lookupClass().getClassLoader();
|
||||
String lambdaClassName = Type.getInternalName(lookup.lookupClass()) + "$$Lambda" + loader.newLambdaIdentifier();
|
||||
|
@ -232,7 +235,7 @@ public final class LambdaBootstrap {
|
|||
|
||||
generateInterfaceMethod(cw, factoryMethodType, lambdaClassType, interfaceMethodName,
|
||||
interfaceMethodType, delegateClassType, delegateInvokeType,
|
||||
delegateMethodName, delegateMethodType, isDelegateInterface == 1, captures);
|
||||
delegateMethodName, delegateMethodType, isDelegateInterface == 1, isDelegateAugmented == 1, captures, injections);
|
||||
|
||||
endLambdaClass(cw);
|
||||
|
||||
|
@ -377,7 +380,9 @@ public final class LambdaBootstrap {
|
|||
String delegateMethodName,
|
||||
MethodType delegateMethodType,
|
||||
boolean isDelegateInterface,
|
||||
Capture[] captures)
|
||||
boolean isDelegateAugmented,
|
||||
Capture[] captures,
|
||||
Object... injections)
|
||||
throws LambdaConversionException {
|
||||
|
||||
String lamDesc = interfaceMethodType.toMethodDescriptorString();
|
||||
|
@ -443,9 +448,17 @@ public final class LambdaBootstrap {
|
|||
new Handle(delegateInvokeType, delegateClassType.getInternalName(),
|
||||
delegateMethodName, delegateMethodType.toMethodDescriptorString(),
|
||||
isDelegateInterface);
|
||||
iface.invokeDynamic(delegateMethodName, Type.getMethodType(interfaceMethodType
|
||||
.toMethodDescriptorString()).getDescriptor(), DELEGATE_BOOTSTRAP_HANDLE,
|
||||
delegateHandle);
|
||||
// Fill in args for indy. Always add the delegate handle and
|
||||
// whether it's static or not then injections as necessary.
|
||||
Object[] args = new Object[2 + injections.length];
|
||||
args[0] = delegateHandle;
|
||||
args[1] = delegateInvokeType == H_INVOKESTATIC && isDelegateAugmented == false ? 0 : 1;
|
||||
System.arraycopy(injections, 0, args, 2, injections.length);
|
||||
iface.invokeDynamic(
|
||||
delegateMethodName,
|
||||
Type.getMethodType(interfaceMethodType.toMethodDescriptorString()).getDescriptor(),
|
||||
DELEGATE_BOOTSTRAP_HANDLE,
|
||||
args);
|
||||
|
||||
iface.returnValue();
|
||||
iface.endMethod();
|
||||
|
@ -517,7 +530,14 @@ public final class LambdaBootstrap {
|
|||
public static CallSite delegateBootstrap(Lookup lookup,
|
||||
String delegateMethodName,
|
||||
MethodType interfaceMethodType,
|
||||
MethodHandle delegateMethodHandle) {
|
||||
MethodHandle delegateMethodHandle,
|
||||
int isVirtual,
|
||||
Object... injections) {
|
||||
|
||||
if (injections.length > 0) {
|
||||
delegateMethodHandle = MethodHandles.insertArguments(delegateMethodHandle, isVirtual, injections);
|
||||
}
|
||||
|
||||
return new ConstantCallSite(delegateMethodHandle.asType(interfaceMethodType));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -515,16 +515,21 @@ public final class MethodWriter extends GeneratorAdapter {
|
|||
}
|
||||
|
||||
public void invokeLambdaCall(FunctionRef functionRef) {
|
||||
Object[] args = new Object[7 + functionRef.delegateInjections.length];
|
||||
args[0] = Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString());
|
||||
args[1] = functionRef.delegateClassName;
|
||||
args[2] = functionRef.delegateInvokeType;
|
||||
args[3] = functionRef.delegateMethodName;
|
||||
args[4] = Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString());
|
||||
args[5] = functionRef.isDelegateInterface ? 1 : 0;
|
||||
args[6] = functionRef.isDelegateAugmented ? 1 : 0;
|
||||
System.arraycopy(functionRef.delegateInjections, 0, args, 7, functionRef.delegateInjections.length);
|
||||
|
||||
invokeDynamic(
|
||||
functionRef.interfaceMethodName,
|
||||
functionRef.factoryMethodType.toMethodDescriptorString(),
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()),
|
||||
functionRef.delegateClassName,
|
||||
functionRef.delegateInvokeType,
|
||||
functionRef.delegateMethodName,
|
||||
Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()),
|
||||
functionRef.isDelegateInterface ? 1 : 0
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
|
|||
|
||||
@Override
|
||||
public List<Setting<?>> getSettings() {
|
||||
return Arrays.asList(CompilerSettings.REGEX_ENABLED);
|
||||
return Arrays.asList(CompilerSettings.REGEX_ENABLED, CompilerSettings.REGEX_LIMIT_FACTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -92,6 +92,7 @@ public final class PainlessScriptEngine implements ScriptEngine {
|
|||
*/
|
||||
public PainlessScriptEngine(Settings settings, Map<ScriptContext<?>, List<Whitelist>> contexts) {
|
||||
defaultCompilerSettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(settings));
|
||||
defaultCompilerSettings.setRegexLimitFactor(CompilerSettings.REGEX_LIMIT_FACTOR.get(settings));
|
||||
|
||||
Map<ScriptContext<?>, Compiler> contextsToCompilers = new HashMap<>();
|
||||
Map<ScriptContext<?>, PainlessLookup> contextsToLookups = new HashMap<>();
|
||||
|
@ -429,6 +430,8 @@ public final class PainlessScriptEngine implements ScriptEngine {
|
|||
// Except regexes enabled - this is a node level setting and can't be changed in the request.
|
||||
compilerSettings.setRegexesEnabled(defaultCompilerSettings.areRegexesEnabled());
|
||||
|
||||
compilerSettings.setRegexLimitFactor(defaultCompilerSettings.getRegexLimitFactor());
|
||||
|
||||
Map<String, String> copy = new HashMap<>(params);
|
||||
|
||||
String value = copy.remove(CompilerSettings.MAX_LOOP_COUNTER);
|
||||
|
@ -451,6 +454,11 @@ public final class PainlessScriptEngine implements ScriptEngine {
|
|||
throw new IllegalArgumentException("[painless.regex.enabled] can only be set on node startup.");
|
||||
}
|
||||
|
||||
value = copy.remove(CompilerSettings.REGEX_LIMIT_FACTOR.getKey());
|
||||
if (value != null) {
|
||||
throw new IllegalArgumentException("[painless.regex.limit-factor] can only be set on node startup.");
|
||||
}
|
||||
|
||||
if (!copy.isEmpty()) {
|
||||
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy);
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ public final class WriterConstants {
|
|||
* regex per time it is run.
|
||||
*/
|
||||
public static final Method PATTERN_COMPILE = getAsmMethod(Pattern.class, "compile", String.class, int.class);
|
||||
public static final Method PATTERN_MATCHER = getAsmMethod(Matcher.class, "matcher", CharSequence.class);
|
||||
public static final Method PATTERN_MATCHER = getAsmMethod(Matcher.class, "matcher", Pattern.class, int.class, CharSequence.class);
|
||||
public static final Method MATCHER_MATCHES = getAsmMethod(boolean.class, "matches");
|
||||
public static final Method MATCHER_FIND = getAsmMethod(boolean.class, "find");
|
||||
|
||||
|
@ -134,12 +134,13 @@ public final class WriterConstants {
|
|||
/** invokedynamic bootstrap for lambda expression/method references */
|
||||
public static final MethodType LAMBDA_BOOTSTRAP_TYPE =
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class,
|
||||
MethodType.class, String.class, int.class, String.class, MethodType.class, int.class);
|
||||
MethodType.class, String.class, int.class, String.class, MethodType.class, int.class, int.class, Object[].class);
|
||||
public static final Handle LAMBDA_BOOTSTRAP_HANDLE =
|
||||
new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(LambdaBootstrap.class),
|
||||
"lambdaBootstrap", LAMBDA_BOOTSTRAP_TYPE.toMethodDescriptorString(), false);
|
||||
public static final MethodType DELEGATE_BOOTSTRAP_TYPE =
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, MethodHandle.class);
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, MethodHandle.class,
|
||||
int.class, Object[].class);
|
||||
public static final Handle DELEGATE_BOOTSTRAP_HANDLE =
|
||||
new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(LambdaBootstrap.class),
|
||||
"delegateBootstrap", DELEGATE_BOOTSTRAP_TYPE.toMethodDescriptorString(), false);
|
||||
|
|
|
@ -41,10 +41,11 @@ import java.util.function.Supplier;
|
|||
import java.util.function.ToDoubleFunction;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/** Additional methods added to classes. These must be static methods with receiver as first argument */
|
||||
public class Augmentation {
|
||||
|
||||
|
||||
// static methods only!
|
||||
private Augmentation() {}
|
||||
|
||||
|
@ -57,10 +58,10 @@ public class Augmentation {
|
|||
public static String namedGroup(Matcher receiver, String name) {
|
||||
return receiver.group(name);
|
||||
}
|
||||
|
||||
|
||||
// some groovy methods on iterable
|
||||
// see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Iterable.html
|
||||
|
||||
|
||||
/** Iterates over the contents of an iterable, and checks whether a predicate is valid for at least one element. */
|
||||
public static <T> boolean any(Iterable<T> receiver, Predicate<T> predicate) {
|
||||
for (T t : receiver) {
|
||||
|
@ -70,7 +71,7 @@ public class Augmentation {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/** Converts this Iterable to a Collection. Returns the original Iterable if it is already a Collection. */
|
||||
public static <T> Collection<T> asCollection(Iterable<T> receiver) {
|
||||
if (receiver instanceof Collection) {
|
||||
|
@ -82,7 +83,7 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/** Converts this Iterable to a List. Returns the original Iterable if it is already a List. */
|
||||
public static <T> List<T> asList(Iterable<T> receiver) {
|
||||
if (receiver instanceof List) {
|
||||
|
@ -94,8 +95,8 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** Counts the number of occurrences which satisfy the given predicate from inside this Iterable. */
|
||||
|
||||
/** Counts the number of occurrences which satisfy the given predicate from inside this Iterable. */
|
||||
public static <T> int count(Iterable<T> receiver, Predicate<T> predicate) {
|
||||
int count = 0;
|
||||
for (T t : receiver) {
|
||||
|
@ -105,7 +106,7 @@ public class Augmentation {
|
|||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
// instead of covariant overrides for every possibility, we just return receiver as 'def' for now
|
||||
// that way if someone chains the calls, everything works.
|
||||
|
||||
|
@ -114,9 +115,9 @@ public class Augmentation {
|
|||
receiver.forEach(consumer);
|
||||
return receiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through an iterable type, passing each item and the item's index
|
||||
|
||||
/**
|
||||
* Iterates through an iterable type, passing each item and the item's index
|
||||
* (a counter starting at zero) to the given consumer.
|
||||
*/
|
||||
public static <T> Object eachWithIndex(Iterable<T> receiver, ObjIntConsumer<T> consumer) {
|
||||
|
@ -126,7 +127,7 @@ public class Augmentation {
|
|||
}
|
||||
return receiver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to determine if the given predicate is valid (i.e. returns true for all items in this iterable).
|
||||
*/
|
||||
|
@ -138,10 +139,10 @@ public class Augmentation {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the Iterable transforming items using the supplied function and
|
||||
* collecting any non-null results.
|
||||
* Iterates through the Iterable transforming items using the supplied function and
|
||||
* collecting any non-null results.
|
||||
*/
|
||||
public static <T,U> List<U> findResults(Iterable<T> receiver, Function<T,U> filter) {
|
||||
List<U> list = new ArrayList<>();
|
||||
|
@ -153,9 +154,9 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts all Iterable members into groups determined by the supplied mapping function.
|
||||
* Sorts all Iterable members into groups determined by the supplied mapping function.
|
||||
*/
|
||||
public static <T,U> Map<U,List<T>> groupBy(Iterable<T> receiver, Function<T,U> mapper) {
|
||||
Map<U,List<T>> map = new LinkedHashMap<>();
|
||||
|
@ -170,10 +171,10 @@ public class Augmentation {
|
|||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates the toString() representation of each item in this Iterable,
|
||||
* with the given String as a separator between each item.
|
||||
* Concatenates the toString() representation of each item in this Iterable,
|
||||
* with the given String as a separator between each item.
|
||||
*/
|
||||
public static <T> String join(Iterable<T> receiver, String separator) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -185,7 +186,7 @@ public class Augmentation {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sums the result of an Iterable
|
||||
*/
|
||||
|
@ -196,9 +197,9 @@ public class Augmentation {
|
|||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sums the result of applying a function to each item of an Iterable.
|
||||
* Sums the result of applying a function to each item of an Iterable.
|
||||
*/
|
||||
public static <T> double sum(Iterable<T> receiver, ToDoubleFunction<T> function) {
|
||||
double sum = 0;
|
||||
|
@ -207,13 +208,13 @@ public class Augmentation {
|
|||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
// some groovy methods on collection
|
||||
// see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through this collection transforming each entry into a new value using
|
||||
* the function, returning a list of transformed values.
|
||||
* Iterates through this collection transforming each entry into a new value using
|
||||
* the function, returning a list of transformed values.
|
||||
*/
|
||||
public static <T,U> List<U> collect(Collection<T> receiver, Function<T,U> function) {
|
||||
List<U> list = new ArrayList<>();
|
||||
|
@ -222,9 +223,9 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through this collection transforming each entry into a new value using
|
||||
* Iterates through this collection transforming each entry into a new value using
|
||||
* the function, adding the values to the specified collection.
|
||||
*/
|
||||
public static <T,U> Object collect(Collection<T> receiver, Collection<U> collection, Function<T,U> function) {
|
||||
|
@ -233,7 +234,7 @@ public class Augmentation {
|
|||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first value matching the predicate, or returns null.
|
||||
*/
|
||||
|
@ -245,7 +246,7 @@ public class Augmentation {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds all values matching the predicate, returns as a list
|
||||
*/
|
||||
|
@ -258,19 +259,19 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the collection calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, null is returned.
|
||||
* Iterates through the collection calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, null is returned.
|
||||
*/
|
||||
public static <T,U> Object findResult(Collection<T> receiver, Function<T,U> function) {
|
||||
return findResult(receiver, null, function);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the collection calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* Iterates through the collection calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, defaultResult is returned.
|
||||
*/
|
||||
public static <T,U> Object findResult(Collection<T> receiver, Object defaultResult, Function<T,U> function) {
|
||||
|
@ -282,10 +283,10 @@ public class Augmentation {
|
|||
}
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits all items into two collections based on the predicate.
|
||||
* The first list contains all items which match the closure expression. The second list all those that don't.
|
||||
* Splits all items into two collections based on the predicate.
|
||||
* The first list contains all items which match the closure expression. The second list all those that don't.
|
||||
*/
|
||||
public static <T> List<List<T>> split(Collection<T> receiver, Predicate<T> predicate) {
|
||||
List<T> matched = new ArrayList<>();
|
||||
|
@ -302,13 +303,13 @@ public class Augmentation {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// some groovy methods on map
|
||||
// see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through this map transforming each entry into a new value using
|
||||
* the function, returning a list of transformed values.
|
||||
* Iterates through this map transforming each entry into a new value using
|
||||
* the function, returning a list of transformed values.
|
||||
*/
|
||||
public static <K,V,T> List<T> collect(Map<K,V> receiver, BiFunction<K,V,T> function) {
|
||||
List<T> list = new ArrayList<>();
|
||||
|
@ -317,9 +318,9 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through this map transforming each entry into a new value using
|
||||
* Iterates through this map transforming each entry into a new value using
|
||||
* the function, adding the values to the specified collection.
|
||||
*/
|
||||
public static <K,V,T> Object collect(Map<K,V> receiver, Collection<T> collection, BiFunction<K,V,T> function) {
|
||||
|
@ -328,8 +329,8 @@ public class Augmentation {
|
|||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
/** Counts the number of occurrences which satisfy the given predicate from inside this Map */
|
||||
|
||||
/** Counts the number of occurrences which satisfy the given predicate from inside this Map */
|
||||
public static <K,V> int count(Map<K,V> receiver, BiPredicate<K,V> predicate) {
|
||||
int count = 0;
|
||||
for (Map.Entry<K,V> kvPair : receiver.entrySet()) {
|
||||
|
@ -339,13 +340,13 @@ public class Augmentation {
|
|||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/** Iterates through a Map, passing each item to the given consumer. */
|
||||
public static <K,V> Object each(Map<K,V> receiver, BiConsumer<K,V> consumer) {
|
||||
receiver.forEach(consumer);
|
||||
return receiver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to determine if the given predicate is valid (i.e. returns true for all items in this map).
|
||||
*/
|
||||
|
@ -357,7 +358,7 @@ public class Augmentation {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first entry matching the predicate, or returns null.
|
||||
*/
|
||||
|
@ -369,7 +370,7 @@ public class Augmentation {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds all values matching the predicate, returns as a map.
|
||||
*/
|
||||
|
@ -388,19 +389,19 @@ public class Augmentation {
|
|||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the map calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, null is returned.
|
||||
* Iterates through the map calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, null is returned.
|
||||
*/
|
||||
public static <K,V,T> Object findResult(Map<K,V> receiver, BiFunction<K,V,T> function) {
|
||||
return findResult(receiver, null, function);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the map calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* Iterates through the map calling the given function for each item
|
||||
* but stopping once the first non-null result is found and returning that result.
|
||||
* If all results are null, defaultResult is returned.
|
||||
*/
|
||||
public static <K,V,T> Object findResult(Map<K,V> receiver, Object defaultResult, BiFunction<K,V,T> function) {
|
||||
|
@ -412,10 +413,10 @@ public class Augmentation {
|
|||
}
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through the map transforming items using the supplied function and
|
||||
* collecting any non-null results.
|
||||
* Iterates through the map transforming items using the supplied function and
|
||||
* collecting any non-null results.
|
||||
*/
|
||||
public static <K,V,T> List<T> findResults(Map<K,V> receiver, BiFunction<K,V,T> filter) {
|
||||
List<T> list = new ArrayList<>();
|
||||
|
@ -427,9 +428,9 @@ public class Augmentation {
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts all Map members into groups determined by the supplied mapping function.
|
||||
* Sorts all Map members into groups determined by the supplied mapping function.
|
||||
*/
|
||||
public static <K,V,T> Map<T,Map<K,V>> groupBy(Map<K,V> receiver, BiFunction<K,V,T> mapper) {
|
||||
Map<T,Map<K,V>> map = new LinkedHashMap<>();
|
||||
|
@ -679,4 +680,36 @@ public class Augmentation {
|
|||
MessageDigests.sha256().digest(source.getBytes(StandardCharsets.UTF_8))
|
||||
);
|
||||
}
|
||||
|
||||
public static final int UNLIMITED_PATTERN_FACTOR = 0;
|
||||
public static final int DISABLED_PATTERN_FACTOR = -1;
|
||||
|
||||
// Regular Expression Pattern augmentations with limit factor injected
|
||||
public static String[] split(Pattern receiver, int limitFactor, CharSequence input) {
|
||||
if (limitFactor == UNLIMITED_PATTERN_FACTOR) {
|
||||
return receiver.split(input);
|
||||
}
|
||||
return receiver.split(new LimitedCharSequence(input, receiver, limitFactor));
|
||||
}
|
||||
|
||||
public static String[] split(Pattern receiver, int limitFactor, CharSequence input, int limit) {
|
||||
if (limitFactor == UNLIMITED_PATTERN_FACTOR) {
|
||||
return receiver.split(input, limit);
|
||||
}
|
||||
return receiver.split(new LimitedCharSequence(input, receiver, limitFactor), limit);
|
||||
}
|
||||
|
||||
public static Stream<String> splitAsStream(Pattern receiver, int limitFactor, CharSequence input) {
|
||||
if (limitFactor == UNLIMITED_PATTERN_FACTOR) {
|
||||
return receiver.splitAsStream(input);
|
||||
}
|
||||
return receiver.splitAsStream(new LimitedCharSequence(input, receiver, limitFactor));
|
||||
}
|
||||
|
||||
public static Matcher matcher(Pattern receiver, int limitFactor, CharSequence input) {
|
||||
if (limitFactor == UNLIMITED_PATTERN_FACTOR) {
|
||||
return receiver.matcher(input);
|
||||
}
|
||||
return receiver.matcher(new LimitedCharSequence(input, receiver, limitFactor));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.elasticsearch.common.breaker.CircuitBreaker;
|
||||
import org.elasticsearch.common.breaker.CircuitBreakingException;
|
||||
import org.elasticsearch.painless.CompilerSettings;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/*
|
||||
* CharSequence that wraps another sequence and limits the number of times charAt can be
|
||||
*/
|
||||
public class LimitedCharSequence implements CharSequence {
|
||||
private final CharSequence wrapped;
|
||||
private final Counter counter;
|
||||
|
||||
// for errors
|
||||
private final Pattern pattern;
|
||||
private final int limitFactor;
|
||||
|
||||
public static final int MAX_STR_LENGTH = 64;
|
||||
private static final String SNIPPET = "...";
|
||||
|
||||
public LimitedCharSequence(CharSequence wrap, Pattern pattern, int limitFactor) {
|
||||
if (limitFactor <= 0) {
|
||||
throw new IllegalArgumentException("limitFactor must be positive");
|
||||
}
|
||||
this.wrapped = wrap;
|
||||
this.counter = new Counter(limitFactor * wrapped.length());
|
||||
|
||||
this.pattern = pattern;
|
||||
this.limitFactor = limitFactor;
|
||||
}
|
||||
|
||||
public String details() {
|
||||
return (pattern != null ? "pattern: [" + pattern.pattern() + "], " : "") +
|
||||
"limit factor: [" + limitFactor + "], " +
|
||||
"char limit: [" + counter.charAtLimit + "], " +
|
||||
"count: [" + counter.count + "], " +
|
||||
"wrapped: [" + snippet(MAX_STR_LENGTH) + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Snip a long wrapped CharSequences for error messages
|
||||
*/
|
||||
String snippet(int maxStrLength) {
|
||||
if (maxStrLength < SNIPPET.length() * 6) {
|
||||
throw new IllegalArgumentException("max str length must be large enough to include three snippets and three context chars, " +
|
||||
"at least [" + SNIPPET.length() * 6 +"], not [" + maxStrLength + "]");
|
||||
}
|
||||
|
||||
if (wrapped.length() <= maxStrLength) {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
return wrapped.subSequence(0, maxStrLength - SNIPPET.length()) + "..." ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return wrapped.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
counter.count++;
|
||||
if (counter.hitLimit()) {
|
||||
throw new CircuitBreakingException("[scripting] Regular expression considered too many characters, " + details() +
|
||||
", this limit can be changed by changed by the [" + CompilerSettings.REGEX_LIMIT_FACTOR.getKey() + "] setting",
|
||||
CircuitBreaker.Durability.TRANSIENT);
|
||||
}
|
||||
return wrapped.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return wrapped.subSequence(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
// Counter object to keep track of charAts for original sequence and all subsequences
|
||||
private static class Counter {
|
||||
public final int charAtLimit;
|
||||
public int count;
|
||||
|
||||
Counter(int charAtLimit) {
|
||||
this.charAtLimit = charAtLimit;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
boolean hitLimit() {
|
||||
return count > charAtLimit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,13 +24,13 @@ import org.elasticsearch.painless.Location;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.elasticsearch.painless.api.Augmentation;
|
||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
||||
import org.elasticsearch.painless.lookup.def;
|
||||
import org.elasticsearch.painless.phase.IRTreeVisitor;
|
||||
import org.elasticsearch.painless.symbol.WriteScope;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class BinaryMathNode extends BinaryNode {
|
||||
|
||||
|
@ -40,6 +40,8 @@ public class BinaryMathNode extends BinaryNode {
|
|||
private Class<?> binaryType;
|
||||
private Class<?> shiftType;
|
||||
private int flags;
|
||||
// TODO(stu): DefaultUserTreeToIRTree -> visitRegex should have compiler settings in script set. set it
|
||||
private int regexLimit;
|
||||
|
||||
public void setOperation(Operation operation) {
|
||||
this.operation = operation;
|
||||
|
@ -81,6 +83,14 @@ public class BinaryMathNode extends BinaryNode {
|
|||
return flags;
|
||||
}
|
||||
|
||||
public void setRegexLimit(int regexLimit) {
|
||||
this.regexLimit = regexLimit;
|
||||
}
|
||||
|
||||
public int getRegexLimit() {
|
||||
return regexLimit;
|
||||
}
|
||||
|
||||
/* ---- end node data, begin visitor ---- */
|
||||
|
||||
@Override
|
||||
|
@ -106,8 +116,9 @@ public class BinaryMathNode extends BinaryNode {
|
|||
|
||||
if (operation == Operation.FIND || operation == Operation.MATCH) {
|
||||
getRightNode().write(classWriter, methodWriter, writeScope);
|
||||
methodWriter.push(regexLimit);
|
||||
getLeftNode().write(classWriter, methodWriter, writeScope);
|
||||
methodWriter.invokeVirtual(org.objectweb.asm.Type.getType(Pattern.class), WriterConstants.PATTERN_MATCHER);
|
||||
methodWriter.invokeStatic(org.objectweb.asm.Type.getType(Augmentation.class), WriterConstants.PATTERN_MATCHER);
|
||||
|
||||
if (operation == Operation.FIND) {
|
||||
methodWriter.invokeVirtual(org.objectweb.asm.Type.getType(Matcher.class), WriterConstants.MATCHER_FIND);
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.painless.spi.WhitelistConstructor;
|
|||
import org.elasticsearch.painless.spi.WhitelistField;
|
||||
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||
import org.elasticsearch.painless.spi.WhitelistMethod;
|
||||
import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation;
|
||||
import org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
@ -249,6 +250,7 @@ public final class PainlessLookupBuilder {
|
|||
|
||||
public void addPainlessClass(Class<?> clazz, boolean importClassName) {
|
||||
Objects.requireNonNull(clazz);
|
||||
//Matcher m = new Matcher();
|
||||
|
||||
if (clazz == def.class) {
|
||||
throw new IllegalArgumentException("cannot add reserved class [" + DEF_CLASS_NAME + "]");
|
||||
|
@ -533,6 +535,17 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// injections alter the type parameters required for the user to call this method, since some are injected by compiler
|
||||
if (annotations.containsKey(InjectConstantAnnotation.class)) {
|
||||
int numInjections = ((InjectConstantAnnotation)annotations.get(InjectConstantAnnotation.class)).injects.size();
|
||||
|
||||
if (numInjections > 0) {
|
||||
typeParameters.subList(0, numInjections).clear();
|
||||
}
|
||||
|
||||
typeParametersSize = typeParameters.size();
|
||||
}
|
||||
|
||||
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||
|
@ -562,7 +575,6 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
|
||||
MethodType methodType = methodHandle.type();
|
||||
|
||||
boolean isStatic = augmentedClass == null && Modifier.isStatic(javaMethod.getModifiers());
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
|
||||
PainlessMethod existingPainlessMethod = isStatic ?
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.elasticsearch.painless.lookup;
|
||||
|
||||
import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -72,7 +74,7 @@ import java.util.Objects;
|
|||
* </ul>
|
||||
*/
|
||||
public final class PainlessLookupUtility {
|
||||
|
||||
|
||||
/**
|
||||
* The name for an anonymous class.
|
||||
*/
|
||||
|
@ -359,7 +361,34 @@ public final class PainlessLookupUtility {
|
|||
public static String buildPainlessFieldKey(String fieldName) {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an array of injectable constants for a specific {@link PainlessMethod}
|
||||
* derived from an {@link org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation}.
|
||||
*/
|
||||
public static Object[] buildInjections(PainlessMethod painlessMethod, Map<String, Object> constants) {
|
||||
if (painlessMethod.annotations.containsKey(InjectConstantAnnotation.class) == false) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
List<String> names = ((InjectConstantAnnotation)painlessMethod.annotations.get(InjectConstantAnnotation.class)).injects;
|
||||
Object[] injections = new Object[names.size()];
|
||||
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
String name = names.get(i);
|
||||
Object constant = constants.get(name);
|
||||
|
||||
if (constant == null) {
|
||||
throw new IllegalStateException("constant [" + name + "] not found for injection into method " +
|
||||
"[" + buildPainlessMethodKey(painlessMethod.javaMethod.getName(), painlessMethod.typeParameters.size()) + "]");
|
||||
}
|
||||
|
||||
injections[i] = constant;
|
||||
}
|
||||
|
||||
return injections;
|
||||
}
|
||||
|
||||
private PainlessLookupUtility() {
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.phase;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.CompilerSettings;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
|
@ -2049,7 +2050,7 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
|
|||
"not a statement: regex constant [" + pattern + "] with flags [" + flags + "] not used"));
|
||||
}
|
||||
|
||||
if (semanticScope.getScriptScope().getCompilerSettings().areRegexesEnabled() == false) {
|
||||
if (semanticScope.getScriptScope().getCompilerSettings().areRegexesEnabled() == CompilerSettings.RegexEnabled.FALSE) {
|
||||
throw userRegexNode.createError(new IllegalStateException("Regexes are disabled. Set [script.painless.regex.enabled] to [true] "
|
||||
+ "in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep "
|
||||
+ "recursion and long loops."));
|
||||
|
@ -2228,7 +2229,8 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
|
|||
semanticScope.putDecoration(userLambdaNode, new EncodingDecoration(defReferenceEncoding));
|
||||
} else {
|
||||
FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
|
||||
location, targetType.getTargetType(), "this", name, capturedVariables.size());
|
||||
location, targetType.getTargetType(), "this", name, capturedVariables.size(),
|
||||
scriptScope.getCompilerSettings().asMap());
|
||||
valueType = targetType.getTargetType();
|
||||
semanticScope.putDecoration(userLambdaNode, new ReferenceDecoration(ref));
|
||||
}
|
||||
|
@ -2276,7 +2278,8 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
|
|||
semanticScope.putDecoration(userFunctionRefNode, new EncodingDecoration(defReferenceEncoding));
|
||||
} else {
|
||||
FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
|
||||
location, targetType.getTargetType(), symbol, methodName, 0);
|
||||
location, targetType.getTargetType(), symbol, methodName, 0,
|
||||
scriptScope.getCompilerSettings().asMap());
|
||||
valueType = targetType.getTargetType();
|
||||
semanticScope.putDecoration(userFunctionRefNode, new ReferenceDecoration(ref));
|
||||
}
|
||||
|
@ -2309,7 +2312,8 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
|
|||
// static case
|
||||
if (captured.getType() != def.class) {
|
||||
FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), location,
|
||||
targetType.getTargetType(), captured.getCanonicalTypeName(), methodName, 1);
|
||||
targetType.getTargetType(), captured.getCanonicalTypeName(), methodName, 1,
|
||||
scriptScope.getCompilerSettings().asMap());
|
||||
semanticScope.putDecoration(userFunctionRefNode, new ReferenceDecoration(ref));
|
||||
}
|
||||
}
|
||||
|
@ -2358,7 +2362,8 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
|
|||
scriptScope.putDecoration(userNewArrayFunctionRefNode, new EncodingDecoration(defReferenceEncoding));
|
||||
} else {
|
||||
FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
|
||||
userNewArrayFunctionRefNode.getLocation(), targetType.getTargetType(), "this", name, 0);
|
||||
userNewArrayFunctionRefNode.getLocation(), targetType.getTargetType(), "this", name, 0,
|
||||
scriptScope.getCompilerSettings().asMap());
|
||||
valueType = targetType.getTargetType();
|
||||
semanticScope.putDecoration(userNewArrayFunctionRefNode, new ReferenceDecoration(ref));
|
||||
}
|
||||
|
|
|
@ -210,6 +210,7 @@ import java.lang.reflect.Modifier;
|
|||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope> {
|
||||
|
@ -219,7 +220,7 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
/**
|
||||
* This injects additional ir nodes required for resolving the def type at runtime.
|
||||
* This includes injection of ir nodes to add a function to call
|
||||
* {@link DefBootstrap#bootstrap(PainlessLookup, FunctionTable, Lookup, String, MethodType, int, int, Object...)}
|
||||
* {@link DefBootstrap#bootstrap(PainlessLookup, FunctionTable, Map, Lookup, String, MethodType, int, int, Object...)}
|
||||
* to do the runtime resolution, and several supporting static fields.
|
||||
*/
|
||||
protected void injectBootstrapMethod(ScriptScope scriptScope) {
|
||||
|
@ -241,6 +242,13 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
|
||||
irClassNode.addFieldNode(irFieldNode);
|
||||
|
||||
irFieldNode = new FieldNode(internalLocation);
|
||||
irFieldNode.setModifiers(modifiers);
|
||||
irFieldNode.setFieldType(Map.class);
|
||||
irFieldNode.setName("$COMPILERSETTINGS");
|
||||
|
||||
irClassNode.addFieldNode(irFieldNode);
|
||||
|
||||
// adds the bootstrap method required for dynamic binding for def type resolution
|
||||
internalLocation = new Location("$internal$injectDefBootstrapMethod", 0);
|
||||
|
||||
|
@ -284,6 +292,7 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
DefBootstrap.class.getMethod("bootstrap",
|
||||
PainlessLookup.class,
|
||||
FunctionTable.class,
|
||||
Map.class,
|
||||
Lookup.class,
|
||||
String.class,
|
||||
MethodType.class,
|
||||
|
@ -295,6 +304,7 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
Arrays.asList(
|
||||
PainlessLookup.class,
|
||||
FunctionTable.class,
|
||||
Map.class,
|
||||
Lookup.class,
|
||||
String.class,
|
||||
MethodType.class,
|
||||
|
@ -324,6 +334,13 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
|
||||
invokeCallNode.addArgumentNode(irLoadFieldMemberNode);
|
||||
|
||||
irLoadFieldMemberNode = new LoadFieldMemberNode(internalLocation);
|
||||
irLoadFieldMemberNode.setExpressionType(Map.class);
|
||||
irLoadFieldMemberNode.setName("$COMPILERSETTINGS");
|
||||
irLoadFieldMemberNode.setStatic(true);
|
||||
|
||||
invokeCallNode.addArgumentNode(irLoadFieldMemberNode);
|
||||
|
||||
LoadVariableNode irLoadVariableNode = new LoadVariableNode(internalLocation);
|
||||
irLoadVariableNode.setExpressionType(Lookup.class);
|
||||
irLoadVariableNode.setName("methodHandlesLookup");
|
||||
|
@ -939,6 +956,9 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
|
||||
BinaryMathNode irBinaryMathNode = new BinaryMathNode(userBinaryNode.getLocation());
|
||||
|
||||
if (operation == Operation.MATCH || operation == Operation.FIND) {
|
||||
irBinaryMathNode.setRegexLimit(scriptScope.getCompilerSettings().getRegexLimitFactor());
|
||||
}
|
||||
irBinaryMathNode.setBinaryType(scriptScope.getDecoration(userBinaryNode, BinaryType.class).getBinaryType());
|
||||
irBinaryMathNode.setShiftType(shiftType);
|
||||
irBinaryMathNode.setOperation(operation);
|
||||
|
@ -1707,9 +1727,27 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
|
|||
}
|
||||
|
||||
InvokeCallNode irInvokeCallNode = new InvokeCallNode(userCallNode.getLocation());
|
||||
PainlessMethod method = scriptScope.getDecoration(userCallNode, StandardPainlessMethod.class).getStandardPainlessMethod();
|
||||
Object[] injections = PainlessLookupUtility.buildInjections(method, scriptScope.getCompilerSettings().asMap());
|
||||
Class<?>[] parameterTypes = method.javaMethod.getParameterTypes();
|
||||
int augmentedOffset = method.javaMethod.getDeclaringClass() == method.targetClass ? 0 : 1;
|
||||
|
||||
for (AExpression userArgumentNode : userCallNode.getArgumentNodes()) {
|
||||
irInvokeCallNode.addArgumentNode(injectCast(userArgumentNode, scriptScope));
|
||||
for (int i = 0; i < injections.length; i++) {
|
||||
Object injection = injections[i];
|
||||
Class<?> parameterType = parameterTypes[i + augmentedOffset];
|
||||
|
||||
if (parameterType != PainlessLookupUtility.typeToUnboxedType(injection.getClass())) {
|
||||
throw new IllegalStateException("illegal tree structure");
|
||||
}
|
||||
|
||||
ConstantNode constantNode = new ConstantNode(userCallNode.getLocation());
|
||||
constantNode.setExpressionType(parameterType);
|
||||
constantNode.setConstant(injection);
|
||||
irInvokeCallNode.addArgumentNode(constantNode);
|
||||
}
|
||||
|
||||
for (AExpression userCallArgumentNode : userCallNode.getArgumentNodes()) {
|
||||
irInvokeCallNode.addArgumentNode(injectCast(userCallArgumentNode, scriptScope));
|
||||
}
|
||||
|
||||
irInvokeCallNode.setExpressionType(scriptScope.getDecoration(userCallNode, ValueType.class).getValueType());;
|
||||
|
|
|
@ -66,6 +66,7 @@ public class ScriptScope extends Decorator {
|
|||
staticConstants.put("$SOURCE", scriptSource);
|
||||
staticConstants.put("$DEFINITION", painlessLookup);
|
||||
staticConstants.put("$FUNCTIONS", functionTable);
|
||||
staticConstants.put("$COMPILERSETTINGS", compilerSettings.asMap());
|
||||
}
|
||||
|
||||
public PainlessLookup getPainlessLookup() {
|
||||
|
|
|
@ -27,12 +27,12 @@ class java.util.regex.Pattern {
|
|||
# the script is run which is super slow. LRegex generates code that calls this method but it skips these checks.
|
||||
Predicate asPredicate()
|
||||
int flags()
|
||||
Matcher matcher(CharSequence)
|
||||
Matcher org.elasticsearch.painless.api.Augmentation matcher(int, CharSequence) @inject_constant[1="regex_limit_factor"]
|
||||
String pattern()
|
||||
String quote(String)
|
||||
String[] split(CharSequence)
|
||||
String[] split(CharSequence,int)
|
||||
Stream splitAsStream(CharSequence)
|
||||
String[] org.elasticsearch.painless.api.Augmentation split(int, CharSequence) @inject_constant[1="regex_limit_factor"]
|
||||
String[] org.elasticsearch.painless.api.Augmentation split(int, CharSequence,int) @inject_constant[1="regex_limit_factor"]
|
||||
Stream org.elasticsearch.painless.api.Augmentation splitAsStream(int, CharSequence) @inject_constant[1="regex_limit_factor"]
|
||||
}
|
||||
|
||||
class java.util.regex.Matcher {
|
||||
|
@ -58,6 +58,7 @@ class java.util.regex.Matcher {
|
|||
String replaceFirst(String)
|
||||
boolean requireEnd()
|
||||
Matcher reset()
|
||||
# Note: Do not whitelist Matcher.reset(String), it subverts regex limiting
|
||||
int start()
|
||||
int start(int)
|
||||
Matcher useAnchoringBounds(boolean)
|
||||
|
|
|
@ -40,6 +40,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testOneType() throws Throwable {
|
||||
CallSite site = DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
|
@ -61,6 +62,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testTwoTypes() throws Throwable {
|
||||
CallSite site = DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
|
@ -87,6 +89,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
assertEquals(5, DefBootstrap.PIC.MAX_DEPTH);
|
||||
CallSite site = DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
|
@ -114,6 +117,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testMegamorphic() throws Throwable {
|
||||
DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"size",
|
||||
MethodType.methodType(int.class, Object.class),
|
||||
|
@ -147,6 +151,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNullGuardAdd() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, Object.class, Object.class),
|
||||
|
@ -160,6 +165,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNullGuardAddWhenCached() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, Object.class, Object.class),
|
||||
|
@ -174,6 +180,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNullGuardEq() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"eq",
|
||||
MethodType.methodType(boolean.class, Object.class, Object.class),
|
||||
|
@ -188,6 +195,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNullGuardEqWhenCached() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"eq",
|
||||
MethodType.methodType(boolean.class, Object.class, Object.class),
|
||||
|
@ -207,6 +215,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNoNullGuardAdd() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, int.class, Object.class),
|
||||
|
@ -222,6 +231,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
public void testNoNullGuardAddWhenCached() throws Throwable {
|
||||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup,
|
||||
new FunctionTable(),
|
||||
Collections.emptyMap(),
|
||||
MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, int.class, Object.class),
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class FeatureTestAugmentationObject {
|
||||
|
||||
public static int getTotal(FeatureTestObject ft) {
|
||||
return ft.getX() + ft.getY();
|
||||
}
|
||||
|
@ -28,5 +31,26 @@ public class FeatureTestAugmentationObject {
|
|||
return getTotal(ft) + add;
|
||||
}
|
||||
|
||||
public static int augmentInjectTimesX(FeatureTestObject ft, int injected, short user) {
|
||||
return ft.getX() * injected * user;
|
||||
}
|
||||
|
||||
public static int augmentTimesSupplier(FeatureTestObject ft, Function<Short, Integer> fn, short fnArg, int userArg) {
|
||||
return fn.apply(fnArg) * userArg;
|
||||
}
|
||||
|
||||
public static int augmentInjectWithLambda(FeatureTestObject ft, int injected, Function<Short, Integer> fn, short arg) {
|
||||
return ft.getX()*fn.apply(arg)*injected;
|
||||
}
|
||||
|
||||
public static int augmentInjectMultiTimesX(FeatureTestObject ft, int inject1, int inject2, short user) {
|
||||
return ft.getX() * (inject1 + inject2) * user;
|
||||
}
|
||||
|
||||
public static int augmentInjectMultiWithLambda(FeatureTestObject ft,
|
||||
int inject1, int inject2, int inject3, int inject4, Function<Short, Integer> fn, short arg) {
|
||||
return ft.getX()*fn.apply(arg)*(inject1 + inject2 + inject3 + inject4);
|
||||
}
|
||||
|
||||
private FeatureTestAugmentationObject() {}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@ public class FeatureTestObject {
|
|||
return number.intValue();
|
||||
}
|
||||
|
||||
public static int staticNumberArgument(int injected, int userArgument) {
|
||||
return injected * userArgument;
|
||||
}
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
public int z;
|
||||
|
@ -90,6 +94,26 @@ public class FeatureTestObject {
|
|||
this.i = i;
|
||||
}
|
||||
|
||||
public int injectTimesX(int injected, short user) {
|
||||
return this.x * injected * user;
|
||||
}
|
||||
|
||||
public int timesSupplier(Function<Short, Integer> fn, short fnArg, int userArg) {
|
||||
return fn.apply(fnArg) * userArg;
|
||||
}
|
||||
|
||||
public int injectWithLambda(int injected, Function<Short, Integer> fn, short arg) {
|
||||
return this.x*fn.apply(arg)*injected;
|
||||
}
|
||||
|
||||
public int injectMultiTimesX(int inject1, int inject2, int inject3, short user) {
|
||||
return this.x * (inject1 + inject2 + inject3) * user;
|
||||
}
|
||||
|
||||
public int injectMultiWithLambda(int inject1, int inject2, int inject3, Function<Short, Integer> fn, short arg) {
|
||||
return this.x*fn.apply(arg)*(inject1 + inject2 + inject3);
|
||||
}
|
||||
|
||||
public Double mixedAdd(int i, Byte b, char c, Float f) {
|
||||
return (double)(i + b + c + f);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** Currently just a dummy class for testing a few features not yet exposed by whitelist! */
|
||||
public class FeatureTestObject2 {
|
||||
public FeatureTestObject2() {super();}
|
||||
public static int staticNumberArgument(int injected, int userArgument) {
|
||||
return injected * userArgument;
|
||||
}
|
||||
public static int staticNumberArgument2(int userArgument1, int userArgument2) {
|
||||
return userArgument1 * userArgument2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public class InjectionTests extends ScriptTestCase {
|
||||
|
||||
public void testInjection() {
|
||||
assertEquals(16,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject.staticNumberArgument(8);"));
|
||||
}
|
||||
|
||||
public void testInstanceInjection() {
|
||||
assertEquals(1000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.injectTimesX(5)"));
|
||||
}
|
||||
|
||||
public void testInstanceInjectWithLambda() {
|
||||
assertEquals(2000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.injectWithLambda(x -> 2*x, 5)"));
|
||||
}
|
||||
|
||||
public void testInstanceInjectWithDefLambda() {
|
||||
assertEquals(2000,
|
||||
exec("def f = new org.elasticsearch.painless.FeatureTestObject(100, 0); f.injectWithLambda(x -> 2*x, (short)5)"));
|
||||
}
|
||||
|
||||
public void testInjectionOnDefNoInject() {
|
||||
assertEquals(1000,
|
||||
exec("def d = new org.elasticsearch.painless.FeatureTestObject(100, 0); d.injectTimesX((short)5)"));
|
||||
}
|
||||
|
||||
public void testInjectionOnMethodReference() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"org.elasticsearch.painless.FeatureTestObject ft1 = " +
|
||||
" new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testInjectionOnMethodReference2() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"org.elasticsearch.painless.FeatureTestObject ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testInjectionOnMethodReference3() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceInjection() {
|
||||
assertEquals(1000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.augmentInjectTimesX(5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceInjectWithLambda() {
|
||||
assertEquals(2000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.augmentInjectWithLambda(x -> 2*x, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceInjectWithDefLambda() {
|
||||
assertEquals(2000,
|
||||
exec("def f = new org.elasticsearch.painless.FeatureTestObject(100, 0); f.augmentInjectWithLambda(x -> 2*x, (short)5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInjectionOnDefNoInject() {
|
||||
assertEquals(1000,
|
||||
exec("def d = new org.elasticsearch.painless.FeatureTestObject(100, 0); d.augmentInjectTimesX((short)5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInjectionOnMethodReference() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"org.elasticsearch.painless.FeatureTestObject ft1 = " +
|
||||
" new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInjectionOnMethodReference2() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"org.elasticsearch.painless.FeatureTestObject ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInjectionOnMethodReference3() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testInstanceMultiInjection() {
|
||||
assertEquals(6000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.injectMultiTimesX(5)"));
|
||||
}
|
||||
|
||||
public void testInstanceMultiInjectWithLambda() {
|
||||
assertEquals(8000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.injectMultiWithLambda(x -> 2*x, 5)"));
|
||||
}
|
||||
|
||||
public void testInstanceMultiInjectWithDefLambda() {
|
||||
assertEquals(2000,
|
||||
exec("def f = new org.elasticsearch.painless.FeatureTestObject(100, 0); f.injectWithLambda(x -> 2*x, (short)5)"));
|
||||
}
|
||||
|
||||
public void testMultiInjectionOnDefNoMultiInject() {
|
||||
assertEquals(6000,
|
||||
exec("def d = new org.elasticsearch.painless.FeatureTestObject(100, 0); d.injectMultiTimesX((short)5)"));
|
||||
}
|
||||
|
||||
public void testMultiInjectionOnMethodReference() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"org.elasticsearch.painless.FeatureTestObject ft1 = " +
|
||||
" new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testMultiInjectionOnMethodReference2() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"org.elasticsearch.painless.FeatureTestObject ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testMultiInjectionOnMethodReference3() {
|
||||
assertEquals(60,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.timesSupplier(ft0::injectTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceMultiInjection() {
|
||||
assertEquals(5000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.augmentInjectMultiTimesX(5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceMultiInjectWithLambda() {
|
||||
assertEquals(20000,
|
||||
exec("org.elasticsearch.painless.FeatureTestObject f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.augmentInjectMultiWithLambda(x -> 2*x, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedInstanceMultiInjectWithDefLambda() {
|
||||
assertEquals(20000,
|
||||
exec("def f = new org.elasticsearch.painless.FeatureTestObject(100, 0); " +
|
||||
"f.augmentInjectMultiWithLambda(x -> 2*x, (short)5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedMultiInjectionOnDefNoMultiInject() {
|
||||
assertEquals(5000,
|
||||
exec("def d = new org.elasticsearch.painless.FeatureTestObject(100, 0); d.augmentInjectMultiTimesX((short)5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedMultiInjectionOnMethodReference() {
|
||||
assertEquals(300,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"org.elasticsearch.painless.FeatureTestObject ft1 = " +
|
||||
" new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectMultiTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedMultiInjectionOnMethodReference2() {
|
||||
assertEquals(300,
|
||||
exec(
|
||||
"org.elasticsearch.painless.FeatureTestObject ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectMultiTimesX, (short)3, 5)"));
|
||||
}
|
||||
|
||||
public void testAugmentedMultiInjectionOnMethodReference3() {
|
||||
assertEquals(300,
|
||||
exec(
|
||||
"def ft0 = new org.elasticsearch.painless.FeatureTestObject(2, 0); " +
|
||||
"def ft1 = new org.elasticsearch.painless.FeatureTestObject(1000, 0); " +
|
||||
"ft1.augmentTimesSupplier(ft0::augmentInjectMultiTimesX, (short)3, 5)"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.elasticsearch.common.breaker.CircuitBreakingException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class RegexLimitTests extends ScriptTestCase {
|
||||
// This regex has backtracking due to .*?
|
||||
private final String pattern = "/abc.*?def/";
|
||||
private final String charSequence = "'abcdodef'";
|
||||
private final String splitCharSequence = "'0-abc-1-def-X-abc-2-def-Y-abc-3-def-Z-abc'";
|
||||
private final String regexCircuitMessage = "[scripting] Regular expression considered too many characters";
|
||||
|
||||
public void testRegexInject_Matcher() {
|
||||
String[] scripts = new String[]{pattern + ".matcher(" + charSequence + ").matches()",
|
||||
"Matcher m = " + pattern + ".matcher(" + charSequence + "); m.matches()"};
|
||||
for (String script : scripts) {
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Boolean.TRUE, exec(script));
|
||||
|
||||
// Backtracking means the regular expression will fail with limit factor 1 (don't consider more than each char once)
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInjectUnlimited_Matcher() {
|
||||
String[] scripts = new String[]{pattern + ".matcher(" + charSequence + ").matches()",
|
||||
"Matcher m = " + pattern + ".matcher(" + charSequence + "); m.matches()"};
|
||||
for (String script : scripts) {
|
||||
setRegexEnabled();
|
||||
assertEquals(Boolean.TRUE, exec(script));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInject_Def_Matcher() {
|
||||
String[] scripts = new String[]{"def p = " + pattern + "; p.matcher(" + charSequence + ").matches()",
|
||||
"def p = " + pattern + "; def m = p.matcher(" + charSequence + "); m.matches()"};
|
||||
for (String script : scripts) {
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Boolean.TRUE, exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
}
|
||||
|
||||
public void testMethodRegexInject_Ref_Matcher() {
|
||||
String script =
|
||||
"boolean isMatch(Function func) { func.apply(" + charSequence +").matches(); } " +
|
||||
"Pattern pattern = " + pattern + ";" +
|
||||
"isMatch(pattern::matcher)";
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Boolean.TRUE, exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_DefMethodRef_Matcher() {
|
||||
String script =
|
||||
"boolean isMatch(Function func) { func.apply(" + charSequence +").matches(); } " +
|
||||
"def pattern = " + pattern + ";" +
|
||||
"isMatch(pattern::matcher)";
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Boolean.TRUE, exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_SplitLimit() {
|
||||
String[] scripts = new String[]{pattern + ".split(" + splitCharSequence + ", 2)",
|
||||
"Pattern p = " + pattern + "; p.split(" + splitCharSequence + ", 2)"};
|
||||
for (String script : scripts) {
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-abc-2-def-Y-abc-3-def-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInjectUnlimited_SplitLimit() {
|
||||
String[] scripts = new String[]{pattern + ".split(" + splitCharSequence + ", 2)",
|
||||
"Pattern p = " + pattern + "; p.split(" + splitCharSequence + ", 2)"};
|
||||
for (String script : scripts) {
|
||||
setRegexEnabled();
|
||||
assertArrayEquals(new String[]{"0-", "-X-abc-2-def-Y-abc-3-def-Z-abc"}, (String[])exec(script));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInject_Def_SplitLimit() {
|
||||
String script = "def p = " + pattern + "; p.split(" + splitCharSequence + ", 2)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-abc-2-def-Y-abc-3-def-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_Ref_SplitLimit() {
|
||||
String script =
|
||||
"String[] splitLimit(BiFunction func) { func.apply(" + splitCharSequence + ", 2); } " +
|
||||
"Pattern pattern = " + pattern + ";" +
|
||||
"splitLimit(pattern::split)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-abc-2-def-Y-abc-3-def-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_DefMethodRef_SplitLimit() {
|
||||
String script =
|
||||
"String[] splitLimit(BiFunction func) { func.apply(" + splitCharSequence + ", 2); } " +
|
||||
"def pattern = " + pattern + ";" +
|
||||
"splitLimit(pattern::split)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-abc-2-def-Y-abc-3-def-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_Split() {
|
||||
String[] scripts = new String[]{pattern + ".split(" + splitCharSequence + ")",
|
||||
"Pattern p = " + pattern + "; p.split(" + splitCharSequence + ")"};
|
||||
for (String script : scripts) {
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInjectUnlimited_Split() {
|
||||
String[] scripts = new String[]{pattern + ".split(" + splitCharSequence + ")",
|
||||
"Pattern p = " + pattern + "; p.split(" + splitCharSequence + ")"};
|
||||
for (String script : scripts) {
|
||||
setRegexEnabled();
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[])exec(script));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInject_Def_Split() {
|
||||
String script = "def p = " + pattern + "; p.split(" + splitCharSequence + ")";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_Ref_Split() {
|
||||
String script =
|
||||
"String[] split(Function func) { func.apply(" + splitCharSequence + "); } " +
|
||||
"Pattern pattern = " + pattern + ";" +
|
||||
"split(pattern::split)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_DefMethodRef_Split() {
|
||||
String script =
|
||||
"String[] split(Function func) { func.apply(" + splitCharSequence +"); } " +
|
||||
"def pattern = " + pattern + ";" +
|
||||
"split(pattern::split)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[])exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_SplitAsStream() {
|
||||
String[] scripts = new String[]{pattern + ".splitAsStream(" + splitCharSequence + ").toArray(String[]::new)",
|
||||
"Pattern p = " + pattern + "; p.splitAsStream(" + splitCharSequence + ").toArray(String[]::new)"};
|
||||
for (String script : scripts) {
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[]) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInjectUnlimited_SplitAsStream() {
|
||||
String[] scripts = new String[]{pattern + ".splitAsStream(" + splitCharSequence + ").toArray(String[]::new)",
|
||||
"Pattern p = " + pattern + "; p.splitAsStream(" + splitCharSequence + ").toArray(String[]::new)"};
|
||||
for (String script : scripts) {
|
||||
setRegexEnabled();
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[]) exec(script));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRegexInject_Def_SplitAsStream() {
|
||||
String script = "def p = " + pattern + "; p.splitAsStream(" + splitCharSequence + ").toArray(String[]::new)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[]) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_Ref_SplitAsStream() {
|
||||
String script =
|
||||
"Stream splitStream(Function func) { func.apply(" + splitCharSequence +"); } " +
|
||||
"Pattern pattern = " + pattern + ";" +
|
||||
"splitStream(pattern::splitAsStream).toArray(String[]::new)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[]) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInject_DefMethodRef_SplitAsStream() {
|
||||
String script =
|
||||
"Stream splitStream(Function func) { func.apply(" + splitCharSequence +"); } " +
|
||||
"def pattern = " + pattern + ";" +
|
||||
"splitStream(pattern::splitAsStream).toArray(String[]::new)";
|
||||
setRegexLimitFactor(2);
|
||||
assertArrayEquals(new String[]{"0-", "-X-", "-Y-", "-Z-abc"}, (String[]) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInjectFindOperator() {
|
||||
String script = "if (" + charSequence + " =~ " + pattern + ") { return 100; } return 200";
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Integer.valueOf(100), (Integer) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testRegexInjectMatchOperator() {
|
||||
String script = "if (" + charSequence + " ==~ " + pattern + ") { return 100; } return 200";
|
||||
setRegexLimitFactor(2);
|
||||
assertEquals(Integer.valueOf(100), (Integer) exec(script));
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
}
|
||||
|
||||
public void testSnippetRegex() {
|
||||
String charSequence = String.join("", Collections.nCopies(100, "abcdef123456"));
|
||||
String script = "if ('" + charSequence + "' ==~ " + pattern + ") { return 100; } return 200";
|
||||
|
||||
setRegexLimitFactor(1);
|
||||
CircuitBreakingException cbe = expectScriptThrows(CircuitBreakingException.class, () -> exec(script));
|
||||
assertTrue(cbe.getMessage().contains(regexCircuitMessage));
|
||||
assertTrue(cbe.getMessage().contains(charSequence.subSequence(0, 61) + "..."));
|
||||
}
|
||||
|
||||
private void setRegexLimitFactor(int factor) {
|
||||
Settings settings = Settings.builder().put(CompilerSettings.REGEX_LIMIT_FACTOR.getKey(), factor).build();
|
||||
scriptEngine = new PainlessScriptEngine(settings, scriptContexts());
|
||||
}
|
||||
|
||||
private void setRegexEnabled() {
|
||||
Settings settings = Settings.builder().put(CompilerSettings.REGEX_ENABLED.getKey(), "true").build();
|
||||
scriptEngine = new PainlessScriptEngine(settings, scriptContexts());
|
||||
}
|
||||
}
|
|
@ -262,12 +262,6 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
|
|||
});
|
||||
}
|
||||
|
||||
public void testRegexDisabledByDefault() {
|
||||
IllegalStateException e = expectScriptThrows(IllegalStateException.class, () -> exec("return 'foo' ==~ /foo/"));
|
||||
assertEquals("Regexes are disabled. Set [script.painless.regex.enabled] to [true] in elasticsearch.yaml to allow them. "
|
||||
+ "Be careful though, regexes break out of Painless's protection against deep recursion and long loops.", e.getMessage());
|
||||
}
|
||||
|
||||
public void testCanNotOverrideRegexEnabled() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
||||
() -> exec("", null, singletonMap(CompilerSettings.REGEX_ENABLED.getKey(), "true"), false));
|
||||
|
@ -540,7 +534,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
|
|||
iae = expectScriptThrows(IllegalArgumentException.class, () -> exec("while (test0) {int x = 1;}"));
|
||||
assertEquals(iae.getMessage(), "cannot resolve symbol [test0]");
|
||||
}
|
||||
|
||||
|
||||
public void testPartialType() {
|
||||
int dots = randomIntBetween(1, 5);
|
||||
StringBuilder builder = new StringBuilder("test0");
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.elasticsearch.common.breaker.CircuitBreakingException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class LimitedCharSequenceTests extends ESTestCase {
|
||||
public void testBadFactor() {
|
||||
IllegalArgumentException badArg = expectThrows(IllegalArgumentException.class,
|
||||
() -> new LimitedCharSequence("abc", null, -1)
|
||||
);
|
||||
assertEquals("limitFactor must be positive", badArg.getMessage());
|
||||
|
||||
badArg = expectThrows(IllegalArgumentException.class,
|
||||
() -> new LimitedCharSequence("abc", null, 0)
|
||||
);
|
||||
assertEquals("limitFactor must be positive", badArg.getMessage());
|
||||
}
|
||||
|
||||
public void testLength() {
|
||||
String str = "abc";
|
||||
assertEquals(str.length(), new LimitedCharSequence("abc", null, 1).length());
|
||||
}
|
||||
|
||||
public void testCharAtEqualLimit() {
|
||||
String str = "abc";
|
||||
for (int limitFactor=1; limitFactor < 4; limitFactor++){
|
||||
CharSequence seq = new LimitedCharSequence(str, null, limitFactor);
|
||||
for (int i=0; i<str.length() * limitFactor; i++) {
|
||||
seq.charAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testCharAtAboveLimit() {
|
||||
String str = "abc";
|
||||
String patternStr = "a.*bc";
|
||||
Pattern p = Pattern.compile(patternStr);
|
||||
final CharSequence seq = new LimitedCharSequence(str, p, 2);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
seq.charAt(0);
|
||||
}
|
||||
CircuitBreakingException circuitBreakingException = expectThrows(CircuitBreakingException.class, () -> seq.charAt(0));
|
||||
assertEquals(
|
||||
"[scripting] Regular expression considered too many characters, " +
|
||||
"pattern: [a.*bc], " +
|
||||
"limit factor: [2], " +
|
||||
"char limit: [6], " +
|
||||
"count: [7], " +
|
||||
"wrapped: [abc], " +
|
||||
"this limit can be changed by changed by the [script.painless.regex.limit-factor] setting",
|
||||
circuitBreakingException.getMessage());
|
||||
|
||||
final CharSequence seqNullPattern = new LimitedCharSequence(str, null, 2);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
seqNullPattern.charAt(0);
|
||||
}
|
||||
circuitBreakingException = expectThrows(CircuitBreakingException.class, () -> seqNullPattern.charAt(0));
|
||||
assertEquals(
|
||||
"[scripting] Regular expression considered too many characters, " +
|
||||
"limit factor: [2], " +
|
||||
"char limit: [6], " +
|
||||
"count: [7], " +
|
||||
"wrapped: [abc], " +
|
||||
"this limit can be changed by changed by the [script.painless.regex.limit-factor] setting",
|
||||
circuitBreakingException.getMessage());
|
||||
}
|
||||
|
||||
public void testSubSequence() {
|
||||
assertEquals("def", (new LimitedCharSequence("abcdef", null, 1)).subSequence(3, 6));
|
||||
}
|
||||
|
||||
public void testToString() {
|
||||
String str = "abc";
|
||||
assertEquals(str, new LimitedCharSequence(str, null, 1).toString());
|
||||
}
|
||||
}
|
|
@ -21,9 +21,20 @@ class org.elasticsearch.painless.FeatureTestObject @no_import {
|
|||
boolean overloadedStatic()
|
||||
boolean overloadedStatic(boolean)
|
||||
int staticNumberTest(Number)
|
||||
int staticNumberArgument(int, int) @inject_constant[1="testInject0"]
|
||||
Double mixedAdd(int, Byte, char, Float)
|
||||
Object twoFunctionsOfX(Function,Function)
|
||||
void listInput(List)
|
||||
int injectTimesX(int, short) @inject_constant[1="testInject0"]
|
||||
int timesSupplier(Function, short, int)
|
||||
int injectWithLambda(int, Function, short) @inject_constant[1="testInject0"]
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject augmentInjectTimesX(int, short) @inject_constant[1="testInject0"]
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject augmentTimesSupplier(Function, short, int)
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject augmentInjectWithLambda(int, Function, short) @inject_constant[1="testInject0"]
|
||||
int injectMultiTimesX(int, int, int, short) @inject_constant[1="testInject0", 2="testInject1", 3="testInject2"]
|
||||
int injectMultiWithLambda(int, int, int, Function, short) @inject_constant[1="testInject0", 2="testInject1", 3="testInject0"]
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject augmentInjectMultiTimesX(int, int, short) @inject_constant[1="testInject1", 2="testInject2"]
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject augmentInjectMultiWithLambda(int, int, int, int, Function, short) @inject_constant[1="testInject2", 2="testInject1", 3="testInject1", 4="testInject2"]
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject getTotal()
|
||||
int org.elasticsearch.painless.FeatureTestAugmentationObject addToTotal(int)
|
||||
}
|
||||
|
@ -34,4 +45,4 @@ static_import {
|
|||
int addWithState(int, int, int, double) bound_to org.elasticsearch.painless.BindingsTests$BindingTestClass
|
||||
int addThisWithState(BindingsTests.BindingsTestScript, int, int, int, double) bound_to org.elasticsearch.painless.BindingsTests$ThisBindingTestClass
|
||||
int addEmptyThisWithState(BindingsTests.BindingsTestScript, int) bound_to org.elasticsearch.painless.BindingsTests$EmptyThisBindingTestClass
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
---
|
||||
"Regex in update fails":
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: test_1
|
||||
id: 1
|
||||
body:
|
||||
foo: bar
|
||||
count: 1
|
||||
|
||||
- do:
|
||||
catch: /Regexes are disabled. Set \[script.painless.regex.enabled\] to \[true\] in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep recursion and long loops./
|
||||
update:
|
||||
index: test_1
|
||||
id: 1
|
||||
body:
|
||||
script:
|
||||
lang: painless
|
||||
inline: "ctx._source.foo = params.bar ==~ /cat/"
|
||||
params: { bar: 'xxx' }
|
||||
|
||||
---
|
||||
"Regex enabled is not a dynamic setting":
|
||||
|
||||
- do:
|
||||
catch: /setting \[script.painless.regex.enabled\], not dynamically updateable/
|
||||
cluster.put_settings:
|
||||
body:
|
||||
transient:
|
||||
script.painless.regex.enabled: true
|
|
@ -329,7 +329,7 @@ public class ScriptContextInfo implements ToXContentObject, Writeable {
|
|||
Class<?>[] parameterTypes = execute.getParameterTypes();
|
||||
List<ParameterInfo> parameters = new ArrayList<>();
|
||||
if (parameterTypes.length > 0) {
|
||||
// TODO(stu): ensure empty/no PARAMETERS if parameterTypes.length == 0?
|
||||
// TODO: ensure empty/no PARAMETERS if parameterTypes.length == 0?
|
||||
String parametersFieldName = "PARAMETERS";
|
||||
|
||||
// See ScriptClassInfo.readArgumentNamesConstant
|
||||
|
|
Loading…
Reference in New Issue