Merge pull request #18600 from rmuir/new_script_exception

replace ScriptException with a better one
This commit is contained in:
Robert Muir 2016-05-26 17:51:34 -04:00
commit 3f06d9f3b8
72 changed files with 686 additions and 307 deletions

View File

@ -200,41 +200,6 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
return rootCause;
}
/**
* Check whether this exception contains an exception of the given type:
* either it is of the given class itself or it contains a nested cause
* of the given type.
*
* @param exType the exception type to look for
* @return whether there is a nested exception of the specified type
*/
public boolean contains(Class<? extends Throwable> exType) {
if (exType == null) {
return false;
}
if (exType.isInstance(this)) {
return true;
}
Throwable cause = getCause();
if (cause == this) {
return false;
}
if (cause instanceof ElasticsearchException) {
return ((ElasticsearchException) cause).contains(exType);
} else {
while (cause != null) {
if (exType.isInstance(cause)) {
return true;
}
if (cause.getCause() == cause) {
break;
}
cause = cause.getCause();
}
return false;
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(this.getMessage());
@ -531,7 +496,8 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
org.elasticsearch.index.shard.IndexShardStartedException::new, 23),
SEARCH_CONTEXT_MISSING_EXCEPTION(org.elasticsearch.search.SearchContextMissingException.class,
org.elasticsearch.search.SearchContextMissingException::new, 24),
SCRIPT_EXCEPTION(org.elasticsearch.script.ScriptException.class, org.elasticsearch.script.ScriptException::new, 25),
GENERAL_SCRIPT_EXCEPTION(org.elasticsearch.script.GeneralScriptException.class,
org.elasticsearch.script.GeneralScriptException::new, 25),
BATCH_OPERATION_EXCEPTION(org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException.class,
org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException::new, 26),
SNAPSHOT_CREATION_EXCEPTION(org.elasticsearch.snapshots.SnapshotCreationException.class,
@ -741,7 +707,8 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
QUERY_SHARD_EXCEPTION(org.elasticsearch.index.query.QueryShardException.class,
org.elasticsearch.index.query.QueryShardException::new, 141),
NO_LONGER_PRIMARY_SHARD_EXCEPTION(ShardStateAction.NoLongerPrimaryShardException.class,
ShardStateAction.NoLongerPrimaryShardException::new, 142);
ShardStateAction.NoLongerPrimaryShardException::new, 142),
SCRIPT_EXCEPTION(org.elasticsearch.script.ScriptException.class, org.elasticsearch.script.ScriptException::new, 143);
final Class<? extends ElasticsearchException> exceptionClass;

View File

@ -26,7 +26,7 @@ import org.apache.lucene.search.Scorer;
import org.elasticsearch.script.ExplainableSearchScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.SearchScript;
import java.io.IOException;
@ -87,7 +87,7 @@ public class ScriptScoreFunction extends ScoreFunction {
scorer.score = subQueryScore;
double result = leafScript.runAsDouble();
if (Double.isNaN(result)) {
throw new ScriptException("script_score returned NaN");
throw new GeneralScriptException("script_score returned NaN");
}
return result;
}

View File

@ -100,7 +100,7 @@ public class JsonXContentGenerator implements XContentGenerator {
@Override
public final void usePrettyPrint() {
generator.setPrettyPrinter(new DefaultPrettyPrinter().withObjectIndenter(INDENTER));
generator.setPrettyPrinter(new DefaultPrettyPrinter().withObjectIndenter(INDENTER).withArrayIndenter(INDENTER));
prettyPrint = true;
}

View File

@ -0,0 +1,51 @@
/*
* 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.script;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import java.io.IOException;
/**
* Simple exception class from a script.
* <p>
* Use of this exception should generally be avoided, it doesn't provide
* much context or structure to users trying to debug scripting when
* things go wrong.
* @deprecated Use ScriptException for exceptions from the scripting engine,
* otherwise use a more appropriate exception (e.g. if thrown
* from various abstractions)
*/
@Deprecated
public class GeneralScriptException extends ElasticsearchException {
public GeneralScriptException(String msg) {
super(msg);
}
public GeneralScriptException(String msg, Throwable cause) {
super(msg, cause);
}
public GeneralScriptException(StreamInput in) throws IOException{
super(in);
}
}

View File

@ -1,3 +1,5 @@
package org.elasticsearch.script;
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
@ -17,27 +19,117 @@
* under the License.
*/
package org.elasticsearch.script;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import java.io.IOException;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
/**
*
* Exception from a scripting engine.
* <p>
* A ScriptException has the following components:
* <ul>
* <li>{@code message}: A short and simple summary of what happened, such as "compile error".
* <li>{@code cause}: The underlying cause of the exception.
* <li>{@code scriptStack}: An implementation-specific "stacktrace" for the error in the script.
* <li>{@code script}: Identifier for which script failed.
* <li>{@code lang}: Scripting engine language, such as "painless"
* </ul>
*/
@SuppressWarnings("serial")
public class ScriptException extends ElasticsearchException {
public ScriptException(String msg) {
super(msg);
private final List<String> scriptStack;
private final String script;
private final String lang;
/**
* Create a new ScriptException.
* @param message A short and simple summary of what happened, such as "compile error".
* Must not be {@code null}.
* @param cause The underlying cause of the exception. Must not be {@code null}.
* @param scriptStack An implementation-specific "stacktrace" for the error in the script.
* Must not be {@code null}, but can be empty (though this should be avoided if possible).
* @param script Identifier for which script failed. Must not be {@code null}.
* @param lang Scripting engine language, such as "painless". Must not be {@code null}.
* @throws NullPointerException if any parameters are {@code null}.
*/
public ScriptException(String message, Throwable cause, List<String> scriptStack, String script, String lang) {
super(Objects.requireNonNull(message), Objects.requireNonNull(cause));
this.scriptStack = Collections.unmodifiableList(Objects.requireNonNull(scriptStack));
this.script = Objects.requireNonNull(script);
this.lang = Objects.requireNonNull(lang);
}
public ScriptException(String msg, Throwable cause) {
super(msg, cause);
}
public ScriptException(StreamInput in) throws IOException{
/**
* Deserializes a ScriptException from a {@code StreamInput}
*/
public ScriptException(StreamInput in) throws IOException {
super(in);
scriptStack = Arrays.asList(in.readStringArray());
script = in.readString();
lang = in.readString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringArray(scriptStack.toArray(new String[0]));
out.writeString(script);
out.writeString(lang);
}
@Override
protected void innerToXContent(XContentBuilder builder, Params params) throws IOException {
super.innerToXContent(builder, params);
builder.field("script_stack", scriptStack);
builder.field("script", script);
builder.field("lang", lang);
}
/**
* Returns the stacktrace for the error in the script.
* @return a read-only list of frames, which may be empty.
*/
public List<String> getScriptStack() {
return scriptStack;
}
/**
* Returns the identifier for which script.
* @return script's name or source text that identifies the script.
*/
public String getScript() {
return script;
}
/**
* Returns the language of the script.
* @return the {@code lang} parameter of the scripting engine.
*/
public String getLang() {
return lang;
}
/**
* Returns a JSON version of this exception for debugging.
*/
public String toJsonString() {
try {
XContentBuilder json = XContentFactory.jsonBuilder().prettyPrint();
json.startObject();
toXContent(json, ToXContent.EMPTY_PARAMS);
json.endObject();
return json.string();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -31,7 +31,6 @@ import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.Strings;
@ -236,7 +235,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(lang);
if (canExecuteScript(lang, scriptEngineService, script.getType(), scriptContext) == false) {
throw new ScriptException("scripts of type [" + script.getType() + "], operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are disabled");
throw new IllegalStateException("scripts of type [" + script.getType() + "], operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are disabled");
}
// TODO: fix this through some API or something, that's wrong
@ -244,7 +243,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
boolean expression = "expression".equals(script.getLang());
boolean notSupported = scriptContext.getKey().equals(ScriptContext.Standard.UPDATE.getKey());
if (expression && notSupported) {
throw new ScriptException("scripts of type [" + script.getType() + "]," +
throw new UnsupportedOperationException("scripts of type [" + script.getType() + "]," +
" operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are not supported");
}
@ -306,8 +305,11 @@ public class ScriptService extends AbstractComponent implements Closeable {
// for the inline case, then its anonymous: null.
String actualName = (type == ScriptType.INLINE) ? null : name;
compiledScript = new CompiledScript(type, name, lang, scriptEngineService.compile(actualName, code, params));
} catch (ScriptException good) {
// TODO: remove this try-catch completely, when all script engines have good exceptions!
throw good; // its already good
} catch (Exception exception) {
throw new ScriptException("Failed to compile " + type + " script [" + name + "] using lang [" + lang + "]", exception);
throw new GeneralScriptException("Failed to compile " + type + " script [" + name + "] using lang [" + lang + "]", exception);
}
//Since the cache key is the script content itself we don't need to
@ -364,6 +366,9 @@ public class ScriptService extends AbstractComponent implements Closeable {
"skipping compile of script [{}], lang [{}] as all scripted operations are disabled for indexed scripts",
template.getScript(), scriptLang);
}
} catch (ScriptException good) {
// TODO: remove this when all script engines have good exceptions!
throw good; // its already good!
} catch (Exception e) {
throw new IllegalArgumentException("Unable to parse [" + template.getScript() +
"] lang [" + scriptLang + "]", e);

View File

@ -179,12 +179,32 @@ public class ESExceptionTests extends ESTestCase {
}
}
/**
* Check whether this exception contains an exception of the given type:
* either it is of the given class itself or it contains a nested cause
* of the given type.
*
* @param exType the exception type to look for
* @return whether there is a nested exception of the specified type
*/
private boolean contains(Throwable t, Class<? extends Throwable> exType) {
if (exType == null) {
return false;
}
for (Throwable cause = t; t != null; t = t.getCause()) {
if (exType.isInstance(cause)) {
return true;
}
}
return false;
}
public void testGetRootCause() {
Exception root = new RuntimeException("foobar");
ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar", new IllegalArgumentException("index is closed", root)));
assertEquals(root, exception.getRootCause());
assertTrue(exception.contains(RuntimeException.class));
assertFalse(exception.contains(EOFException.class));
assertTrue(contains(exception, RuntimeException.class));
assertFalse(contains(exception, EOFException.class));
}
public void testToString() {

View File

@ -663,7 +663,7 @@ public class ExceptionSerializationTests extends ESTestCase {
ids.put(22, null); // was CreateFailedEngineException
ids.put(23, org.elasticsearch.index.shard.IndexShardStartedException.class);
ids.put(24, org.elasticsearch.search.SearchContextMissingException.class);
ids.put(25, org.elasticsearch.script.ScriptException.class);
ids.put(25, org.elasticsearch.script.GeneralScriptException.class);
ids.put(26, org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException.class);
ids.put(27, org.elasticsearch.snapshots.SnapshotCreationException.class);
ids.put(28, org.elasticsearch.index.engine.DeleteFailedEngineException.class);
@ -778,6 +778,7 @@ public class ExceptionSerializationTests extends ESTestCase {
ids.put(140, org.elasticsearch.discovery.Discovery.FailedToCommitClusterStateException.class);
ids.put(141, org.elasticsearch.index.query.QueryShardException.class);
ids.put(142, ShardStateAction.NoLongerPrimaryShardException.class);
ids.put(143, org.elasticsearch.script.ScriptException.class);
Map<Class<? extends ElasticsearchException>, Integer> reverse = new HashMap<>();
for (Map.Entry<Integer, Class<? extends ElasticsearchException>> entry : ids.entrySet()) {

View File

@ -23,7 +23,7 @@ import org.apache.lucene.index.LeafReaderContext;
import org.elasticsearch.script.AbstractDoubleSearchScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.test.ESTestCase;
@ -57,7 +57,7 @@ public class ScriptScoreFunctionTests extends ESTestCase {
}
});
LeafScoreFunction leafScoreFunction = scoreFunction.getLeafScoreFunction(null);
ScriptException expected = expectThrows(ScriptException.class, () -> {
GeneralScriptException expected = expectThrows(GeneralScriptException.class, () -> {
leafScoreFunction.score(randomInt(), randomFloat());
});
assertTrue(expected.getMessage().contains("returned NaN"));

View File

@ -64,7 +64,7 @@ public class ScriptContextTests extends ESTestCase {
Script script = new Script("1", scriptType, MockScriptEngine.NAME, null);
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op"), Collections.emptyMap(), null);
fail("script compilation should have been rejected");
} catch (ScriptException e) {
} catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("scripts of type [" + scriptType + "], operation [" + PLUGIN_NAME + "_custom_globally_disabled_op] and lang [" + MockScriptEngine.NAME + "] are disabled"));
}
}
@ -76,7 +76,7 @@ public class ScriptContextTests extends ESTestCase {
try {
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_exp_disabled_op"), Collections.emptyMap(), null);
fail("script compilation should have been rejected");
} catch (ScriptException e) {
} catch (IllegalStateException e) {
assertTrue(e.getMessage(), e.getMessage().contains("scripts of type [inline], operation [" + PLUGIN_NAME + "_custom_exp_disabled_op] and lang [" + MockScriptEngine.NAME + "] are disabled"));
}

View File

@ -0,0 +1,99 @@
/*
* 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.script;
import org.elasticsearch.common.io.stream.DataOutputStreamOutput;
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.test.ESTestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Simple tests for {@link ScriptException} */
public class ScriptExceptionTests extends ESTestCase {
/** ensure we can round trip in serialization */
public void testRoundTrip() throws IOException {
ScriptException e = new ScriptException("messageData", new Exception("causeData"), Arrays.asList("stack1", "stack2"),
"sourceData", "langData");
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
StreamOutput output = new DataOutputStreamOutput(new DataOutputStream(bytes));
e.writeTo(output);
output.close();
StreamInput input = new InputStreamStreamInput(new ByteArrayInputStream(bytes.toByteArray()));
ScriptException e2 = new ScriptException(input);
input.close();
assertEquals(e.getMessage(), e2.getMessage());
assertEquals(e.getScriptStack(), e2.getScriptStack());
assertEquals(e.getScript(), e2.getScript());
assertEquals(e.getLang(), e2.getLang());
}
/** Test that our elements are present in the json output */
public void testJsonOutput() {
ScriptException e = new ScriptException("messageData", new Exception("causeData"), Arrays.asList("stack1", "stack2"),
"sourceData", "langData");
String json = e.toJsonString();
assertTrue(json.contains(e.getMessage()));
assertTrue(json.contains(e.getCause().getMessage()));
assertTrue(json.contains("stack1"));
assertTrue(json.contains("stack2"));
assertTrue(json.contains(e.getScript()));
assertTrue(json.contains(e.getLang()));
}
/** ensure the script stack is immutable */
public void testImmutableStack() {
ScriptException e = new ScriptException("a", new Exception(), Arrays.asList("element1", "element2"), "a", "b");
List<String> stack = e.getScriptStack();
expectThrows(UnsupportedOperationException.class, () -> {
stack.add("no");
});
}
/** ensure no parameters can be null */
public void testNoLeniency() {
expectThrows(NullPointerException.class, () -> {
new ScriptException(null, new Exception(), Collections.emptyList(), "a", "b");
});
expectThrows(NullPointerException.class, () -> {
new ScriptException("test", null, Collections.emptyList(), "a", "b");
});
expectThrows(NullPointerException.class, () -> {
new ScriptException("test", new Exception(), null, "a", "b");
});
expectThrows(NullPointerException.class, () -> {
new ScriptException("test", new Exception(), Collections.emptyList(), null, "b");
});
expectThrows(NullPointerException.class, () -> {
new ScriptException("test", new Exception(), Collections.emptyList(), "a", null);
});
}
}

View File

@ -481,7 +481,7 @@ public class ScriptServiceTests extends ESTestCase {
try {
scriptService.compile(new Script(script, scriptType, lang, null), scriptContext, Collections.emptyMap(), emptyClusterState());
fail("compile should have been rejected for lang [" + lang + "], script_type [" + scriptType + "], scripted_op [" + scriptContext + "]");
} catch(ScriptException e) {
} catch(IllegalStateException e) {
//all good
}
}

View File

@ -25,7 +25,7 @@ import java.util.Map;
import org.apache.lucene.expressions.Expression;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
/**
* A bridge to evaluate an {@link Expression} against a map of variables in the context
@ -45,7 +45,7 @@ public class ExpressionExecutableScript implements ExecutableScript {
int functionValuesLength = expression.variables.length;
if (vars.size() != functionValuesLength) {
throw new ScriptException("Error using " + compiledScript + ". " +
throw new GeneralScriptException("Error using " + compiledScript + ". " +
"The number of variables in an executable expression script [" +
functionValuesLength + "] must match the number of variables in the variable map" +
" [" + vars.size() + "].");
@ -72,12 +72,12 @@ public class ExpressionExecutableScript implements ExecutableScript {
double doubleValue = ((Number)value).doubleValue();
functionValuesMap.get(name).setValue(doubleValue);
} else {
throw new ScriptException("Error using " + compiledScript + ". " +
throw new GeneralScriptException("Error using " + compiledScript + ". " +
"Executable expressions scripts can only process numbers." +
" The variable [" + name + "] is not a number.");
}
} else {
throw new ScriptException("Error using " + compiledScript + ". " +
throw new GeneralScriptException("Error using " + compiledScript + ". " +
"The variable [" + name + "] does not exist in the executable expressions script.");
}
}
@ -87,7 +87,7 @@ public class ExpressionExecutableScript implements ExecutableScript {
try {
return ((Expression) compiledScript.compiled()).evaluate(NO_DOCUMENT, functionValuesArray);
} catch (Exception exception) {
throw new ScriptException("Error evaluating " + compiledScript, exception);
throw new GeneralScriptException("Error evaluating " + compiledScript, exception);
}
}
}

View File

@ -50,7 +50,7 @@ import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.ParseException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -107,7 +107,7 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
// NOTE: validation is delayed to allow runtime vars, and we don't have access to per index stuff here
return JavascriptCompiler.compile(scriptSource, JavascriptCompiler.DEFAULT_FUNCTIONS, loader);
} catch (ParseException e) {
throw new ScriptException("Failed to parse expression: " + scriptSource, e);
throw convertToScriptException("compile error", scriptSource, scriptSource, e);
}
}
});
@ -115,15 +115,15 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
@Override
public SearchScript search(CompiledScript compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
try {
Expression expr = (Expression)compiledScript.compiled();
MapperService mapper = lookup.doc().mapperService();
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
SimpleBindings bindings = new SimpleBindings();
ReplaceableConstValueSource specialValue = null;
for (String variable : expr.variables) {
Expression expr = (Expression)compiledScript.compiled();
MapperService mapper = lookup.doc().mapperService();
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
SimpleBindings bindings = new SimpleBindings();
ReplaceableConstValueSource specialValue = null;
for (String variable : expr.variables) {
try {
if (variable.equals("_score")) {
bindings.add(new SortField("_score", SortField.Type.SCORE));
} else if (variable.equals("_value")) {
@ -141,7 +141,7 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
if (value instanceof Number) {
bindings.add(variable, new DoubleConstValueSource(((Number) value).doubleValue()));
} else {
throw new ScriptException("Parameter [" + variable + "] must be a numeric type");
throw new ParseException("Parameter [" + variable + "] must be a numeric type", 0);
}
} else {
@ -150,10 +150,10 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
String variablename = "value"; // .value is the default for doc['field'], its optional.
VariableContext[] parts = VariableContext.parse(variable);
if (parts[0].text.equals("doc") == false) {
throw new ScriptException("Unknown variable [" + parts[0].text + "] in expression");
throw new ParseException("Unknown variable [" + parts[0].text + "]", 0);
}
if (parts.length < 2 || parts[1].type != VariableContext.Type.STR_INDEX) {
throw new ScriptException("Variable 'doc' in expression must be used with a specific field like: doc['myfield']");
throw new ParseException("Variable 'doc' must be used with a specific field like: doc['myfield']", 3);
} else {
fieldname = parts[1].text;
}
@ -163,17 +163,17 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
} else if (parts[2].type == VariableContext.Type.MEMBER) {
variablename = parts[2].text;
} else {
throw new ScriptException("Only member variables or member methods may be accessed on a field when not accessing the field directly");
throw new IllegalArgumentException("Only member variables or member methods may be accessed on a field when not accessing the field directly");
}
}
if (parts.length > 3) {
throw new ScriptException("Variable [" + variable + "] does not follow an allowed format of either doc['field'] or doc['field'].method()");
throw new IllegalArgumentException("Variable [" + variable + "] does not follow an allowed format of either doc['field'] or doc['field'].method()");
}
MappedFieldType fieldType = mapper.fullName(fieldname);
if (fieldType == null) {
throw new ScriptException("Field [" + fieldname + "] used in expression does not exist in mappings");
throw new ParseException("Field [" + fieldname + "] does not exist in mappings", 5);
}
IndexFieldData<?> fieldData = lookup.doc().fieldDataService().getForField(fieldType);
@ -205,18 +205,37 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
valueSource = NumericField.getMethod(fieldData, fieldname, methodname);
}
} else {
throw new ScriptException("Field [" + fieldname + "] used in expression must be numeric, date, or geopoint");
throw new ParseException("Field [" + fieldname + "] must be numeric, date, or geopoint", 5);
}
bindings.add(variable, valueSource);
}
} catch (Exception e) {
// we defer "binding" of variables until here: give context for that variable
throw convertToScriptException("link error", expr.sourceText, variable, e);
}
final boolean needsScores = expr.getSortField(bindings, false).needsScores();
return new ExpressionSearchScript(compiledScript, bindings, specialValue, needsScores);
} catch (Exception exception) {
throw new ScriptException("Error during search with " + compiledScript, exception);
}
final boolean needsScores = expr.getSortField(bindings, false).needsScores();
return new ExpressionSearchScript(compiledScript, bindings, specialValue, needsScores);
}
/**
* converts a ParseException at compile-time or link-time to a ScriptException
*/
private ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
List<String> stack = new ArrayList<>();
stack.add(portion);
StringBuilder pointer = new StringBuilder();
if (cause instanceof ParseException) {
int offset = ((ParseException) cause).getErrorOffset();
for (int i = 0; i < offset; i++) {
pointer.append(' ');
}
}
pointer.append("^---- HERE");
stack.add(pointer.toString());
throw new ScriptException(message, cause, stack, source, NAME);
}
@Override

View File

@ -33,7 +33,7 @@ import org.apache.lucene.search.Scorer;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.SearchScript;
/**
@ -73,7 +73,7 @@ class ExpressionSearchScript implements SearchScript {
try {
return values.doubleVal(docid);
} catch (Exception exception) {
throw new ScriptException("Error evaluating " + compiledScript, exception);
throw new GeneralScriptException("Error evaluating " + compiledScript, exception);
}
}
@ -114,7 +114,7 @@ class ExpressionSearchScript implements SearchScript {
if (value instanceof Number) {
specialValue.setValue(((Number)value).doubleValue());
} else {
throw new ScriptException("Cannot use expression with text variable using " + compiledScript);
throw new GeneralScriptException("Cannot use expression with text variable using " + compiledScript);
}
}
}

View File

@ -22,36 +22,50 @@ package org.elasticsearch.script.expression;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.test.ESSingleNodeTestCase;
import java.text.ParseException;
import java.util.Collections;
public class ExpressionTests extends ESSingleNodeTestCase {
public void testNeedsScores() {
ExpressionScriptEngineService service;
SearchLookup lookup;
@Override
public void setUp() throws Exception {
super.setUp();
IndexService index = createIndex("test", Settings.EMPTY, "type", "d", "type=double");
ExpressionScriptEngineService service = new ExpressionScriptEngineService(Settings.EMPTY);
SearchLookup lookup = new SearchLookup(index.mapperService(), index.fieldData(), null);
Object compiled = service.compile(null, "1.2", Collections.emptyMap());
SearchScript ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "expression", compiled), lookup, Collections.<String, Object>emptyMap());
assertFalse(ss.needsScores());
compiled = service.compile(null, "doc['d'].value", Collections.emptyMap());
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "expression", compiled), lookup, Collections.<String, Object>emptyMap());
assertFalse(ss.needsScores());
compiled = service.compile(null, "1/_score", Collections.emptyMap());
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "expression", compiled), lookup, Collections.<String, Object>emptyMap());
assertTrue(ss.needsScores());
compiled = service.compile(null, "doc['d'].value * _score", Collections.emptyMap());
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "expression", compiled), lookup, Collections.<String, Object>emptyMap());
assertTrue(ss.needsScores());
service = new ExpressionScriptEngineService(Settings.EMPTY);
lookup = new SearchLookup(index.mapperService(), index.fieldData(), null);
}
private SearchScript compile(String expression) {
Object compiled = service.compile(null, expression, Collections.emptyMap());
return service.search(new CompiledScript(ScriptType.INLINE, "randomName", "expression", compiled), lookup, Collections.<String, Object>emptyMap());
}
public void testNeedsScores() {
assertFalse(compile("1.2").needsScores());
assertFalse(compile("doc['d'].value").needsScores());
assertTrue(compile("1/_score").needsScores());
assertTrue(compile("doc['d'].value * _score").needsScores());
}
public void testCompileError() {
ScriptException e = expectThrows(ScriptException.class, () -> {
compile("doc['d'].value * *@#)(@$*@#$ + 4");
});
assertTrue(e.getCause() instanceof ParseException);
}
public void testLinkError() {
ScriptException e = expectThrows(ScriptException.class, () -> {
compile("doc['e'].value * 5");
});
assertTrue(e.getCause() instanceof ParseException);
}
}

View File

@ -44,7 +44,7 @@ import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
@ -324,7 +324,7 @@ public class MoreExpressionTests extends ESIntegTestCase {
assertThat(e.toString() + "should have contained ScriptException",
e.toString().contains("ScriptException"), equalTo(true));
assertThat(e.toString() + "should have contained compilation failure",
e.toString().contains("Failed to parse expression"), equalTo(true));
e.toString().contains("compile error"), equalTo(true));
}
}
@ -494,7 +494,7 @@ public class MoreExpressionTests extends ESIntegTestCase {
ees = new ExpressionExecutableScript(compiledScript, vars);
ees.run();
fail("An incorrect number of variables were allowed to be used in an expression.");
} catch (ScriptException se) {
} catch (GeneralScriptException se) {
message = se.getMessage();
assertThat(message + " should have contained number of variables", message.contains("number of variables"), equalTo(true));
}
@ -507,7 +507,7 @@ public class MoreExpressionTests extends ESIntegTestCase {
ees = new ExpressionExecutableScript(compiledScript, vars);
ees.run();
fail("A variable was allowed to be set that does not exist in the expression.");
} catch (ScriptException se) {
} catch (GeneralScriptException se) {
message = se.getMessage();
assertThat(message + " should have contained does not exist in", message.contains("does not exist in"), equalTo(true));
}
@ -520,7 +520,7 @@ public class MoreExpressionTests extends ESIntegTestCase {
ees = new ExpressionExecutableScript(compiledScript, vars);
ees.run();
fail("A non-number was allowed to be use in the expression.");
} catch (ScriptException se) {
} catch (GeneralScriptException se) {
message = se.getMessage();
assertThat(message + " should have contained process numbers", message.contains("process numbers"), equalTo(true));
}

View File

@ -50,7 +50,7 @@ import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.ScoreAccessor;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.lookup.LeafSearchLookup;
import org.elasticsearch.search.lookup.SearchLookup;
@ -193,7 +193,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
if (logger.isTraceEnabled()) {
logger.trace("exception compiling Groovy script:", e);
}
throw new ScriptException("failed to compile groovy script", e);
throw new GeneralScriptException("failed to compile groovy script", e);
}
}
@ -219,7 +219,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
}
return new GroovyScript(compiledScript, createScript(compiledScript.compiled(), allVars), this.logger);
} catch (ReflectiveOperationException e) {
throw new ScriptException("failed to build executable " + compiledScript, e);
throw new GeneralScriptException("failed to build executable " + compiledScript, e);
}
}
@ -239,7 +239,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
try {
scriptObject = createScript(compiledScript.compiled(), allVars);
} catch (ReflectiveOperationException e) {
throw new ScriptException("failed to build search " + compiledScript, e);
throw new GeneralScriptException("failed to build search " + compiledScript, e);
}
return new GroovyScript(compiledScript, scriptObject, leafLookup, logger);
}
@ -312,7 +312,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
if (logger.isTraceEnabled()) {
logger.trace("failed to run {}", e, compiledScript);
}
throw new ScriptException("failed to run " + compiledScript, e);
throw new GeneralScriptException("failed to run " + compiledScript, e);
}
}

View File

@ -24,7 +24,7 @@ import org.apache.lucene.util.Constants;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ESTestCase;
@ -157,7 +157,7 @@ public class GroovySecurityTests extends ESTestCase {
try {
doTest(script);
fail("did not get expected exception");
} catch (ScriptException expected) {
} catch (GeneralScriptException expected) {
Throwable cause = expected.getCause();
assertNotNull(cause);
if (exceptionClass.isAssignableFrom(cause.getClass()) == false) {

View File

@ -31,7 +31,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.GeneralScriptException;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.lookup.SearchLookup;
@ -185,7 +185,7 @@ public final class MustacheScriptEngineService extends AbstractComponent impleme
});
} catch (Exception e) {
logger.error("Error running {}", e, template);
throw new ScriptException("Error running " + template, e);
throw new GeneralScriptException("Error running " + template, e);
}
return result.bytes();
}

View File

@ -29,6 +29,7 @@ import java.net.URL;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.util.BitSet;
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
@ -93,9 +94,28 @@ final class Compiler {
* @return An {@link Executable} Painless script.
*/
static Executable compile(Loader loader, String name, String source, CompilerSettings settings) {
byte[] bytes = compile(name, source, settings);
if (source.length() > MAXIMUM_SOURCE_LENGTH) {
throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH +
" characters. The passed in script is " + source.length() + " characters. Consider using a" +
" plugin if a script longer than this length is a requirement.");
}
return createExecutable(loader, name, source, bytes);
Reserved reserved = new Reserved();
SSource root = Walker.buildPainlessTree(source, reserved, settings);
Variables variables = Analyzer.analyze(reserved, root);
BitSet expressions = new BitSet(source.length());
byte[] bytes = Writer.write(settings, name, source, variables, root, expressions);
try {
Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
java.lang.reflect.Constructor<? extends Executable> constructor =
clazz.getConstructor(String.class, String.class, BitSet.class);
return constructor.newInstance(name, source, expressions);
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
throw new IllegalStateException(
"An internal error occurred attempting to define the script [" + name + "].", exception);
}
}
/**
@ -115,27 +135,7 @@ final class Compiler {
SSource root = Walker.buildPainlessTree(source, reserved, settings);
Variables variables = Analyzer.analyze(reserved, root);
return Writer.write(settings, name, source, variables, root);
}
/**
* Generates an {@link Executable} that can run a Painless script.
* @param loader The {@link Loader} to define the script's class file.
* @param name The name of the script.
* @param source The source text of the script.
* @param bytes The ASM generated byte code to define the class with.
* @return A Painless {@link Executable} script.
*/
private static Executable createExecutable(Loader loader, String name, String source, byte[] bytes) {
try {
Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
java.lang.reflect.Constructor<? extends Executable> constructor = clazz.getConstructor(String.class, String.class);
return constructor.newInstance(name, source);
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
throw new IllegalStateException(
"An internal error occurred attempting to define the script [" + name + "].", exception);
}
return Writer.write(settings, name, source, variables, root, new BitSet(source.length()));
}
/**

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.search.lookup.LeafDocLookup;
import java.util.BitSet;
import java.util.Map;
/**
@ -31,10 +32,12 @@ public abstract class Executable {
private final String name;
private final String source;
private final BitSet statements;
public Executable(final String name, final String source) {
public Executable(String name, String source, BitSet statements) {
this.name = name;
this.source = source;
this.statements = statements;
}
public String getName() {
@ -45,6 +48,24 @@ public abstract class Executable {
return source;
}
/**
* Finds the start of the first statement boundary that is
* on or before {@code offset}. If one is not found, {@code -1}
* is returned.
*/
public int getPreviousStatement(int offset) {
return statements.previousSetBit(offset);
}
/**
* Finds the start of the first statement boundary that is
* after {@code offset}. If one is not found, {@code -1}
* is returned.
*/
public int getNextStatement(int offset) {
return statements.nextSetBit(offset+1);
}
public abstract Object execute(
final Map<String, Object> params, final Scorer scorer, final LeafDocLookup doc, final Object value);
}

View File

@ -30,6 +30,7 @@ import org.objectweb.asm.commons.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Deque;
import java.util.List;
@ -86,13 +87,15 @@ import static org.elasticsearch.painless.WriterConstants.UTILITY_TYPE;
* shared by the nodes of the Painless tree.
*/
public final class MethodWriter extends GeneratorAdapter {
private final BitSet statements;
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs = (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ?
null : new ArrayDeque<>();
MethodWriter(int access, Method method, org.objectweb.asm.Type[] exceptions, ClassVisitor cv) {
MethodWriter(int access, Method method, org.objectweb.asm.Type[] exceptions, ClassVisitor cv, BitSet statements) {
super(Opcodes.ASM5, cv.visitMethod(access, method.getName(), method.getDescriptor(), null, getInternalNames(exceptions)),
access, method.getName(), method.getDescriptor());
this.statements = statements;
}
private static String[] getInternalNames(final org.objectweb.asm.Type[] types) {
@ -106,8 +109,33 @@ public final class MethodWriter extends GeneratorAdapter {
return names;
}
public void writeLoopCounter(final int slot, final int count) {
/**
* Marks a new statement boundary.
* <p>
* This is invoked for each statement boundary (leaf {@code S*} nodes).
*/
public void writeStatementOffset(int offset) {
// ensure we don't have duplicate stuff going in here. can catch bugs
// (e.g. nodes get assigned wrong offsets by antlr walker)
assert statements.get(offset) == false;
statements.set(offset);
}
/**
* Encodes the offset into the line number table as {@code offset + 1}.
* <p>
* This is invoked before instructions that can hit exceptions.
*/
public void writeDebugInfo(int offset) {
// TODO: maybe track these in bitsets too? this is trickier...
Label label = new Label();
visitLabel(label);
visitLineNumber(offset + 1, label);
}
public void writeLoopCounter(int slot, int count, int offset) {
if (slot > -1) {
writeDebugInfo(offset);
final Label end = new Label();
iinc(slot, -count);

View File

@ -22,10 +22,13 @@ package org.elasticsearch.painless;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.search.lookup.LeafDocLookup;
import org.elasticsearch.search.lookup.LeafSearchLookup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -114,7 +117,63 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
*/
@Override
public Object run() {
return executable.execute(variables, scorer, doc, aggregationValue);
try {
return executable.execute(variables, scorer, doc, aggregationValue);
} catch (PainlessError | Exception t) {
throw convertToScriptException(t);
}
}
private ScriptException convertToScriptException(Throwable t) {
// create a script stack: this is just the script portion
List<String> scriptStack = new ArrayList<>();
for (StackTraceElement element : t.getStackTrace()) {
if (WriterConstants.CLASS_NAME.equals(element.getClassName())) {
// found the script portion
int offset = element.getLineNumber();
if (offset == -1) {
scriptStack.add("<<< unknown portion of script >>>");
} else {
offset--; // offset is 1 based, line numbers must be!
int startOffset = executable.getPreviousStatement(offset);
if (startOffset == -1) {
assert false; // should never happen unless we hit exc in ctor prologue...
startOffset = 0;
}
int endOffset = executable.getNextStatement(startOffset);
if (endOffset == -1) {
endOffset = executable.getSource().length();
}
// TODO: if this is still too long, truncate and use ellipses
String snippet = executable.getSource().substring(startOffset, endOffset);
scriptStack.add(snippet);
StringBuilder pointer = new StringBuilder();
for (int i = startOffset; i < offset; i++) {
pointer.append(' ');
}
pointer.append("^---- HERE");
scriptStack.add(pointer.toString());
}
break;
// but filter our own internal stacks (e.g. indy bootstrap)
} else if (!shouldFilter(element)) {
scriptStack.add(element.toString());
}
}
// build a name for the script:
final String name;
if (PainlessScriptEngineService.INLINE_NAME.equals(executable.getName())) {
name = executable.getSource();
} else {
name = executable.getName();
}
throw new ScriptException("runtime error", t, scriptStack, name, PainlessScriptEngineService.NAME);
}
/** returns true for methods that are part of the runtime */
private static boolean shouldFilter(StackTraceElement element) {
return element.getClassName().startsWith("org.elasticsearch.painless.") ||
element.getClassName().startsWith("java.lang.invoke.");
}
/**

View File

@ -33,15 +33,15 @@ import static org.elasticsearch.painless.WriterConstants.EXECUTE;
import static org.elasticsearch.painless.WriterConstants.MAP_GET;
import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
import java.util.BitSet;
/**
* Runs the writing phase of compilation using the Painless AST.
*/
final class Writer {
static byte[] write(final CompilerSettings settings,
String name, final String source, final Variables variables, final SSource root) {
final Writer writer = new Writer(settings, name, source, variables, root);
static byte[] write(CompilerSettings settings, String name, String source, Variables variables, SSource root, BitSet expressions) {
Writer writer = new Writer(settings, name, source, variables, root, expressions);
return writer.getBytes();
}
@ -54,8 +54,7 @@ final class Writer {
private final ClassWriter writer;
private final MethodWriter adapter;
private Writer(final CompilerSettings settings,
String name, final String source, final Variables variables, final SSource root) {
private Writer(CompilerSettings settings, String name, String source, Variables variables, SSource root, BitSet expressions) {
this.settings = settings;
this.scriptName = name;
this.source = source;
@ -67,7 +66,7 @@ final class Writer {
writeBegin();
writeConstructor();
adapter = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, null, writer);
adapter = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, null, writer, expressions);
writeExecute();
writeEnd();

View File

@ -29,6 +29,7 @@ import org.objectweb.asm.commons.Method;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.BitSet;
import java.util.Map;
/**
@ -42,7 +43,7 @@ public final class WriterConstants {
public final static String CLASS_NAME = BASE_CLASS_NAME + "$Script";
public final static Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/'));
public final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", String.class, String.class);
public final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", String.class, String.class, BitSet.class);
public final static Method EXECUTE =
getAsmMethod(Object.class, "execute", Map.class, Scorer.class, LeafDocLookup.class, Object.class);

View File

@ -383,7 +383,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
String name = declvar.ID().getText();
AExpression expression = declvar.expression() == null ? null : (AExpression)visitExpression(declvar.expression());
declarations.add(new SDeclaration(line(ctx), offset(ctx), location(ctx), type, name, expression));
declarations.add(new SDeclaration(line(declvar), offset(declvar), location(declvar), type, name, expression));
}
return new SDeclBlock(line(ctx), offset(ctx), location(ctx), declarations);

View File

@ -19,9 +19,6 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
/**
* The superclass for all other nodes.
*/
@ -51,15 +48,4 @@ public abstract class ANode {
public String error(final String message) {
return "Error " + location + ": " + message;
}
/**
* Writes line number information
* <p>
* Currently we emit line number data for for leaf S-nodes
*/
void writeDebugInfo(MethodWriter writer) {
Label label = new Label();
writer.visitLabel(label);
writer.visitLineNumber(line, label);
}
}

View File

@ -470,6 +470,7 @@ public final class EBinary extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (actual.sort == Sort.STRING && operation == Operation.ADD) {
if (!cat) {
writer.writeNewStrings();

View File

@ -51,6 +51,7 @@ final class ECast extends AExpression {
@Override
void write(MethodWriter writer) {
child.write(writer);
writer.writeDebugInfo(offset);
writer.writeCast(cast);
writer.writeBranch(tru, fals);
}

View File

@ -248,6 +248,10 @@ public final class EChain extends AExpression {
@Override
void write(MethodWriter writer) {
if (cat) {
writer.writeDebugInfo(offset);
}
if (cat) {
writer.writeNewStrings();
}

View File

@ -399,6 +399,7 @@ public final class EComp extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(offset);
boolean branch = tru != null || fals != null;
org.objectweb.asm.Type rtype = right.actual.type;
Sort rsort = right.actual.sort;

View File

@ -78,6 +78,7 @@ public final class EConditional extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(offset);
Label localfals = new Label();
Label end = new Label();

View File

@ -165,6 +165,7 @@ public final class EUnary extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (operation == Operation.NOT) {
if (tru == null && fals == null) {
Label localfals = new Label();

View File

@ -60,6 +60,7 @@ public final class LArrayLength extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
writer.arrayLength();
}

View File

@ -74,11 +74,13 @@ public final class LBrace extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
writer.arrayLoad(after.type);
}
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
writer.arrayStore(after.type);
}

View File

@ -91,6 +91,7 @@ public final class LCall extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
for (AExpression argument : arguments) {
argument.write(writer);
}

View File

@ -61,6 +61,7 @@ public final class LCast extends ALink {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(offset);
writer.writeCast(cast);
}

View File

@ -58,12 +58,14 @@ final class LDefArray extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_LOAD);
}
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_STORE);
}

View File

@ -67,6 +67,7 @@ final class LDefCall extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
StringBuilder signature = new StringBuilder();
signature.append('(');

View File

@ -55,12 +55,14 @@ final class LDefField extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.LOAD);
}
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.STORE);
}

View File

@ -105,6 +105,7 @@ public final class LField extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.getStatic(field.owner.type, field.javaName, field.type.type);
} else {
@ -114,6 +115,7 @@ public final class LField extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.putStatic(field.owner.type, field.javaName, field.type.type);
} else {

View File

@ -79,6 +79,7 @@ final class LListShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -92,6 +93,7 @@ final class LListShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -78,6 +78,7 @@ final class LMapShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -91,6 +92,7 @@ final class LMapShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -79,6 +79,7 @@ public final class LNewArray extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
for (AExpression argument : arguments) {
argument.write(writer);
}

View File

@ -98,6 +98,7 @@ public final class LNewObj extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
writer.newInstance(after.type);
if (load) {

View File

@ -84,6 +84,7 @@ final class LShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -97,6 +98,7 @@ final class LShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(offset);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -45,8 +45,6 @@ public final class SBreak extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.goTo(brake);
}
}

View File

@ -84,8 +84,7 @@ public final class SCatch extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label jump = new Label();
writer.mark(jump);

View File

@ -48,8 +48,6 @@ public final class SContinue extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.goTo(continu);
}
}

View File

@ -66,8 +66,7 @@ public final class SDeclaration extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
if (expression == null) {
switch (variable.type.sort) {
case VOID: throw new IllegalStateException(error("Illegal tree structure."));

View File

@ -86,8 +86,7 @@ public final class SDo extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label start = new Label();
Label begin = new Label();
Label end = new Label();
@ -103,7 +102,7 @@ public final class SDo extends AStatement {
condition.fals = end;
condition.write(writer);
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount));
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), offset);
writer.goTo(start);
writer.mark(end);

View File

@ -60,8 +60,7 @@ public final class SExpression extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
expression.write(writer);
if (methodEscape) {

View File

@ -127,8 +127,7 @@ public final class SFor extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label start = new Label();
Label begin = afterthought == null ? start : new Label();
Label end = new Label();
@ -160,10 +159,10 @@ public final class SFor extends AStatement {
++statementCount;
}
writer.writeLoopCounter(loopCounterSlot, statementCount);
writer.writeLoopCounter(loopCounterSlot, statementCount, offset);
block.write(writer);
} else {
writer.writeLoopCounter(loopCounterSlot, 1);
writer.writeLoopCounter(loopCounterSlot, 1, offset);
}
if (afterthought != null) {

View File

@ -68,8 +68,7 @@ public final class SIf extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label fals = new Label();
condition.fals = fals;

View File

@ -89,8 +89,7 @@ public final class SIfElse extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label end = new Label();
Label fals = elseblock != null ? new Label() : end;

View File

@ -52,8 +52,7 @@ public final class SReturn extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
expression.write(writer);
writer.returnValue();
}

View File

@ -50,8 +50,7 @@ public final class SThrow extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
expression.write(writer);
writer.throwException();
}

View File

@ -86,8 +86,7 @@ public final class STry extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label begin = new Label();
Label end = new Label();
Label exception = new Label();

View File

@ -92,8 +92,7 @@ public final class SWhile extends AStatement {
@Override
void write(MethodWriter writer) {
writeDebugInfo(writer);
writer.writeStatementOffset(offset);
Label begin = new Label();
Label end = new Label();
@ -103,13 +102,13 @@ public final class SWhile extends AStatement {
condition.write(writer);
if (block != null) {
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount));
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), offset);
block.continu = begin;
block.brake = end;
block.write(writer);
} else {
writer.writeLoopCounter(loopCounterSlot, 1);
writer.writeLoopCounter(loopCounterSlot, 1, offset);
}
if (block == null || !block.allEscape) {

View File

@ -120,34 +120,29 @@ public class CompoundAssignmentTests extends ScriptTestCase {
public void testDivisionByZero() {
// byte
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("byte x = 1; x /= 0; return x;");
fail("should have hit exception");
} catch (ArithmeticException expected) {}
});
// short
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("short x = 1; x /= 0; return x;");
fail("should have hit exception");
} catch (ArithmeticException expected) {}
});
// char
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("char x = 1; x /= 0; return x;");
fail("should have hit exception");
} catch (ArithmeticException expected) {}
});
// int
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("int x = 1; x /= 0; return x;");
fail("should have hit exception");
} catch (ArithmeticException expected) {}
});
// long
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("long x = 1; x /= 0; return x;");
fail("should have hit exception");
} catch (ArithmeticException expected) {}
});
}
public void testRemainder() {

View File

@ -21,10 +21,14 @@ package org.elasticsearch.painless;
public class DefOperationTests extends ScriptTestCase {
public void testIllegalCast() {
Exception exception = expectThrows(ClassCastException.class, () -> exec("def x = 1.0; int y = x; return y;"));
Exception exception = expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 1.0; int y = x; return y;");
});
assertTrue(exception.getMessage().contains("cannot be cast"));
exception = expectThrows(ClassCastException.class, () -> exec("def x = (short)1; byte y = x; return y;"));
exception = expectScriptThrows(ClassCastException.class, () -> {
exec("def x = (short)1; byte y = x; return y;");
});
assertTrue(exception.getMessage().contains("cannot be cast"));
}

