Scripting: enable regular expressions by default (#63029) (#63272)

* 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:
Stuart Tettemer 2020-10-05 13:17:47 -05:00 committed by GitHub
parent d134b4f70b
commit 791a9d5102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1369 additions and 189 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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))
);

View File

@ -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();

View File

@ -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 + "]"
);
}
}
}

View File

@ -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()));
}

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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
);
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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 ?

View File

@ -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() {
}

View File

@ -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));
}

View File

@ -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());;

View File

@ -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() {

View File

@ -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)

View File

@ -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),

View File

@ -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() {}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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)"));
}
}

View File

@ -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());
}
}

View File

@ -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");

View File

@ -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());
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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