View File

@ -173,7 +173,7 @@ public class DefOptimizationTests extends ScriptTestCase {
final String script = "int x;\ndef y = new HashMap();\ny['double'] = 1.0;\nx = y.get('double');\n";
assertBytecodeExists(script, "INVOKEDYNAMIC get(Ljava/lang/Object;Ljava/lang/String;)I");
final Exception exception = expectThrows(ClassCastException.class, () -> {
final Exception exception = expectScriptThrows(ClassCastException.class, () -> {
exec(script);
});
assertTrue(exception.getMessage().contains("Cannot cast java.lang.Double to java.lang.Integer"));

View File

@ -114,34 +114,22 @@ public class DivisionTests extends ScriptTestCase {
}
public void testDivideByZero() throws Exception {
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("int x = 1; int y = 0; return x / y;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("long x = 1L; long y = 0L; return x / y;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
}
public void testDivideByZeroConst() throws Exception {
try {
expectThrows(ArithmeticException.class, () -> {
exec("return 1/0;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
try {
expectThrows(ArithmeticException.class, () -> {
exec("return 1L/0L;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
}
}

View File

@ -34,7 +34,7 @@ public class OverloadTests extends ScriptTestCase {
public void testMethodDynamic() {
assertEquals(2, exec("def x = 'abc123abc'; return x.indexOf('c');"));
assertEquals(8, exec("def x = 'abc123abc'; return x.indexOf('c', 3);"));
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def x = 'abc123abc'; return x.indexOf('c', 3, 'bogus');");
});
assertTrue(expected.getMessage().contains("dynamic method [indexOf] with signature [(String,int,String)"));

View File

@ -114,19 +114,13 @@ public class RemainderTests extends ScriptTestCase {
}
public void testDivideByZero() throws Exception {
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("int x = 1; int y = 0; return x % y;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
try {
expectScriptThrows(ArithmeticException.class, () -> {
exec("long x = 1L; long y = 0L; return x % y;");
fail("should have hit exception");
} catch (ArithmeticException expected) {
// divide by zero
}
});
}
public void testDivideByZeroConst() throws Exception {

View File

@ -24,11 +24,13 @@ import org.elasticsearch.common.lucene.ScorerAware;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.util.Collections;
import junit.framework.AssertionFailedError;
import java.util.HashMap;
import java.util.Map;
@ -76,4 +78,27 @@ public abstract class ScriptTestCase extends ESTestCase {
final String asm = Debugger.toString(script);
assertTrue("bytecode not found", asm.contains(bytecode));
}
/** Checks a specific exception class is thrown (boxed inside ScriptException) and returns it. */
public static <T extends Throwable> T expectScriptThrows(Class<T> expectedType, ThrowingRunnable runnable) {
try {
runnable.run();
} catch (Throwable e) {
if (e instanceof ScriptException) {
e = e.getCause();
if (expectedType.isInstance(e)) {
return expectedType.cast(e);
}
} else {
AssertionFailedError assertion = new AssertionFailedError("Expected boxed ScriptException");
assertion.initCause(e);
throw assertion;
}
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
+ expectedType.getSimpleName());
assertion.initCause(e);
throw assertion;
}
throw new AssertionFailedError("Expected exception " + expectedType.getSimpleName());
}
}

View File

@ -162,32 +162,24 @@ public class StringTests extends ScriptTestCase {
assertEquals('c', exec("String s = \"c\"; (char)s"));
assertEquals('c', exec("String s = 'c'; (char)s"));
try {
ClassCastException expected = expectScriptThrows(ClassCastException.class, () -> {
assertEquals("cc", exec("return (String)(char)\"cc\""));
fail();
} catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
}
});
assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
try {
expected = expectScriptThrows(ClassCastException.class, () -> {
assertEquals("cc", exec("return (String)(char)'cc'"));
fail();
} catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
}
});
assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
try {
expected = expectScriptThrows(ClassCastException.class, () -> {
assertEquals('c', exec("String s = \"cc\"; (char)s"));
fail();
} catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
}
});
assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
try {
expected = expectScriptThrows(ClassCastException.class, () -> {
assertEquals('c', exec("String s = 'cc'; (char)s"));
fail();
} catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
}
});
assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
}
}

View File

@ -26,7 +26,7 @@ public class TryCatchTests extends ScriptTestCase {
/** throws an exception */
public void testThrow() {
RuntimeException exception = expectThrows(RuntimeException.class, () -> {
RuntimeException exception = expectScriptThrows(RuntimeException.class, () -> {
exec("throw new RuntimeException('test')");
});
assertEquals("test", exception.getMessage());
@ -48,7 +48,7 @@ public class TryCatchTests extends ScriptTestCase {
/** tries to catch a different type of exception */
public void testNoCatch() {
RuntimeException exception = expectThrows(RuntimeException.class, () -> {
RuntimeException exception = expectScriptThrows(RuntimeException.class, () -> {
exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (ArithmeticException e) { return 1; } return 2;",
Collections.singletonMap("param", "true"));

View File

@ -25,36 +25,40 @@ import java.util.Collections;
public class WhenThingsGoWrongTests extends ScriptTestCase {
public void testNullPointer() {
expectThrows(NullPointerException.class, () -> {
expectScriptThrows(NullPointerException.class, () -> {
exec("int x = params['missing']; return x;");
});
}
/** test "line numbers" in the bytecode, which are really 1-based offsets */
public void testLineNumbers() {
// trigger NPE at line 1 of the script
NullPointerException exception = expectThrows(NullPointerException.class, () -> {
NullPointerException exception = expectScriptThrows(NullPointerException.class, () -> {
exec("String x = null; boolean y = x.isEmpty();\n" +
"return y;");
});
assertEquals(1, exception.getStackTrace()[0].getLineNumber());
// null deref at x.isEmpty(), the '.' is offset 30 (+1)
assertEquals(30 + 1, exception.getStackTrace()[0].getLineNumber());
// trigger NPE at line 2 of the script
exception = expectThrows(NullPointerException.class, () -> {
exception = expectScriptThrows(NullPointerException.class, () -> {
exec("String x = null;\n" +
"return x.isEmpty();");
});
assertEquals(2, exception.getStackTrace()[0].getLineNumber());
// null deref at x.isEmpty(), the '.' is offset 25 (+1)
assertEquals(25 + 1, exception.getStackTrace()[0].getLineNumber());
// trigger NPE at line 3 of the script
exception = expectThrows(NullPointerException.class, () -> {
exception = expectScriptThrows(NullPointerException.class, () -> {
exec("String x = null;\n" +
"String y = x;\n" +
"return y.isEmpty();");
});
assertEquals(3, exception.getStackTrace()[0].getLineNumber());
// null deref at y.isEmpty(), the '.' is offset 39 (+1)
assertEquals(39 + 1, exception.getStackTrace()[0].getLineNumber());
// trigger NPE at line 4 in script (inside conditional)
exception = expectThrows(NullPointerException.class, () -> {
exception = expectScriptThrows(NullPointerException.class, () -> {
exec("String x = null;\n" +
"boolean y = false;\n" +
"if (!y) {\n" +
@ -62,7 +66,8 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
"}\n" +
"return y;");
});
assertEquals(4, exception.getStackTrace()[0].getLineNumber());
// null deref at x.isEmpty(), the '.' is offset 53 (+1)
assertEquals(53 + 1, exception.getStackTrace()[0].getLineNumber());
}
public void testInvalidShift() {
@ -83,46 +88,46 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
}
public void testInfiniteLoops() {
PainlessError expected = expectThrows(PainlessError.class, () -> {
PainlessError expected = expectScriptThrows(PainlessError.class, () -> {
exec("boolean x = true; while (x) {}");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("while (true) {int y = 5;}");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("while (true) { boolean x = true; while (x) {} }");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("while (true) { boolean x = false; while (x) {} }");
fail("should have hit PainlessError");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("boolean x = true; for (;x;) {}");
fail("should have hit PainlessError");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("for (;;) {int x = 5;}");
fail("should have hit PainlessError");
});
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> {
expected = expectScriptThrows(PainlessError.class, () -> {
exec("def x = true; do {int y = 5;} while (x)");
fail("should have hit PainlessError");
});
@ -140,7 +145,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
// right below limit: ok
exec("for (int x = 0; x < 9999; ++x) {}");
PainlessError expected = expectThrows(PainlessError.class, () -> {
PainlessError expected = expectScriptThrows(PainlessError.class, () -> {
exec("for (int x = 0; x < 10000; ++x) {}");
});
assertTrue(expected.getMessage().contains(
@ -163,32 +168,32 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
}
public void testIllegalDynamicMethod() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def x = 'test'; return x.getClass().toString()");
});
assertTrue(expected.getMessage().contains("Unable to find dynamic method"));
}
public void testDynamicNPE() {
expectThrows(NullPointerException.class, () -> {
expectScriptThrows(NullPointerException.class, () -> {
exec("def x = null; return x.toString()");
});
}
public void testDynamicWrongArgs() {
expectThrows(WrongMethodTypeException.class, () -> {
expectScriptThrows(WrongMethodTypeException.class, () -> {
exec("def x = new ArrayList(); return x.get('bogus');");
});
}
public void testDynamicArrayWrongIndex() {
expectThrows(WrongMethodTypeException.class, () -> {
expectScriptThrows(WrongMethodTypeException.class, () -> {
exec("def x = new long[1]; x[0]=1; return x['bogus'];");
});
}
public void testDynamicListWrongIndex() {
expectThrows(WrongMethodTypeException.class, () -> {
expectScriptThrows(WrongMethodTypeException.class, () -> {
exec("def x = new ArrayList(); x.add('foo'); return x['bogus'];");
});
}

View File

@ -27,7 +27,7 @@
- is_true: failures.0.shard
- match: {failures.0.index: source}
- is_true: failures.0.node
- match: {failures.0.reason.type: script_exception}
- match: {failures.0.reason.type: general_script_exception}
- match: {failures.0.reason.reason: "failed to run inline script [1/0] using lang [groovy]"}
- match: {failures.0.reason.caused_by.type: arithmetic_exception}
- match: {failures.0.reason.caused_by.reason: Division by zero}

View File

@ -23,7 +23,7 @@
- is_true: failures.0.shard
- match: {failures.0.index: source}
- is_true: failures.0.node
- match: {failures.0.reason.type: script_exception}
- match: {failures.0.reason.type: general_script_exception}
- match: {failures.0.reason.reason: "failed to run inline script [1/0] using lang [groovy]"}
- match: {failures.0.reason.caused_by.type: arithmetic_exception}
- match: {failures.0.reason.caused_by.reason: Division by zero}