Plan A Update...

New Features:

Exceptions (throw, try/catch)
Infinite Loop Checking
Iterators added to the API

Fixes:

Improved loop path analysis
Fixed set/add issue with shortcuts in lists
Fixed minor promotion issue with incorrect type
Fixed score issue related to score extending Number

Documentation:

Added JavaDocs for some files
This commit is contained in:
Jack Conradson 2016-01-19 08:37:22 -08:00
parent da699ceae2
commit 2249a640bb
25 changed files with 3317 additions and 1985 deletions

View File

@ -34,18 +34,23 @@ statement
| CONTINUE SEMICOLON? # continue | CONTINUE SEMICOLON? # continue
| BREAK SEMICOLON? # break | BREAK SEMICOLON? # break
| RETURN expression SEMICOLON? # return | RETURN expression SEMICOLON? # return
| TRY block ( CATCH LP ( TYPE ID ) RP block )+ # try | TRY block trap+ # try
| THROW expression SEMICOLON? # throw | THROW expression SEMICOLON? # throw
| expression SEMICOLON? # expr | expression SEMICOLON? # expr
; ;
block block
: LBRACK statement* RBRACK # multiple : LBRACK statement+ RBRACK # multiple
| statement # single | statement # single
; ;
empty empty
: SEMICOLON : emptyscope
| SEMICOLON
;
emptyscope
: LBRACK RBRACK
; ;
initializer initializer
@ -69,6 +74,10 @@ declvar
: ID ( ASSIGN expression )? : ID ( ASSIGN expression )?
; ;
trap
: CATCH LP ( TYPE ID ) RP ( block | emptyscope )
;
expression expression
: LP expression RP # precedence : LP expression RP # precedence
| ( OCTAL | HEX | INTEGER | DECIMAL ) # numeric | ( OCTAL | HEX | INTEGER | DECIMAL ) # numeric

View File

@ -1,278 +0,0 @@
/*
* 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.plan.a;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.plan.a.Definition.Cast;
import static org.elasticsearch.plan.a.Definition.Type;
import static org.elasticsearch.plan.a.PlanAParser.ExpressionContext;
import static org.elasticsearch.plan.a.PlanAParser.PrecedenceContext;
class Adapter {
static class StatementMetadata {
final ParserRuleContext source;
boolean last;
boolean allExit;
boolean allReturn;
boolean anyReturn;
boolean allBreak;
boolean anyBreak;
boolean allContinue;
boolean anyContinue;
private StatementMetadata(final ParserRuleContext source) {
this.source = source;
last = false;
allExit = false;
allReturn = false;
anyReturn = false;
allBreak = false;
anyBreak = false;
allContinue = false;
anyContinue = false;
}
}
static class ExpressionMetadata {
final ParserRuleContext source;
boolean read;
boolean statement;
Object preConst;
Object postConst;
boolean isNull;
Type to;
Type from;
boolean explicit;
boolean typesafe;
Cast cast;
private ExpressionMetadata(final ParserRuleContext source) {
this.source = source;
read = true;
statement = false;
preConst = null;
postConst = null;
isNull = false;
to = null;
from = null;
explicit = false;
typesafe = true;
cast = null;
}
}
static class ExternalMetadata {
final ParserRuleContext source;
boolean read;
ParserRuleContext storeExpr;
int token;
boolean pre;
boolean post;
int scope;
Type current;
boolean statik;
boolean statement;
Object constant;
private ExternalMetadata(final ParserRuleContext source) {
this.source = source;
read = false;
storeExpr = null;
token = 0;
pre = false;
post = false;
scope = 0;
current = null;
statik = false;
statement = false;
constant = null;
}
}
static class ExtNodeMetadata {
final ParserRuleContext parent;
final ParserRuleContext source;
Object target;
boolean last;
Type type;
Type promote;
Cast castFrom;
Cast castTo;
private ExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
this.parent = parent;
this.source = source;
target = null;
last = false;
type = null;
promote = null;
castFrom = null;
castTo = null;
}
}
static String error(final ParserRuleContext ctx) {
return "Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: ";
}
final Definition definition;
final String source;
final ParserRuleContext root;
final CompilerSettings settings;
private final Map<ParserRuleContext, StatementMetadata> statementMetadata;
private final Map<ParserRuleContext, ExpressionMetadata> expressionMetadata;
private final Map<ParserRuleContext, ExternalMetadata> externalMetadata;
private final Map<ParserRuleContext, ExtNodeMetadata> extNodeMetadata;
Adapter(final Definition definition, final String source, final ParserRuleContext root, final CompilerSettings settings) {
this.definition = definition;
this.source = source;
this.root = root;
this.settings = settings;
statementMetadata = new HashMap<>();
expressionMetadata = new HashMap<>();
externalMetadata = new HashMap<>();
extNodeMetadata = new HashMap<>();
}
StatementMetadata createStatementMetadata(final ParserRuleContext source) {
final StatementMetadata sourcesmd = new StatementMetadata(source);
statementMetadata.put(source, sourcesmd);
return sourcesmd;
}
StatementMetadata getStatementMetadata(final ParserRuleContext source) {
final StatementMetadata sourcesmd = statementMetadata.get(source);
if (sourcesmd == null) {
throw new IllegalStateException(error(source) + "Statement metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourcesmd;
}
ExpressionContext updateExpressionTree(ExpressionContext source) {
if (source instanceof PrecedenceContext) {
final ParserRuleContext parent = source.getParent();
int index = 0;
for (final ParseTree child : parent.children) {
if (child == source) {
break;
}
++index;
}
while (source instanceof PrecedenceContext) {
source = ((PrecedenceContext)source).expression();
}
parent.children.set(index, source);
}
return source;
}
ExpressionMetadata createExpressionMetadata(ParserRuleContext source) {
final ExpressionMetadata sourceemd = new ExpressionMetadata(source);
expressionMetadata.put(source, sourceemd);
return sourceemd;
}
ExpressionMetadata getExpressionMetadata(final ParserRuleContext source) {
final ExpressionMetadata sourceemd = expressionMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "Expression metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
ExternalMetadata createExternalMetadata(final ParserRuleContext source) {
final ExternalMetadata sourceemd = new ExternalMetadata(source);
externalMetadata.put(source, sourceemd);
return sourceemd;
}
ExternalMetadata getExternalMetadata(final ParserRuleContext source) {
final ExternalMetadata sourceemd = externalMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "External metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
ExtNodeMetadata createExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
final ExtNodeMetadata sourceemd = new ExtNodeMetadata(parent, source);
extNodeMetadata.put(source, sourceemd);
return sourceemd;
}
ExtNodeMetadata getExtNodeMetadata(final ParserRuleContext source) {
final ExtNodeMetadata sourceemd = extNodeMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "External metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
}

View File

@ -30,88 +30,87 @@ import java.security.CodeSource;
import java.security.SecureClassLoader; import java.security.SecureClassLoader;
import java.security.cert.Certificate; import java.security.cert.Certificate;
/**
* The Compiler is the entry point for generating a Plan A script. The compiler will generate an ANTLR
* parse tree based on the source code that is passed in. Two passes will then be run over the parse tree,
* one for analysis using the {@link Analyzer} and another to generate the actual byte code using ASM in
* the {@link Writer}.
*/
final class Compiler { final class Compiler {
/**
* The default language API to be used with Plan A. The second construction is used
* to finalize all the variables, so there is no mistake of modification afterwards.
*/
private static Definition DEFAULT_DEFINITION = new Definition(new Definition()); private static Definition DEFAULT_DEFINITION = new Definition(new Definition());
/** we define the class with lowest privileges */ /**
* Define the class with lowest privileges.
*/
private static final CodeSource CODESOURCE; private static final CodeSource CODESOURCE;
/**
* Setup the code privileges.
*/
static { static {
try { try {
// Setup the code privileges.
CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[]) null); CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[]) null);
} catch (MalformedURLException impossible) { } catch (MalformedURLException impossible) {
throw new RuntimeException(impossible); throw new RuntimeException(impossible);
} }
} }
/**
* A secure class loader used to define Plan A scripts.
*/
static class Loader extends SecureClassLoader { static class Loader extends SecureClassLoader {
Loader(ClassLoader parent) { /**
* @param parent The parent ClassLoader.
*/
Loader(final ClassLoader parent) {
super(parent); super(parent);
} }
Class<? extends Executable> define(String name, byte[] bytes) { /**
* Generates a Class object from the generated byte code.
* @param name The name of the class.
* @param bytes The generated byte code.
* @return A Class object extending {@link Executable}.
*/
Class<? extends Executable> define(final String name, final byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length, CODESOURCE).asSubclass(Executable.class); return defineClass(name, bytes, 0, bytes.length, CODESOURCE).asSubclass(Executable.class);
} }
} }
static Executable compile(Loader loader, final String name, final String source, final Definition custom, CompilerSettings settings) { /**
long start = System.currentTimeMillis(); * Runs the two-pass compiler to generate a Plan A script.
* @param loader The ClassLoader used to define the script.
final Definition definition = custom == null ? DEFAULT_DEFINITION : new Definition(custom); * @param name The name of the script.
* @param source The source code for the script.
//long end = System.currentTimeMillis() - start; * @param settings The CompilerSettings to be used during the compilation.
//System.out.println("types: " + end); * @return An {@link Executable} Plan A script.
//start = System.currentTimeMillis(); */
static Executable compile(final Loader loader, final String name, final String source,
//final ParserRuleContext root = createParseTree(source, types); final Definition custom, final CompilerSettings settings) {
final ANTLRInputStream stream = new ANTLRInputStream(source); final Definition definition = custom != null ? new Definition(custom) : DEFAULT_DEFINITION;
final ErrorHandlingLexer lexer = new ErrorHandlingLexer(stream); final ParserRuleContext root = createParseTree(source, definition);
final PlanAParser parser = new PlanAParser(new CommonTokenStream(lexer)); final Metadata metadata = new Metadata(definition, source, root, settings);
final ParserErrorStrategy strategy = new ParserErrorStrategy(); Analyzer.analyze(metadata);
final byte[] bytes = Writer.write(metadata);
lexer.removeErrorListeners();
lexer.setTypes(definition.structs.keySet());
//List<? extends Token> tokens = lexer.getAllTokens();
//for (final Token token : tokens) {
// System.out.println(token.getType() + " " + token.getText());
//}
parser.removeErrorListeners();
parser.setErrorHandler(strategy);
ParserRuleContext root = parser.source();
//end = System.currentTimeMillis() - start;
//System.out.println("tree: " + end);
final Adapter adapter = new Adapter(definition, source, root, settings);
start = System.currentTimeMillis();
Analyzer.analyze(adapter);
//System.out.println(root.toStringTree(parser));
//end = System.currentTimeMillis() - start;
//System.out.println("analyze: " + end);
//start = System.currentTimeMillis();
final byte[] bytes = Writer.write(adapter);
//end = System.currentTimeMillis() - start;
//System.out.println("write: " + end);
//start = System.currentTimeMillis();
final Executable executable = createExecutable(loader, definition, name, source, bytes); final Executable executable = createExecutable(loader, definition, name, source, bytes);
//end = System.currentTimeMillis() - start;
//System.out.println("create: " + end);
return executable; return executable;
} }
private static ParserRuleContext createParseTree(String source, Definition definition) { /**
* Generates the ANTLR tree from the given source code. Several methods below, are used
* to ensure that the first error generated by ANTLR will cause the compilation to fail rather than
* use ANTLR's recovery strategies that may be potentially dangerous.
* @param source The source code for the script.
* @param definition The Plan A API.
* @return The root node for the ANTLR parse tree.
*/
private static ParserRuleContext createParseTree(final String source, final Definition definition) {
final ANTLRInputStream stream = new ANTLRInputStream(source); final ANTLRInputStream stream = new ANTLRInputStream(source);
final ErrorHandlingLexer lexer = new ErrorHandlingLexer(stream); final ErrorHandlingLexer lexer = new ErrorHandlingLexer(stream);
final PlanAParser parser = new PlanAParser(new CommonTokenStream(lexer)); final PlanAParser parser = new PlanAParser(new CommonTokenStream(lexer));
@ -119,36 +118,50 @@ final class Compiler {
lexer.removeErrorListeners(); lexer.removeErrorListeners();
lexer.setTypes(definition.structs.keySet()); lexer.setTypes(definition.structs.keySet());
parser.removeErrorListeners(); parser.removeErrorListeners();
parser.setErrorHandler(strategy); parser.setErrorHandler(strategy);
ParserRuleContext root = parser.source(); ParserRuleContext root = parser.source();
// System.out.println(root.toStringTree(parser));
return root; return root;
} }
private static Executable createExecutable(Loader loader, Definition definition, String name, String source, byte[] bytes) { /**
* Generates an {@link Executable} that can run a Plan A script.
* @param loader The {@link Loader} to define the script's class file.
* @param definition The Plan A API.
* @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 Plan A {@link Executable} script.
*/
private static Executable createExecutable(final Loader loader, final Definition definition,
final String name, final String source, final byte[] bytes) {
try { try {
// for debugging: // Used for debugging. Uncomment this code and add -Dtests.security.manager=false when running to save
//try { // the generated Java class files. The javap tool can then be used to inspect the generated byte code.
// FileOutputStream f = new FileOutputStream(new File("/Users/jdconrad/lang/generated/out.class"), false);
// f.write(bytes); // try {
// f.close(); // FileOutputStream f = new FileOutputStream(new File("<path>"), false);
//} catch (Exception e) { // f.write(bytes);
// throw new RuntimeException(e); // f.close();
//} // } catch (Exception e) {
// throw new RuntimeException(e);
// }
final Class<? extends Executable> clazz = loader.define(Writer.CLASS_NAME, bytes); final Class<? extends Executable> clazz = loader.define(Writer.CLASS_NAME, bytes);
final java.lang.reflect.Constructor<? extends Executable> constructor = final java.lang.reflect.Constructor<? extends Executable> constructor =
clazz.getConstructor(Definition.class, String.class, String.class); clazz.getConstructor(Definition.class, String.class, String.class);
return constructor.newInstance(definition, name, source); return constructor.newInstance(definition, name, source);
} catch (Exception exception) { } catch (final Exception exception) { // Catch everything to let the user know this is something caused internally.
throw new IllegalStateException( throw new IllegalStateException(
"An internal error occurred attempting to define the script [" + name + "].", exception); "An internal error occurred attempting to define the script [" + name + "].", exception);
} }
} }
/**
* All methods in the compiler should be static.
*/
private Compiler() {} private Compiler() {}
} }

View File

@ -19,13 +19,30 @@
package org.elasticsearch.plan.a; package org.elasticsearch.plan.a;
/** /**
* Settings to use when compiling a script * Settings to use when compiling a script.
*/ */
final class CompilerSettings { final class CompilerSettings {
/**
* Constant to be used when specifying numeric overflow when compiling a script.
*/
public static final String NUMERIC_OVERFLOW = "numeric_overflow";
/**
* Constant to be used when specifying the maximum loop counter when compiling a script.
*/
public static final String MAX_LOOP_COUNTER = "max_loop_counter";
/**
* Whether or not to allow numeric values to overflow without exception.
*/
private boolean numericOverflow = true; private boolean numericOverflow = true;
/**
* The maximum number of statements allowed to be run in a loop.
*/
private int maxLoopCounter = 10000;
/** /**
* Returns {@code true} if numeric operations should overflow, {@code false} * Returns {@code true} if numeric operations should overflow, {@code false}
* if they should signal an exception. * if they should signal an exception.
@ -46,4 +63,20 @@ final class CompilerSettings {
public void setNumericOverflow(boolean allow) { public void setNumericOverflow(boolean allow) {
this.numericOverflow = allow; this.numericOverflow = allow;
} }
/**
* Returns the value for the cumulative total number of statements that can be made in all loops
* in a script before an exception is thrown. This attempts to prevent infinite loops.
*/
public int getMaxLoopCounter() {
return maxLoopCounter;
}
/**
* Set the cumulative total number of statements that can be made in all loops.
* @see #getMaxLoopCounter
*/
public void setMaxLoopCounter(int max) {
this.maxLoopCounter = max;
}
} }

View File

@ -107,7 +107,7 @@ public class Def {
} else if (owner instanceof List) { } else if (owner instanceof List) {
try { try {
final int index = Integer.parseInt(name); final int index = Integer.parseInt(name);
((List)owner).add(index, value); ((List)owner).set(index, value);
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "]."); throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
} }
@ -198,7 +198,7 @@ public class Def {
"in array class [" + array.getClass().getCanonicalName() + "].", throwable); "in array class [" + array.getClass().getCanonicalName() + "].", throwable);
} }
} else if (array instanceof List) { } else if (array instanceof List) {
((List)array).add((int)index, value); ((List)array).set((int)index, value);
} else { } else {
throw new IllegalArgumentException("Attempting to address a non-array type " + throw new IllegalArgumentException("Attempting to address a non-array type " +
"[" + array.getClass().getCanonicalName() + "] as an array."); "[" + array.getClass().getCanonicalName() + "] as an array.");

View File

@ -1,5 +1,3 @@
package org.elasticsearch.plan.a;
/* /*
* Licensed to Elasticsearch under one or more contributor * Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with * license agreements. See the NOTICE file distributed with
@ -19,6 +17,8 @@ package org.elasticsearch.plan.a;
* under the License. * under the License.
*/ */
package org.elasticsearch.plan.a;
import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.LexerNoViableAltException; import org.antlr.v4.runtime.LexerNoViableAltException;
import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.misc.Interval;

View File

@ -0,0 +1,619 @@
/*
* 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.plan.a;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.elasticsearch.plan.a.Definition.Cast;
import org.elasticsearch.plan.a.Definition.Type;
import org.elasticsearch.plan.a.PlanAParser.ExpressionContext;
import org.elasticsearch.plan.a.PlanAParser.PrecedenceContext;
import java.util.HashMap;
import java.util.Map;
/**
* Metadata is a wrapper for all the data that is collected by the {@link Analyzer}. Each node in the ANTLR parse tree
* will have one of the types of metadata to store information used either in a different node by the analyzer
* or by the {@link Writer} during byte code generation. Metadata also contains several objects passed into the
* {@link Analyzer} and {@link Writer} used during compilation including the {@link Definition}, the source code,
* the root of the ANTLR parse tree, and the {@link CompilerSettings}.
*/
class Metadata {
/**
* StatementMetadata is used to store metadata mostly about
* control flow for ANTLR nodes related to if/else, do, while, for, etc.
*/
static class StatementMetadata {
/**
* The source variable is the ANTLR node used to generate this metadata.
*/
final ParserRuleContext source;
/**
* The lastSource variable will be set to true when the final statement from the root ANTLR node is about
* to be visited. This is used to determine whether or not the auto-return feature is allowed to be used,
* and if a null return value needs to be generated automatically since a return value is always required.
*/
boolean lastSource = false;
/**
* The beginLoop variable will be set to true whenever a loop node is initially visited including inner
* loops. This will not be propagated down the parse tree afterwards, though. This is used to determine
* whether or not inLoop should be set further down the tree. Note that inLoop alone is not enough
* information to determine whether we are in the last statement of a loop because we may inside of
* multiple loops, so this variable is necessary.
*/
boolean beginLoop = false;
/**
* The inLoop variable is set to true when inside a loop. This will be propagated down the parse tree. This
* is used to determine whether or not continue and break statements are legal.
*/
boolean inLoop = false;
/**
* The lastLoop variable is set to true when the final statement of a loop is reached. This will be
* propagated down the parse tree until another loop is reached and then will not be propagated further for
* the current loop. This is used to determine whether or not a continue statement is superfluous.
*/
boolean lastLoop = false;
/**
* The methodEscape variable is set to true when a statement would cause the method to potentially exit. This
* includes return, throw, and continuous loop statements. Note that a catch statement may possibly
* reset this to false after a throw statement. This will be propagated up the tree as far as necessary.
* This is used by the {@link Writer} to ensure that superfluous statements aren't unnecessarily written
* into generated bytecode.
*/
boolean methodEscape = false;
/**
* The loopEscape variable is set to true when a loop is going to be exited. This may be caused by a number of
* different statements including continue, break, return, etc. This will only be propagated as far as the
* loop node. This is used to ensure that in certain case an infinite loop will be caught at
* compile-time rather than run-time.
*/
boolean loopEscape = false;
/**
* The allLast variable is set whenever a final statement in a block is reached. This includes the end of loop,
* if, else, etc. This will be only propagated to the top of the block statement ANTLR node.
* This is used to ensure that there are no unreachable statements within the script.
*/
boolean allLast = false;
/**
* The anyContinue will be set to true when a continue statement is visited. This will be propagated to the
* loop node it's within. This is used to ensure that in certain case an infinite loop will be caught at
* compile-time rather than run-time.
*/
boolean anyContinue = false;
/**
* The anyBreak will be set to true when a break statement is visited. This will be propagated to the
* loop node it's within. This is used to in conjunction with methodEscape to ensure there are no unreachable
* statements within the script.
*/
boolean anyBreak = false;
/**
* The count variable is used as a rudimentary count of statements within a loop. This will be used in
* the {@link Writer} to keep a count of statements that have been executed at run-time to ensure that a loop
* will exit if it runs too long.
*/
int count = 0;
/**
* The exception variable is used to store the exception type when a throw node is visited. This is used by
* the {@link Writer} to write the correct type of exception in the generated byte code.
*/
Type exception = null;
/**
* The slot variable is used to store the place on the stack of where a thrown exception will be stored to.
* This is used by the {@link Writer}.
*/
int slot = -1;
/**
* Constructor.
* @param source The associated ANTLR node.
*/
private StatementMetadata(final ParserRuleContext source) {
this.source = source;
}
}
/**
* ExpressionMetadata is used to store metadata mostly about constants and casting
* for ANTLR nodes related to mathematical operations.
*/
static class ExpressionMetadata {
/**
* The source variable is the ANTLR node used to generate this metadata.
*/
final ParserRuleContext source;
/**
* The read variable is used to determine whether or not the value of an expression will be read from.
* This is set to false when the expression is the left-hand side of an assignment that is not chained or
* when a method call is made alone. This will propagate down the tree as far as necessary.
* The {@link Writer} uses this to determine when a value may need to be popped from the stack
* such as when a method call returns a value that is never read.
*/
boolean read = true;
/**
* The statement variable is set true when an expression is a complete meaning that there is some sort
* of effect on a variable or a method call is made. This will propagate up the tree as far as necessary.
* This prevents statements that have no effect on the output of a script from being executed.
*/
boolean statement = false;
/**
* The preConst variable is set to a non-null value when a constant statement is made in a script. This is
* used to track the constant value prior to any casts being made on an ANTLR node.
*/
Object preConst = null;
/**
* The postConst variable is set to a non-null value when a cast is made on a node where a preConst variable
* has already been set when the cast would leave the constant as a non-object value except in the case of a
* String. This will be propagated up the tree and used to simplify constants when possible such as making
* the value of 2*2 be 4 in the * node, so that the {@link Writer} only has to push a 4 onto the stack.
*/
Object postConst = null;
/**
* The isNull variable is set to true when a null constant statement is made in the script. This allows the
* {@link Writer} to potentially shortcut certain comparison operations.
*/
boolean isNull = false;
/**
* The to variable is used to track what an ANTLR node's value should be cast to. This is set on every ANTLR
* node in the tree, and used by the {@link Writer} to make a run-time cast if necessary in the byte code.
* This is also used by the {@link Analyzer} to determine if a cast is legal.
*/
Type to = null;
/**
* The from variable is used to track what an ANTLR node's value should be cast from. This is set on every
* ANTLR node in the tree independent of other nodes. This is used by the {@link Analyzer} to determine if a
* cast is legal.
*/
Type from = null;
/**
* The explicit variable is set to true when a cast is explicitly made in the script. This tracks whether
* or not a cast is a legal up cast.
*/
boolean explicit = false;
/**
* The typesafe variable is set to true when a dynamic type is used as part of an expression. This propagates
* up the tree to the top of the expression. This allows for implicit up casts throughout the expression and
* is used by the {@link Analyzer}.
*/
boolean typesafe = true;
/**
* This is set to the combination of the to and from variables at the end of each node visit in the
* {@link Analyzer}. This is set on every ANTLR node in the tree independent of other nodes, and is
* used by {@link Writer} to make a run-time cast if necessary in the byte code.
*/
Cast cast = null;
/**
* Constructor.
* @param source The associated ANTLR node.
*/
private ExpressionMetadata(final ParserRuleContext source) {
this.source = source;
}
}
/**
* ExternalMetadata is used to store metadata about the overall state of a variable/method chain such as
* '(int)x.get(3)' where each piece of that chain is broken into it's indiviual pieces and stored in
* {@link ExtNodeMetadata}.
*/
static class ExternalMetadata {
/**
* The source variable is the ANTLR node used to generate this metadata.
*/
final ParserRuleContext source;
/**
* The read variable is set to true when the value of a variable/method chain is going to be read from.
* This is used by the {@link Analyzer} to determine if this variable/method chain will be in a standalone
* statement.
*/
boolean read = false;
/**
* The storeExpr variable is set to the right-hand side of an assignment in the variable/method chain if
* necessary. This is used by the {@link Analyzer} to set the proper metadata for a read versus a write,
* and is used by the {@link Writer} to determine if a bytecode operation should be a load or a store.
*/
ParserRuleContext storeExpr = null;
/**
* The token variable is set to a constant value of the operator type (+, -, etc.) when a compound assignment
* is being visited. This is also used by the increment and decrement operators. This is used by both the
* {@link Analyzer} and {@link Writer} to correctly handle the compound assignment.
*/
int token = 0;
/**
* The pre variable is set to true when pre-increment or pre-decrement is visited. This is used by both the
* {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are
* necessary.
*/
boolean pre = false;
/**
* The post variable is set to true when post-increment or post-decrement is visited. This is used by both the
* {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are
* necessary.
*/
boolean post = false;
/**
* The scope variable is incremented and decremented when a precedence node is visited as part of a
* variable/method chain. This is used by the {@link Analyzer} to determine when the final piece of the
* variable/method chain has been reached.
*/
int scope = 0;
/**
* The current variable is set to whatever the current type is within the visited node of the variable/method
* chain. This changes as the nodes for the variable/method are walked through. This is used by the
* {@link Analyzer} to make decisions about whether or not a cast is legal, and what methods are available
* for that specific type.
*/
Type current = null;
/**
* The statik variable is set to true when a variable/method chain begins with static type. This is used by
* the {@link Analyzer} to determine what methods/members are available for that specific type.
*/
boolean statik = false;
/**
* The statement variable is set to true when a variable/method chain can be standalone statement. This is
* used by the {@link Analyzer} to error out if there a variable/method chain that is not a statement.
*/
boolean statement = false;
/**
* The constant variable is set when a String constant is part of the variable/method chain. String is a
* special case because methods/members need to be able to be called on a String constant, so this can't be
* only as part of {@link ExpressionMetadata}. This is used by the {@link Writer} to write out the String
* constant in the byte code.
*/
Object constant = null;
/**
* Constructor.
* @param source The associated ANTLR node.
*/
private ExternalMetadata(final ParserRuleContext source) {
this.source = source;
}
}
static class ExtNodeMetadata {
/**
* The parent variable is top-level ANTLR node of the variable/method chain. This is used to retrieve the
* ExternalMetadata for the variable/method chain this ExtNodeMetadata is a piece of.
*/
final ParserRuleContext parent;
/**
* The source variable is the ANTLR node used to generate this metadata.
*/
final ParserRuleContext source;
/**
* The target variable is set to a value based on the type of ANTLR node that is visited. This is used by
* {@link Writer} to determine whether a cast, store, load, or method call should be written in byte code
* depending on what the target variable is.
*/
Object target = null;
/**
* The last variable is set to true when the last ANTLR node of the variable/method chain is visted. This is
* used by the {@link Writer} in conjuction with the storeExpr variable to determine whether or not a store
* needs to be written as opposed to a load.
*/
boolean last = false;
/**
* The type variable is set to the type that a visited node ends with. This is used by both the
* {@link Analyzer} and {@link Writer} to make decisions about compound assignments, String constants, and
* shortcuts.
*/
Type type = null;
/**
* The promote variable is set to the type of a promotion within a compound assignment. Compound assignments
* may require promotion between the left-hand side variable and right-hand side value. This is used by the
* {@link Writer} to make the correct decision about the byte code operation.
*/
Type promote = null;
/**
* The castFrom variable is set during a compound assignment. This is used by the {@link Writer} to
* cast the values to the promoted type during a compound assignment.
*/
Cast castFrom = null;
/**
* The castTo variable is set during an explicit cast in a variable/method chain or during a compound
* assignment. This is used by the {@link Writer} to either do an explicit cast, or cast the values
* from the promoted type back to the original type during a compound assignment.
*/
Cast castTo = null;
/**
* Constructor.
* @param parent The top-level ANTLR node for the variable/method chain.
* @param source The associated ANTLR node.
*/
private ExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
this.parent = parent;
this.source = source;
}
}
/**
* A utility method to output consistent error messages.
* @param ctx The ANTLR node the error occurred in.
* @return The error message with tacked on line number and character position.
*/
static String error(final ParserRuleContext ctx) {
return "Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: ";
}
/**
* Acts as both the Plan A API and white-list for what types and methods are allowed.
*/
final Definition definition;
/**
* The original text of the input script. This is used to write out the source code into
* the byte code file for debugging purposes.
*/
final String source;
/**
* Toot node of the ANTLR tree for the Plan A script.
*/
final ParserRuleContext root;
/**
* Used to determine certain compile-time constraints such as whether or not numeric overflow is allowed
* and how many statements are allowed before a loop will throw an exception.
*/
final CompilerSettings settings;
/**
* Used to determine what slot the input variable is stored in. This is used in the {@link Writer} whenever
* the input variable is accessed.
*/
int inputValueSlot = -1;
/**
* Used to determine what slot the score variable is stored in. This is used in the {@link Writer} whenever
* the score variable is accessed.
*/
int scoreValueSlot = -1;
/**
* Used to determine what slot the loopCounter variable is stored in. This is used n the {@link Writer} whenever
* the loop variable is accessed.
*/
int loopCounterSlot = -1;
/**
* Maps the relevant ANTLR node to its metadata.
*/
private final Map<ParserRuleContext, StatementMetadata> statementMetadata = new HashMap<>();
/**
* Maps the relevant ANTLR node to its metadata.
*/
private final Map<ParserRuleContext, ExpressionMetadata> expressionMetadata = new HashMap<>();
/**
* Maps the relevant ANTLR node to its metadata.
*/
private final Map<ParserRuleContext, ExternalMetadata> externalMetadata = new HashMap<>();
/**
* Maps the relevant ANTLR node to its metadata.
*/
private final Map<ParserRuleContext, ExtNodeMetadata> extNodeMetadata = new HashMap<>();
/**
* Constructor.
* @param definition The Plan A definition.
* @param source The source text for the script.
* @param root The root ANTLR node.
* @param settings The compile-time settings.
*/
Metadata(final Definition definition, final String source, final ParserRuleContext root, final CompilerSettings settings) {
this.definition = definition;
this.source = source;
this.root = root;
this.settings = settings;
}
/**
* Creates a new StatementMetadata and stores it in the statementMetadata map.
* @param source The ANTLR node for this metadata.
* @return The new StatementMetadata.
*/
StatementMetadata createStatementMetadata(final ParserRuleContext source) {
final StatementMetadata sourcesmd = new StatementMetadata(source);
statementMetadata.put(source, sourcesmd);
return sourcesmd;
}
/**
* Retrieves StatementMetadata from the statementMetadata map.
* @param source The ANTLR node for this metadata.
* @return The retrieved StatementMetadata.
*/
StatementMetadata getStatementMetadata(final ParserRuleContext source) {
final StatementMetadata sourcesmd = statementMetadata.get(source);
if (sourcesmd == null) {
throw new IllegalStateException(error(source) + "Statement metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourcesmd;
}
/**
* The ANTLR parse tree is modified in one single case; a parent node needs to check a child node to see if it's
* a precedence node, and if so, it must be removed from the tree permanently. Once the ANTLR tree is built,
* precedence nodes are no longer necessary to maintain the correct ordering of the tree, so they only
* add a level of indirection where complicated decisions about metadata passing would have to be made. This
* method removes the need for those decisions.
* @param source The child ANTLR node to check for precedence.
* @return The updated child ANTLR node.
*/
ExpressionContext updateExpressionTree(ExpressionContext source) {
// Check to see if the ANTLR node is a precedence node.
if (source instanceof PrecedenceContext) {
final ParserRuleContext parent = source.getParent();
int index = 0;
// Mark the index of the source node within the list of child nodes from the parent.
for (final ParseTree child : parent.children) {
if (child == source) {
break;
}
++index;
}
// If there are multiple precedence nodes in a row, remove them all.
while (source instanceof PrecedenceContext) {
source = ((PrecedenceContext)source).expression();
}
// Update the parent node with the child of the precedence node.
parent.children.set(index, source);
}
return source;
}
/**
* Creates a new ExpressionMetadata and stores it in the expressionMetadata map.
* @param source The ANTLR node for this metadata.
* @return The new ExpressionMetadata.
*/
ExpressionMetadata createExpressionMetadata(ParserRuleContext source) {
final ExpressionMetadata sourceemd = new ExpressionMetadata(source);
expressionMetadata.put(source, sourceemd);
return sourceemd;
}
/**
* Retrieves ExpressionMetadata from the expressionMetadata map.
* @param source The ANTLR node for this metadata.
* @return The retrieved ExpressionMetadata.
*/
ExpressionMetadata getExpressionMetadata(final ParserRuleContext source) {
final ExpressionMetadata sourceemd = expressionMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "Expression metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
/**
* Creates a new ExternalMetadata and stores it in the externalMetadata map.
* @param source The ANTLR node for this metadata.
* @return The new ExternalMetadata.
*/
ExternalMetadata createExternalMetadata(final ParserRuleContext source) {
final ExternalMetadata sourceemd = new ExternalMetadata(source);
externalMetadata.put(source, sourceemd);
return sourceemd;
}
/**
* Retrieves ExternalMetadata from the externalMetadata map.
* @param source The ANTLR node for this metadata.
* @return The retrieved ExternalMetadata.
*/
ExternalMetadata getExternalMetadata(final ParserRuleContext source) {
final ExternalMetadata sourceemd = externalMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "External metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
/**
* Creates a new ExtNodeMetadata and stores it in the extNodeMetadata map.
* @param source The ANTLR node for this metadata.
* @return The new ExtNodeMetadata.
*/
ExtNodeMetadata createExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
final ExtNodeMetadata sourceemd = new ExtNodeMetadata(parent, source);
extNodeMetadata.put(source, sourceemd);
return sourceemd;
}
/**
* Retrieves ExtNodeMetadata from the extNodeMetadata map.
* @param source The ANTLR node for this metadata.
* @return The retrieved ExtNodeMetadata.
*/
ExtNodeMetadata getExtNodeMetadata(final ParserRuleContext source) {
final ExtNodeMetadata sourceemd = extNodeMetadata.get(source);
if (sourceemd == null) {
throw new IllegalStateException(error(source) + "External metadata does not exist at" +
" the parse node with text [" + source.getText() + "].");
}
return sourceemd;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.plan.a;
/**
* The PlanAError class is used to throw internal errors caused by Plan A scripts that cannot be
* caught using a standard {@link Exception}. This prevents the user from catching this specific error
* (as Exceptions are available in the Plan A API, but Errors are not,) and possibly continuing to do
* something hazardous. The alternative was extending {@link Throwable}, but that seemed worse than using
* an {@link Error} in this case.
*/
public class PlanAError extends Error {
/**
* Constructor.
* @param message The error message.
*/
public PlanAError(final String message) {
super(message);
}
}

View File

@ -116,6 +116,13 @@ class PlanAParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements P
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitEmpty(PlanAParser.EmptyContext ctx) { return visitChildren(ctx); } @Override public T visitEmpty(PlanAParser.EmptyContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitEmptyscope(PlanAParser.EmptyscopeContext ctx) { return visitChildren(ctx); }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -151,6 +158,13 @@ class PlanAParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements P
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitDeclvar(PlanAParser.DeclvarContext ctx) { return visitChildren(ctx); } @Override public T visitDeclvar(PlanAParser.DeclvarContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitTrap(PlanAParser.TrapContext ctx) { return visitChildren(ctx); }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *

View File

@ -113,6 +113,12 @@ interface PlanAParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result * @return the visitor result
*/ */
T visitEmpty(PlanAParser.EmptyContext ctx); T visitEmpty(PlanAParser.EmptyContext ctx);
/**
* Visit a parse tree produced by {@link PlanAParser#emptyscope}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitEmptyscope(PlanAParser.EmptyscopeContext ctx);
/** /**
* Visit a parse tree produced by {@link PlanAParser#initializer}. * Visit a parse tree produced by {@link PlanAParser#initializer}.
* @param ctx the parse tree * @param ctx the parse tree
@ -143,6 +149,12 @@ interface PlanAParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result * @return the visitor result
*/ */
T visitDeclvar(PlanAParser.DeclvarContext ctx); T visitDeclvar(PlanAParser.DeclvarContext ctx);
/**
* Visit a parse tree produced by {@link PlanAParser#trap}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitTrap(PlanAParser.TrapContext ctx);
/** /**
* Visit a parse tree produced by the {@code comp} * Visit a parse tree produced by the {@code comp}
* labeled alternative in {@link PlanAParser#expression}. * labeled alternative in {@link PlanAParser#expression}.

View File

@ -34,7 +34,7 @@ public final class PlanAPlugin extends Plugin {
return "Plan A scripting language for Elasticsearch"; return "Plan A scripting language for Elasticsearch";
} }
public void onModule(ScriptModule module) { public void onModule(final ScriptModule module) {
module.addScriptEngine(PlanAScriptEngineService.class); module.addScriptEngine(PlanAScriptEngineService.class);
} }
} }

View File

@ -40,117 +40,199 @@ import java.security.ProtectionDomain;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* Implementation of a ScriptEngine for the Plan A language.
*/
public class PlanAScriptEngineService extends AbstractComponent implements ScriptEngineService { public class PlanAScriptEngineService extends AbstractComponent implements ScriptEngineService {
/**
* Standard name of the Plan A language.
*/
public static final String NAME = "plan-a"; public static final String NAME = "plan-a";
// default settings, used unless otherwise specified
/**
* Default compiler settings to be used.
*/
private static final CompilerSettings DEFAULT_COMPILER_SETTINGS = new CompilerSettings(); private static final CompilerSettings DEFAULT_COMPILER_SETTINGS = new CompilerSettings();
public static final String NUMERIC_OVERFLOW = "numeric_overflow"; /**
* Permissions context used during compilation.
*/
private static final AccessControlContext COMPILATION_CONTEXT;
// TODO: how should custom definitions be specified? /**
* Setup the allowed permissions.
*/
static {
final Permissions none = new Permissions();
none.setReadOnly();
COMPILATION_CONTEXT = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, none)
});
}
/**
* Used only for testing.
*/
private Definition definition = null; private Definition definition = null;
/**
* Used only for testing.
*/
void setDefinition(final Definition definition) {
this.definition = definition;
}
/**
* Constructor.
* @param settings The settings to initialize the engine with.
*/
@Inject @Inject
public PlanAScriptEngineService(Settings settings) { public PlanAScriptEngineService(final Settings settings) {
super(settings); super(settings);
} }
public void setDefinition(final Definition definition) { /**
this.definition = new Definition(definition); * Get the type name(s) for the language.
} * @return Always contains only the single name of the language.
*/
@Override @Override
public String[] types() { public String[] types() {
return new String[] { NAME }; return new String[] { NAME };
} }
/**
* Get the extension(s) for the language.
* @return Always contains only the single extension of the language.
*/
@Override @Override
public String[] extensions() { public String[] extensions() {
return new String[] { NAME }; return new String[] { NAME };
} }
/**
* Whether or not the engine is secure.
* @return Always true as the engine should be secure at runtime.
*/
@Override @Override
public boolean sandboxed() { public boolean sandboxed() {
return true; return true;
} }
// context used during compilation /**
private static final AccessControlContext COMPILATION_CONTEXT; * Compiles a Plan A script with the specified parameters.
static { * @param script The code to be compiled.
Permissions none = new Permissions(); * @param params The params used to modify the compiler settings on a per script basis.
none.setReadOnly(); * @return Compiled script object represented by an {@link Executable}.
COMPILATION_CONTEXT = new AccessControlContext(new ProtectionDomain[] { */
new ProtectionDomain(null, none)
});
}
@Override @Override
public Object compile(String script, Map<String, String> params) { public Object compile(final String script, final Map<String, String> params) {
final CompilerSettings compilerSettings; final CompilerSettings compilerSettings;
if (params.isEmpty()) { if (params.isEmpty()) {
// Use the default settings.
compilerSettings = DEFAULT_COMPILER_SETTINGS; compilerSettings = DEFAULT_COMPILER_SETTINGS;
} else { } else {
// custom settings // Use custom settings specified by params.
compilerSettings = new CompilerSettings(); compilerSettings = new CompilerSettings();
Map<String,String> clone = new HashMap<>(params); Map<String, String> copy = new HashMap<>(params);
String value = clone.remove(NUMERIC_OVERFLOW); String value = copy.remove(CompilerSettings.NUMERIC_OVERFLOW);
if (value != null) { if (value != null) {
// TODO: can we get a real boolean parser in here?
compilerSettings.setNumericOverflow(Boolean.parseBoolean(value)); compilerSettings.setNumericOverflow(Boolean.parseBoolean(value));
} }
if (!clone.isEmpty()) {
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + clone); value = copy.remove(CompilerSettings.MAX_LOOP_COUNTER);
if (value != null) {
compilerSettings.setMaxLoopCounter(Integer.parseInt(value));
}
if (!copy.isEmpty()) {
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy);
} }
} }
// check we ourselves are not being called by unprivileged code
SecurityManager sm = System.getSecurityManager(); // Check we ourselves are not being called by unprivileged code.
final SecurityManager sm = System.getSecurityManager();
if (sm != null) { if (sm != null) {
sm.checkPermission(new SpecialPermission()); sm.checkPermission(new SpecialPermission());
} }
// create our loader (which loads compiled code with no permissions)
Compiler.Loader loader = AccessController.doPrivileged(new PrivilegedAction<Compiler.Loader>() { // Create our loader (which loads compiled code with no permissions).
final Compiler.Loader loader = AccessController.doPrivileged(new PrivilegedAction<Compiler.Loader>() {
@Override @Override
public Compiler.Loader run() { public Compiler.Loader run() {
return new Compiler.Loader(getClass().getClassLoader()); return new Compiler.Loader(getClass().getClassLoader());
} }
}); });
// drop all permissions to actually compile the code itself
// Drop all permissions to actually compile the code itself.
return AccessController.doPrivileged(new PrivilegedAction<Executable>() { return AccessController.doPrivileged(new PrivilegedAction<Executable>() {
@Override @Override
public Executable run() { public Executable run() {
return Compiler.compile(loader, "something", script, definition, compilerSettings); return Compiler.compile(loader, "unknown", script, definition, compilerSettings);
} }
}, COMPILATION_CONTEXT); }, COMPILATION_CONTEXT);
} }
/**
* Retrieve an {@link ExecutableScript} for later use.
* @param compiledScript A previously compiled script.
* @param vars The variables to be used in the script.
* @return An {@link ExecutableScript} with the currently specified variables.
*/
@Override @Override
public ExecutableScript executable(CompiledScript compiledScript, Map<String,Object> vars) { public ExecutableScript executable(final CompiledScript compiledScript, final Map<String, Object> vars) {
return new ScriptImpl((Executable) compiledScript.compiled(), vars, null); return new ScriptImpl((Executable)compiledScript.compiled(), vars, null);
} }
/**
* Retrieve a {@link SearchScript} for later use.
* @param compiledScript A previously compiled script.
* @param lookup The object that ultimately allows access to search fields.
* @param vars The variables to be used in the script.
* @return An {@link SearchScript} with the currently specified variables.
*/
@Override @Override
public SearchScript search(CompiledScript compiledScript, SearchLookup lookup, Map<String,Object> vars) { public SearchScript search(final CompiledScript compiledScript, final SearchLookup lookup, final Map<String, Object> vars) {
return new SearchScript() { return new SearchScript() {
/**
* Get the search script that will have access to search field values.
* @param context The LeafReaderContext to be used.
* @return A script that will have the search fields from the current context available for use.
*/
@Override @Override
public LeafSearchScript getLeafSearchScript(LeafReaderContext context) throws IOException { public LeafSearchScript getLeafSearchScript(final LeafReaderContext context) throws IOException {
return new ScriptImpl((Executable) compiledScript.compiled(), vars, lookup.getLeafSearchLookup(context)); return new ScriptImpl((Executable)compiledScript.compiled(), vars, lookup.getLeafSearchLookup(context));
} }
/**
* Whether or not the score is needed.
* @return Always true as it's assumed score is needed.
*/
@Override @Override
public boolean needsScores() { public boolean needsScores() {
return true; // TODO: maybe even do these different and more like expressions. return true;
} }
}; };
} }
/**
* Action taken when a script is removed from the cache.
* @param script The removed script.
*/
@Override @Override
public void scriptRemoved(CompiledScript script) { public void scriptRemoved(final CompiledScript script) {
// nothing to do // Nothing to do.
} }
/**
* Action taken when the engine is closed.
*/
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// nothing to do // Nothing to do.
} }
} }

View File

@ -28,67 +28,128 @@ import org.elasticsearch.search.lookup.LeafSearchLookup;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* ScriptImpl can be used as either an {@link ExecutableScript} or a {@link LeafSearchScript}
* to run a previously compiled Plan A script.
*/
final class ScriptImpl implements ExecutableScript, LeafSearchScript { final class ScriptImpl implements ExecutableScript, LeafSearchScript {
final Executable executable; /**
final Map<String,Object> variables; * The Plan A Executable script that can be run.
final LeafSearchLookup lookup; */
private final Executable executable;
ScriptImpl(Executable executable, Map<String,Object> vars, LeafSearchLookup lookup) {
/**
* A map that can be used to access input parameters at run-time.
*/
private final Map<String, Object> variables;
/**
* The lookup is used to access search field values at run-time.
*/
private final LeafSearchLookup lookup;
/**
* Creates a ScriptImpl for the a previously compiled Plan A script.
* @param executable The previously compiled Plan A script.
* @param vars The initial variables to run the script with.
* @param lookup The lookup to allow search fields to be available if this is run as a search script.
*/
ScriptImpl(final Executable executable, final Map<String, Object> vars, final LeafSearchLookup lookup) {
this.executable = executable; this.executable = executable;
this.lookup = lookup; this.lookup = lookup;
this.variables = new HashMap<>(); this.variables = new HashMap<>();
if (vars != null) { if (vars != null) {
variables.putAll(vars); variables.putAll(vars);
} }
if (lookup != null) { if (lookup != null) {
variables.putAll(lookup.asMap()); variables.putAll(lookup.asMap());
} }
} }
/**
* Set a variable for the script to be run against.
* @param name The variable name.
* @param value The variable value.
*/
@Override @Override
public void setNextVar(String name, Object value) { public void setNextVar(final String name, final Object value) {
variables.put(name, value); variables.put(name, value);
} }
/**
* Run the script.
* @return The script result.
*/
@Override @Override
public Object run() { public Object run() {
return executable.execute(variables); return executable.execute(variables);
} }
@Override
public float runAsFloat() {
return ((Number) run()).floatValue();
}
@Override
public long runAsLong() {
return ((Number) run()).longValue();
}
/**
* Run the script.
* @return The script result as a double.
*/
@Override @Override
public double runAsDouble() { public double runAsDouble() {
return ((Number) run()).doubleValue(); return ((Number)run()).doubleValue();
} }
/**
* Run the script.
* @return The script result as a float.
*/
@Override @Override
public Object unwrap(Object value) { public float runAsFloat() {
return ((Number)run()).floatValue();
}
/**
* Run the script.
* @return The script result as a long.
*/
@Override
public long runAsLong() {
return ((Number)run()).longValue();
}
/**
* This method has no effect in Plan A.
* @param value The value to unwrap.
* @return The value passed in.
*/
@Override
public Object unwrap(final Object value) {
return value; return value;
} }
/**
* Sets the scorer to be accessible within a script.
* @param scorer The scorer used for a search.
*/
@Override @Override
public void setScorer(Scorer scorer) { public void setScorer(final Scorer scorer) {
variables.put("_score", new ScoreAccessor(scorer)); variables.put("#score", new ScoreAccessor(scorer));
} }
/**
* Sets the current document.
* @param doc The current document.
*/
@Override @Override
public void setDocument(int doc) { public void setDocument(final int doc) {
if (lookup != null) { if (lookup != null) {
lookup.setDocument(doc); lookup.setDocument(doc);
} }
} }
/**
* Sets the current source.
* @param source The current source.
*/
@Override @Override
public void setSource(Map<String,Object> source) { public void setSource(final Map<String, Object> source) {
if (lookup != null) { if (lookup != null) {
lookup.source().setSource(source); lookup.source().setSource(source);
} }

View File

@ -1,5 +1,3 @@
package org.elasticsearch.plan.a;
/* /*
* Licensed to Elasticsearch under one or more contributor * Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with * license agreements. See the NOTICE file distributed with
@ -19,6 +17,8 @@ package org.elasticsearch.plan.a;
* under the License. * under the License.
*/ */
package org.elasticsearch.plan.a;
public class Utility { public class Utility {
public static boolean NumberToboolean(final Number value) { public static boolean NumberToboolean(final Number value) {
return value.longValue() != 0; return value.longValue() != 0;

View File

@ -0,0 +1,52 @@
/*
* 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.plan.a;
public class BasicAPITests extends ScriptTestCase {
public void testListIterator() {
assertEquals(3, exec("List x = new ArrayList(); x.add(2); x.add(3); x.add(-2); Iterator y = x.iterator(); " +
"int total = 0; while (y.hasNext()) total += y.next(); return total;"));
assertEquals(3, exec("List<Object> x = new ArrayList(); x.add(2); x.add(3); x.add(-2); Iterator<Object> y = x.iterator(); " +
"int total = 0; while (y.hasNext()) total += (int)y.next(); return total;"));
assertEquals("abc", exec("List<String> x = new ArrayList(); x.add(\"a\"); x.add(\"b\"); x.add(\"c\"); " +
"Iterator<String> y = x.iterator(); String total = \"\"; while (y.hasNext()) total += y.next(); return total;"));
assertEquals(3, exec("def x = new ArrayList(); x.add(2); x.add(3); x.add(-2); def y = x.iterator(); " +
"def total = 0; while (y.hasNext()) total += y.next(); return total;"));
}
public void testSetIterator() {
assertEquals(3, exec("Set x = new HashSet(); x.add(2); x.add(3); x.add(-2); Iterator y = x.iterator(); " +
"int total = 0; while (y.hasNext()) total += y.next(); return total;"));
assertEquals(3, exec("Set<Object> x = new HashSet(); x.add(2); x.add(3); x.add(-2); Iterator<Object> y = x.iterator(); " +
"int total = 0; while (y.hasNext()) total += (int)y.next(); return total;"));
assertEquals("abc", exec("Set<String> x = new HashSet(); x.add(\"a\"); x.add(\"b\"); x.add(\"c\"); " +
"Iterator<String> y = x.iterator(); String total = \"\"; while (y.hasNext()) total += y.next(); return total;"));
assertEquals(3, exec("def x = new HashSet(); x.add(2); x.add(3); x.add(-2); def y = x.iterator(); " +
"def total = 0; while (y.hasNext()) total += (int)y.next(); return total;"));
}
public void testMapIterator() {
assertEquals(3, exec("Map x = new HashMap(); x.put(2, 2); x.put(3, 3); x.put(-2, -2); Iterator y = x.keySet().iterator(); " +
"int total = 0; while (y.hasNext()) total += (int)y.next(); return total;"));
assertEquals(3, exec("Map x = new HashMap(); x.put(2, 2); x.put(3, 3); x.put(-2, -2); Iterator y = x.values().iterator(); " +
"int total = 0; while (y.hasNext()) total += (int)y.next(); return total;"));
}
}

View File

@ -24,14 +24,14 @@ import java.util.Map;
/** Tests floating point overflow with numeric overflow disabled */ /** Tests floating point overflow with numeric overflow disabled */
public class FloatOverflowDisabledTests extends ScriptTestCase { public class FloatOverflowDisabledTests extends ScriptTestCase {
/** wire overflow to false for all tests */ /** wire overflow to false for all tests */
@Override @Override
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
return exec(script, vars, Collections.singletonMap(PlanAScriptEngineService.NUMERIC_OVERFLOW, "false")); return exec(script, vars, Collections.singletonMap(CompilerSettings.NUMERIC_OVERFLOW, "false"));
} }
public void testAssignmentAdditionOverflow() { public void testAssignmentAdditionOverflow() {
// float // float
try { try {
exec("float x = 3.4028234663852886E38f; x += 3.4028234663852886E38f; return x;"); exec("float x = 3.4028234663852886E38f; x += 3.4028234663852886E38f; return x;");
@ -41,7 +41,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = -3.4028234663852886E38f; x += -3.4028234663852886E38f; return x;"); exec("float x = -3.4028234663852886E38f; x += -3.4028234663852886E38f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double // double
try { try {
exec("double x = 1.7976931348623157E308; x += 1.7976931348623157E308; return x;"); exec("double x = 1.7976931348623157E308; x += 1.7976931348623157E308; return x;");
@ -52,8 +52,8 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentSubtractionOverflow() { public void testAssignmentSubtractionOverflow() {
// float // float
try { try {
exec("float x = 3.4028234663852886E38f; x -= -3.4028234663852886E38f; return x;"); exec("float x = 3.4028234663852886E38f; x -= -3.4028234663852886E38f; return x;");
@ -63,7 +63,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = -3.4028234663852886E38f; x -= 3.4028234663852886E38f; return x;"); exec("float x = -3.4028234663852886E38f; x -= 3.4028234663852886E38f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double // double
try { try {
exec("double x = 1.7976931348623157E308; x -= -1.7976931348623157E308; return x;"); exec("double x = 1.7976931348623157E308; x -= -1.7976931348623157E308; return x;");
@ -74,7 +74,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentMultiplicationOverflow() { public void testAssignmentMultiplicationOverflow() {
// float // float
try { try {
@ -85,7 +85,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = 3.4028234663852886E38f; x *= -3.4028234663852886E38f; return x;"); exec("float x = 3.4028234663852886E38f; x *= -3.4028234663852886E38f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double // double
try { try {
exec("double x = 1.7976931348623157E308; x *= 1.7976931348623157E308; return x;"); exec("double x = 1.7976931348623157E308; x *= 1.7976931348623157E308; return x;");
@ -96,7 +96,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentDivisionOverflow() { public void testAssignmentDivisionOverflow() {
// float // float
try { try {
@ -111,7 +111,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = 1.0f; x /= 0.0f; return x;"); exec("float x = 1.0f; x /= 0.0f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double // double
try { try {
exec("double x = 1.7976931348623157E308; x /= 4.9E-324; return x;"); exec("double x = 1.7976931348623157E308; x /= 4.9E-324; return x;");
@ -137,7 +137,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAdditionConst() throws Exception { public void testAdditionConst() throws Exception {
try { try {
exec("return 3.4028234663852886E38f + 3.4028234663852886E38f;"); exec("return 3.4028234663852886E38f + 3.4028234663852886E38f;");
@ -148,7 +148,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testSubtraction() throws Exception { public void testSubtraction() throws Exception {
try { try {
exec("float x = -3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x - y;"); exec("float x = -3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x - y;");
@ -159,7 +159,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testSubtractionConst() throws Exception { public void testSubtractionConst() throws Exception {
try { try {
exec("return -3.4028234663852886E38f - 3.4028234663852886E38f;"); exec("return -3.4028234663852886E38f - 3.4028234663852886E38f;");
@ -170,7 +170,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testMultiplication() throws Exception { public void testMultiplication() throws Exception {
try { try {
exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x * y;"); exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x * y;");
@ -181,7 +181,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testMultiplicationConst() throws Exception { public void testMultiplicationConst() throws Exception {
try { try {
exec("return 3.4028234663852886E38f * 3.4028234663852886E38f;"); exec("return 3.4028234663852886E38f * 3.4028234663852886E38f;");
@ -211,7 +211,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testDivisionConst() throws Exception { public void testDivisionConst() throws Exception {
try { try {
exec("return 3.4028234663852886E38f / 1.401298464324817E-45f;"); exec("return 3.4028234663852886E38f / 1.401298464324817E-45f;");
@ -230,7 +230,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testDivisionNaN() throws Exception { public void testDivisionNaN() throws Exception {
// float division, constant division, and assignment // float division, constant division, and assignment
try { try {
@ -245,7 +245,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = 0f; x /= 0f; return x;"); exec("float x = 0f; x /= 0f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double division, constant division, and assignment // double division, constant division, and assignment
try { try {
exec("double x = 0.0; double y = 0.0; return x / y;"); exec("double x = 0.0; double y = 0.0; return x / y;");
@ -260,7 +260,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testRemainderNaN() throws Exception { public void testRemainderNaN() throws Exception {
// float division, constant division, and assignment // float division, constant division, and assignment
try { try {
@ -275,7 +275,7 @@ public class FloatOverflowDisabledTests extends ScriptTestCase {
exec("float x = 1f; x %= 0f; return x;"); exec("float x = 1f; x %= 0f; return x;");
fail("didn't hit expected exception"); fail("didn't hit expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// double division, constant division, and assignment // double division, constant division, and assignment
try { try {
exec("double x = 1.0; double y = 0.0; return x % y;"); exec("double x = 1.0; double y = 0.0; return x % y;");

View File

@ -24,49 +24,49 @@ import java.util.Map;
/** Tests floating point overflow with numeric overflow enabled */ /** Tests floating point overflow with numeric overflow enabled */
public class FloatOverflowEnabledTests extends ScriptTestCase { public class FloatOverflowEnabledTests extends ScriptTestCase {
/** wire overflow to true for all tests */ /** wire overflow to true for all tests */
@Override @Override
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
return exec(script, vars, Collections.singletonMap(PlanAScriptEngineService.NUMERIC_OVERFLOW, "true")); return exec(script, vars, Collections.singletonMap(CompilerSettings.NUMERIC_OVERFLOW, "true"));
} }
public void testAssignmentAdditionOverflow() { public void testAssignmentAdditionOverflow() {
// float // float
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x += 3.4028234663852886E38f; return x;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x += 3.4028234663852886E38f; return x;"));
assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; x += -3.4028234663852886E38f; return x;")); assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; x += -3.4028234663852886E38f; return x;"));
// double // double
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x += 1.7976931348623157E308; return x;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x += 1.7976931348623157E308; return x;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; x += -1.7976931348623157E308; return x;")); assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; x += -1.7976931348623157E308; return x;"));
} }
public void testAssignmentSubtractionOverflow() { public void testAssignmentSubtractionOverflow() {
// float // float
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x -= -3.4028234663852886E38f; return x;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x -= -3.4028234663852886E38f; return x;"));
assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; x -= 3.4028234663852886E38f; return x;")); assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; x -= 3.4028234663852886E38f; return x;"));
// double // double
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x -= -1.7976931348623157E308; return x;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x -= -1.7976931348623157E308; return x;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; x -= 1.7976931348623157E308; return x;")); assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; x -= 1.7976931348623157E308; return x;"));
} }
public void testAssignmentMultiplicationOverflow() { public void testAssignmentMultiplicationOverflow() {
// float // float
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x *= 3.4028234663852886E38f; return x;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x *= 3.4028234663852886E38f; return x;"));
assertEquals(Float.NEGATIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x *= -3.4028234663852886E38f; return x;")); assertEquals(Float.NEGATIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x *= -3.4028234663852886E38f; return x;"));
// double // double
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x *= 1.7976931348623157E308; return x;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x *= 1.7976931348623157E308; return x;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("double x = 1.7976931348623157E308; x *= -1.7976931348623157E308; return x;")); assertEquals(Double.NEGATIVE_INFINITY, exec("double x = 1.7976931348623157E308; x *= -1.7976931348623157E308; return x;"));
} }
public void testAssignmentDivisionOverflow() { public void testAssignmentDivisionOverflow() {
// float // float
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x /= 1.401298464324817E-45f; return x;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x /= 1.401298464324817E-45f; return x;"));
assertEquals(Float.NEGATIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x /= -1.401298464324817E-45f; return x;")); assertEquals(Float.NEGATIVE_INFINITY, exec("float x = 3.4028234663852886E38f; x /= -1.401298464324817E-45f; return x;"));
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 1.0f; x /= 0.0f; return x;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 1.0f; x /= 0.0f; return x;"));
// double // double
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x /= 4.9E-324; return x;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; x /= 4.9E-324; return x;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("double x = 1.7976931348623157E308; x /= -4.9E-324; return x;")); assertEquals(Double.NEGATIVE_INFINITY, exec("double x = 1.7976931348623157E308; x /= -4.9E-324; return x;"));
@ -74,32 +74,32 @@ public class FloatOverflowEnabledTests extends ScriptTestCase {
} }
public void testAddition() throws Exception { public void testAddition() throws Exception {
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x + y;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x + y;"));
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 1.7976931348623157E308; return x + y;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 1.7976931348623157E308; return x + y;"));
} }
public void testAdditionConst() throws Exception { public void testAdditionConst() throws Exception {
assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f + 3.4028234663852886E38f;")); assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f + 3.4028234663852886E38f;"));
assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 + 1.7976931348623157E308;")); assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 + 1.7976931348623157E308;"));
} }
public void testSubtraction() throws Exception { public void testSubtraction() throws Exception {
assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x - y;")); assertEquals(Float.NEGATIVE_INFINITY, exec("float x = -3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x - y;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; double y = 1.7976931348623157E308; return x - y;")); assertEquals(Double.NEGATIVE_INFINITY, exec("double x = -1.7976931348623157E308; double y = 1.7976931348623157E308; return x - y;"));
} }
public void testSubtractionConst() throws Exception { public void testSubtractionConst() throws Exception {
assertEquals(Float.NEGATIVE_INFINITY, exec("return -3.4028234663852886E38f - 3.4028234663852886E38f;")); assertEquals(Float.NEGATIVE_INFINITY, exec("return -3.4028234663852886E38f - 3.4028234663852886E38f;"));
assertEquals(Double.NEGATIVE_INFINITY, exec("return -1.7976931348623157E308 - 1.7976931348623157E308;")); assertEquals(Double.NEGATIVE_INFINITY, exec("return -1.7976931348623157E308 - 1.7976931348623157E308;"));
} }
public void testMultiplication() throws Exception { public void testMultiplication() throws Exception {
assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x * y;")); assertEquals(Float.POSITIVE_INFINITY, exec("float x = 3.4028234663852886E38f; float y = 3.4028234663852886E38f; return x * y;"));
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 1.7976931348623157E308; return x * y;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 1.7976931348623157E308; return x * y;"));
} }
public void testMultiplicationConst() throws Exception { public void testMultiplicationConst() throws Exception {
assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f * 3.4028234663852886E38f;")); assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f * 3.4028234663852886E38f;"));
assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 * 1.7976931348623157E308;")); assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 * 1.7976931348623157E308;"));
} }
@ -109,32 +109,32 @@ public class FloatOverflowEnabledTests extends ScriptTestCase {
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 4.9E-324; return x / y;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.7976931348623157E308; double y = 4.9E-324; return x / y;"));
assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.0; double y = 0.0; return x / y;")); assertEquals(Double.POSITIVE_INFINITY, exec("double x = 1.0; double y = 0.0; return x / y;"));
} }
public void testDivisionConst() throws Exception { public void testDivisionConst() throws Exception {
assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f / 1.401298464324817E-45f;")); assertEquals(Float.POSITIVE_INFINITY, exec("return 3.4028234663852886E38f / 1.401298464324817E-45f;"));
assertEquals(Float.POSITIVE_INFINITY, exec("return 1.0f / 0.0f;")); assertEquals(Float.POSITIVE_INFINITY, exec("return 1.0f / 0.0f;"));
assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 / 4.9E-324;")); assertEquals(Double.POSITIVE_INFINITY, exec("return 1.7976931348623157E308 / 4.9E-324;"));
assertEquals(Double.POSITIVE_INFINITY, exec("return 1.0 / 0.0;")); assertEquals(Double.POSITIVE_INFINITY, exec("return 1.0 / 0.0;"));
} }
public void testDivisionNaN() throws Exception { public void testDivisionNaN() throws Exception {
// float division, constant division, and assignment // float division, constant division, and assignment
assertTrue(Float.isNaN((Float) exec("float x = 0f; float y = 0f; return x / y;"))); assertTrue(Float.isNaN((Float) exec("float x = 0f; float y = 0f; return x / y;")));
assertTrue(Float.isNaN((Float) exec("return 0f / 0f;"))); assertTrue(Float.isNaN((Float) exec("return 0f / 0f;")));
assertTrue(Float.isNaN((Float) exec("float x = 0f; x /= 0f; return x;"))); assertTrue(Float.isNaN((Float) exec("float x = 0f; x /= 0f; return x;")));
// double division, constant division, and assignment // double division, constant division, and assignment
assertTrue(Double.isNaN((Double) exec("double x = 0.0; double y = 0.0; return x / y;"))); assertTrue(Double.isNaN((Double) exec("double x = 0.0; double y = 0.0; return x / y;")));
assertTrue(Double.isNaN((Double) exec("return 0.0 / 0.0;"))); assertTrue(Double.isNaN((Double) exec("return 0.0 / 0.0;")));
assertTrue(Double.isNaN((Double) exec("double x = 0.0; x /= 0.0; return x;"))); assertTrue(Double.isNaN((Double) exec("double x = 0.0; x /= 0.0; return x;")));
} }
public void testRemainderNaN() throws Exception { public void testRemainderNaN() throws Exception {
// float division, constant division, and assignment // float division, constant division, and assignment
assertTrue(Float.isNaN((Float) exec("float x = 1f; float y = 0f; return x % y;"))); assertTrue(Float.isNaN((Float) exec("float x = 1f; float y = 0f; return x % y;")));
assertTrue(Float.isNaN((Float) exec("return 1f % 0f;"))); assertTrue(Float.isNaN((Float) exec("return 1f % 0f;")));
assertTrue(Float.isNaN((Float) exec("float x = 1f; x %= 0f; return x;"))); assertTrue(Float.isNaN((Float) exec("float x = 1f; x %= 0f; return x;")));
// double division, constant division, and assignment // double division, constant division, and assignment
assertTrue(Double.isNaN((Double) exec("double x = 1.0; double y = 0.0; return x % y;"))); assertTrue(Double.isNaN((Double) exec("double x = 1.0; double y = 0.0; return x % y;")));
assertTrue(Double.isNaN((Double) exec("return 1.0 % 0.0;"))); assertTrue(Double.isNaN((Double) exec("return 1.0 % 0.0;")));

View File

@ -24,11 +24,11 @@ import java.util.Map;
/** Tests integer overflow with numeric overflow disabled */ /** Tests integer overflow with numeric overflow disabled */
public class IntegerOverflowDisabledTests extends ScriptTestCase { public class IntegerOverflowDisabledTests extends ScriptTestCase {
/** wire overflow to true for all tests */ /** wire overflow to true for all tests */
@Override @Override
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
return exec(script, vars, Collections.singletonMap(PlanAScriptEngineService.NUMERIC_OVERFLOW, "false")); return exec(script, vars, Collections.singletonMap(CompilerSettings.NUMERIC_OVERFLOW, "false"));
} }
public void testAssignmentAdditionOverflow() { public void testAssignmentAdditionOverflow() {
@ -42,7 +42,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("byte x = 0; x += -129; return x;"); exec("byte x = 0; x += -129; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// short // short
try { try {
exec("short x = 0; x += 32768; return x;"); exec("short x = 0; x += 32768; return x;");
@ -53,7 +53,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("byte x = 0; x += -32769; return x;"); exec("byte x = 0; x += -32769; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// char // char
try { try {
exec("char x = 0; x += 65536; return x;"); exec("char x = 0; x += 65536; return x;");
@ -64,7 +64,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("char x = 0; x += -65536; return x;"); exec("char x = 0; x += -65536; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// int // int
try { try {
exec("int x = 1; x += 2147483647; return x;"); exec("int x = 1; x += 2147483647; return x;");
@ -75,7 +75,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("int x = -2; x += -2147483647; return x;"); exec("int x = -2; x += -2147483647; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// long // long
try { try {
exec("long x = 1; x += 9223372036854775807L; return x;"); exec("long x = 1; x += 9223372036854775807L; return x;");
@ -87,7 +87,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentSubtractionOverflow() { public void testAssignmentSubtractionOverflow() {
// byte // byte
try { try {
@ -99,7 +99,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("byte x = 0; x -= 129; return x;"); exec("byte x = 0; x -= 129; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// short // short
try { try {
exec("short x = 0; x -= -32768; return x;"); exec("short x = 0; x -= -32768; return x;");
@ -110,7 +110,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("byte x = 0; x -= 32769; return x;"); exec("byte x = 0; x -= 32769; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// char // char
try { try {
exec("char x = 0; x -= -65536; return x;"); exec("char x = 0; x -= -65536; return x;");
@ -121,7 +121,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("char x = 0; x -= 65536; return x;"); exec("char x = 0; x -= 65536; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// int // int
try { try {
exec("int x = 1; x -= -2147483647; return x;"); exec("int x = 1; x -= -2147483647; return x;");
@ -132,7 +132,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("int x = -2; x -= 2147483647; return x;"); exec("int x = -2; x -= 2147483647; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// long // long
try { try {
exec("long x = 1; x -= -9223372036854775807L; return x;"); exec("long x = 1; x -= -9223372036854775807L; return x;");
@ -144,7 +144,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentMultiplicationOverflow() { public void testAssignmentMultiplicationOverflow() {
// byte // byte
try { try {
@ -156,7 +156,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("byte x = 2; x *= -128; return x;"); exec("byte x = 2; x *= -128; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// char // char
try { try {
exec("char x = 2; x *= 65536; return x;"); exec("char x = 2; x *= 65536; return x;");
@ -167,7 +167,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("char x = 2; x *= -65536; return x;"); exec("char x = 2; x *= -65536; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// int // int
try { try {
exec("int x = 2; x *= 2147483647; return x;"); exec("int x = 2; x *= 2147483647; return x;");
@ -178,7 +178,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("int x = 2; x *= -2147483647; return x;"); exec("int x = 2; x *= -2147483647; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// long // long
try { try {
exec("long x = 2; x *= 9223372036854775807L; return x;"); exec("long x = 2; x *= 9223372036854775807L; return x;");
@ -190,7 +190,7 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAssignmentDivisionOverflow() { public void testAssignmentDivisionOverflow() {
// byte // byte
try { try {
@ -203,108 +203,108 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("short x = (short) -32768; x /= -1; return x;"); exec("short x = (short) -32768; x /= -1; return x;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// cannot happen for char: unsigned // cannot happen for char: unsigned
// int // int
try { try {
exec("int x = -2147483647 - 1; x /= -1; return x;"); exec("int x = -2147483647 - 1; x /= -1; return x;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// long // long
try { try {
exec("long x = -9223372036854775807L - 1L; x /=-1L; return x;"); exec("long x = -9223372036854775807L - 1L; x /=-1L; return x;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testIncrementOverFlow() throws Exception { public void testIncrementOverFlow() throws Exception {
// byte // byte
try { try {
exec("byte x = 127; ++x; return x;"); exec("byte x = 127; ++x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("byte x = 127; x++; return x;"); exec("byte x = 127; x++; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("byte x = (byte) -128; --x; return x;"); exec("byte x = (byte) -128; --x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("byte x = (byte) -128; x--; return x;"); exec("byte x = (byte) -128; x--; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// short // short
try { try {
exec("short x = 32767; ++x; return x;"); exec("short x = 32767; ++x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("short x = 32767; x++; return x;"); exec("short x = 32767; x++; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("short x = (short) -32768; --x; return x;"); exec("short x = (short) -32768; --x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("short x = (short) -32768; x--; return x;"); exec("short x = (short) -32768; x--; return x;");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// char // char
try { try {
exec("char x = 65535; ++x; return x;"); exec("char x = 65535; ++x; return x;");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("char x = 65535; x++; return x;"); exec("char x = 65535; x++; return x;");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("char x = (char) 0; --x; return x;"); exec("char x = (char) 0; --x; return x;");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("char x = (char) 0; x--; return x;"); exec("char x = (char) 0; x--; return x;");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// int // int
try { try {
exec("int x = 2147483647; ++x; return x;"); exec("int x = 2147483647; ++x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("int x = 2147483647; x++; return x;"); exec("int x = 2147483647; x++; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("int x = (int) -2147483648L; --x; return x;"); exec("int x = (int) -2147483648L; --x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("int x = (int) -2147483648L; x--; return x;"); exec("int x = (int) -2147483648L; x--; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
// long // long
try { try {
exec("long x = 9223372036854775807L; ++x; return x;"); exec("long x = 9223372036854775807L; ++x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = 9223372036854775807L; x++; return x;"); exec("long x = 9223372036854775807L; x++; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
@ -320,68 +320,68 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAddition() throws Exception { public void testAddition() throws Exception {
try { try {
exec("int x = 2147483647; int y = 2147483647; return x + y;"); exec("int x = 2147483647; int y = 2147483647; return x + y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x + y;"); exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x + y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testAdditionConst() throws Exception { public void testAdditionConst() throws Exception {
try { try {
exec("return 2147483647 + 2147483647;"); exec("return 2147483647 + 2147483647;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("return 9223372036854775807L + 9223372036854775807L;"); exec("return 9223372036854775807L + 9223372036854775807L;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testSubtraction() throws Exception { public void testSubtraction() throws Exception {
try { try {
exec("int x = -10; int y = 2147483647; return x - y;"); exec("int x = -10; int y = 2147483647; return x - y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = -10L; long y = 9223372036854775807L; return x - y;"); exec("long x = -10L; long y = 9223372036854775807L; return x - y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testSubtractionConst() throws Exception { public void testSubtractionConst() throws Exception {
try { try {
exec("return -10 - 2147483647;"); exec("return -10 - 2147483647;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("return -10L - 9223372036854775807L;"); exec("return -10L - 9223372036854775807L;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testMultiplication() throws Exception { public void testMultiplication() throws Exception {
try { try {
exec("int x = 2147483647; int y = 2147483647; return x * y;"); exec("int x = 2147483647; int y = 2147483647; return x * y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x * y;"); exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x * y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testMultiplicationConst() throws Exception { public void testMultiplicationConst() throws Exception {
try { try {
exec("return 2147483647 * 2147483647;"); exec("return 2147483647 * 2147483647;");
@ -399,13 +399,13 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
exec("int x = -2147483647 - 1; int y = -1; return x / y;"); exec("int x = -2147483647 - 1; int y = -1; return x / y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = -9223372036854775808L; long y = -1L; return x / y;"); exec("long x = -9223372036854775808L; long y = -1L; return x / y;");
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testDivisionConst() throws Exception { public void testDivisionConst() throws Exception {
try { try {
exec("return (-2147483648) / -1;"); exec("return (-2147483648) / -1;");
@ -417,25 +417,25 @@ public class IntegerOverflowDisabledTests extends ScriptTestCase {
fail("should have hit exception"); fail("should have hit exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testNegationOverflow() throws Exception { public void testNegationOverflow() throws Exception {
try { try {
exec("int x = -2147483648; x = -x; return x;"); exec("int x = -2147483648; x = -x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = -9223372036854775808L; x = -x; return x;"); exec("long x = -9223372036854775808L; x = -x; return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
} }
public void testNegationOverflowConst() throws Exception { public void testNegationOverflowConst() throws Exception {
try { try {
exec("int x = -(-2147483648); return x;"); exec("int x = -(-2147483648); return x;");
fail("did not get expected exception"); fail("did not get expected exception");
} catch (ArithmeticException expected) {} } catch (ArithmeticException expected) {}
try { try {
exec("long x = -(-9223372036854775808L); return x;"); exec("long x = -(-9223372036854775808L); return x;");
fail("did not get expected exception"); fail("did not get expected exception");

View File

@ -24,148 +24,148 @@ import java.util.Map;
/** Tests integer overflow with numeric overflow enabled */ /** Tests integer overflow with numeric overflow enabled */
public class IntegerOverflowEnabledTests extends ScriptTestCase { public class IntegerOverflowEnabledTests extends ScriptTestCase {
/** wire overflow to true for all tests */ /** wire overflow to true for all tests */
@Override @Override
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
return exec(script, vars, Collections.singletonMap(PlanAScriptEngineService.NUMERIC_OVERFLOW, "true")); return exec(script, vars, Collections.singletonMap(CompilerSettings.NUMERIC_OVERFLOW, "true"));
} }
public void testAssignmentAdditionOverflow() { public void testAssignmentAdditionOverflow() {
// byte // byte
assertEquals((byte)(0 + 128), exec("byte x = 0; x += 128; return x;")); assertEquals((byte)(0 + 128), exec("byte x = 0; x += 128; return x;"));
assertEquals((byte)(0 + -129), exec("byte x = 0; x += -129; return x;")); assertEquals((byte)(0 + -129), exec("byte x = 0; x += -129; return x;"));
// short // short
assertEquals((short)(0 + 32768), exec("short x = 0; x += 32768; return x;")); assertEquals((short)(0 + 32768), exec("short x = 0; x += 32768; return x;"));
assertEquals((short)(0 + -32769), exec("short x = 0; x += -32769; return x;")); assertEquals((short)(0 + -32769), exec("short x = 0; x += -32769; return x;"));
// char // char
assertEquals((char)(0 + 65536), exec("char x = 0; x += 65536; return x;")); assertEquals((char)(0 + 65536), exec("char x = 0; x += 65536; return x;"));
assertEquals((char)(0 + -65536), exec("char x = 0; x += -65536; return x;")); assertEquals((char)(0 + -65536), exec("char x = 0; x += -65536; return x;"));
// int // int
assertEquals(1 + 2147483647, exec("int x = 1; x += 2147483647; return x;")); assertEquals(1 + 2147483647, exec("int x = 1; x += 2147483647; return x;"));
assertEquals(-2 + -2147483647, exec("int x = -2; x += -2147483647; return x;")); assertEquals(-2 + -2147483647, exec("int x = -2; x += -2147483647; return x;"));
// long // long
assertEquals(1L + 9223372036854775807L, exec("long x = 1; x += 9223372036854775807L; return x;")); assertEquals(1L + 9223372036854775807L, exec("long x = 1; x += 9223372036854775807L; return x;"));
assertEquals(-2L + -9223372036854775807L, exec("long x = -2; x += -9223372036854775807L; return x;")); assertEquals(-2L + -9223372036854775807L, exec("long x = -2; x += -9223372036854775807L; return x;"));
} }
public void testAssignmentSubtractionOverflow() { public void testAssignmentSubtractionOverflow() {
// byte // byte
assertEquals((byte)(0 - -128), exec("byte x = 0; x -= -128; return x;")); assertEquals((byte)(0 - -128), exec("byte x = 0; x -= -128; return x;"));
assertEquals((byte)(0 - 129), exec("byte x = 0; x -= 129; return x;")); assertEquals((byte)(0 - 129), exec("byte x = 0; x -= 129; return x;"));
// short // short
assertEquals((short)(0 - -32768), exec("short x = 0; x -= -32768; return x;")); assertEquals((short)(0 - -32768), exec("short x = 0; x -= -32768; return x;"));
assertEquals((short)(0 - 32769), exec("short x = 0; x -= 32769; return x;")); assertEquals((short)(0 - 32769), exec("short x = 0; x -= 32769; return x;"));
// char // char
assertEquals((char)(0 - -65536), exec("char x = 0; x -= -65536; return x;")); assertEquals((char)(0 - -65536), exec("char x = 0; x -= -65536; return x;"));
assertEquals((char)(0 - 65536), exec("char x = 0; x -= 65536; return x;")); assertEquals((char)(0 - 65536), exec("char x = 0; x -= 65536; return x;"));
// int // int
assertEquals(1 - -2147483647, exec("int x = 1; x -= -2147483647; return x;")); assertEquals(1 - -2147483647, exec("int x = 1; x -= -2147483647; return x;"));
assertEquals(-2 - 2147483647, exec("int x = -2; x -= 2147483647; return x;")); assertEquals(-2 - 2147483647, exec("int x = -2; x -= 2147483647; return x;"));
// long // long
assertEquals(1L - -9223372036854775807L, exec("long x = 1; x -= -9223372036854775807L; return x;")); assertEquals(1L - -9223372036854775807L, exec("long x = 1; x -= -9223372036854775807L; return x;"));
assertEquals(-2L - 9223372036854775807L, exec("long x = -2; x -= 9223372036854775807L; return x;")); assertEquals(-2L - 9223372036854775807L, exec("long x = -2; x -= 9223372036854775807L; return x;"));
} }
public void testAssignmentMultiplicationOverflow() { public void testAssignmentMultiplicationOverflow() {
// byte // byte
assertEquals((byte) (2 * 128), exec("byte x = 2; x *= 128; return x;")); assertEquals((byte) (2 * 128), exec("byte x = 2; x *= 128; return x;"));
assertEquals((byte) (2 * -128), exec("byte x = 2; x *= -128; return x;")); assertEquals((byte) (2 * -128), exec("byte x = 2; x *= -128; return x;"));
// char // char
assertEquals((char) (2 * 65536), exec("char x = 2; x *= 65536; return x;")); assertEquals((char) (2 * 65536), exec("char x = 2; x *= 65536; return x;"));
assertEquals((char) (2 * -65536), exec("char x = 2; x *= -65536; return x;")); assertEquals((char) (2 * -65536), exec("char x = 2; x *= -65536; return x;"));
// int // int
assertEquals(2 * 2147483647, exec("int x = 2; x *= 2147483647; return x;")); assertEquals(2 * 2147483647, exec("int x = 2; x *= 2147483647; return x;"));
assertEquals(2 * -2147483647, exec("int x = 2; x *= -2147483647; return x;")); assertEquals(2 * -2147483647, exec("int x = 2; x *= -2147483647; return x;"));
// long // long
assertEquals(2L * 9223372036854775807L, exec("long x = 2; x *= 9223372036854775807L; return x;")); assertEquals(2L * 9223372036854775807L, exec("long x = 2; x *= 9223372036854775807L; return x;"));
assertEquals(2L * -9223372036854775807L, exec("long x = 2; x *= -9223372036854775807L; return x;")); assertEquals(2L * -9223372036854775807L, exec("long x = 2; x *= -9223372036854775807L; return x;"));
} }
public void testAssignmentDivisionOverflow() { public void testAssignmentDivisionOverflow() {
// byte // byte
assertEquals((byte) (-128 / -1), exec("byte x = (byte) -128; x /= -1; return x;")); assertEquals((byte) (-128 / -1), exec("byte x = (byte) -128; x /= -1; return x;"));
// short // short
assertEquals((short) (-32768 / -1), exec("short x = (short) -32768; x /= -1; return x;")); assertEquals((short) (-32768 / -1), exec("short x = (short) -32768; x /= -1; return x;"));
// cannot happen for char: unsigned // cannot happen for char: unsigned
// int // int
assertEquals((-2147483647 - 1) / -1, exec("int x = -2147483647 - 1; x /= -1; return x;")); assertEquals((-2147483647 - 1) / -1, exec("int x = -2147483647 - 1; x /= -1; return x;"));
// long // long
assertEquals((-9223372036854775807L - 1L) / -1L, exec("long x = -9223372036854775807L - 1L; x /=-1L; return x;")); assertEquals((-9223372036854775807L - 1L) / -1L, exec("long x = -9223372036854775807L - 1L; x /=-1L; return x;"));
} }
public void testIncrementOverFlow() throws Exception { public void testIncrementOverFlow() throws Exception {
// byte // byte
assertEquals((byte) 128, exec("byte x = 127; ++x; return x;")); assertEquals((byte) 128, exec("byte x = 127; ++x; return x;"));
assertEquals((byte) 128, exec("byte x = 127; x++; return x;")); assertEquals((byte) 128, exec("byte x = 127; x++; return x;"));
assertEquals((byte) -129, exec("byte x = (byte) -128; --x; return x;")); assertEquals((byte) -129, exec("byte x = (byte) -128; --x; return x;"));
assertEquals((byte) -129, exec("byte x = (byte) -128; x--; return x;")); assertEquals((byte) -129, exec("byte x = (byte) -128; x--; return x;"));
// short // short
assertEquals((short) 32768, exec("short x = 32767; ++x; return x;")); assertEquals((short) 32768, exec("short x = 32767; ++x; return x;"));
assertEquals((short) 32768, exec("short x = 32767; x++; return x;")); assertEquals((short) 32768, exec("short x = 32767; x++; return x;"));
assertEquals((short) -32769, exec("short x = (short) -32768; --x; return x;")); assertEquals((short) -32769, exec("short x = (short) -32768; --x; return x;"));
assertEquals((short) -32769, exec("short x = (short) -32768; x--; return x;")); assertEquals((short) -32769, exec("short x = (short) -32768; x--; return x;"));
// char // char
assertEquals((char) 65536, exec("char x = 65535; ++x; return x;")); assertEquals((char) 65536, exec("char x = 65535; ++x; return x;"));
assertEquals((char) 65536, exec("char x = 65535; x++; return x;")); assertEquals((char) 65536, exec("char x = 65535; x++; return x;"));
assertEquals((char) -1, exec("char x = (char) 0; --x; return x;")); assertEquals((char) -1, exec("char x = (char) 0; --x; return x;"));
assertEquals((char) -1, exec("char x = (char) 0; x--; return x;")); assertEquals((char) -1, exec("char x = (char) 0; x--; return x;"));
// int // int
assertEquals(2147483647 + 1, exec("int x = 2147483647; ++x; return x;")); assertEquals(2147483647 + 1, exec("int x = 2147483647; ++x; return x;"));
assertEquals(2147483647 + 1, exec("int x = 2147483647; x++; return x;")); assertEquals(2147483647 + 1, exec("int x = 2147483647; x++; return x;"));
assertEquals(-2147483648 - 1, exec("int x = (int) -2147483648L; --x; return x;")); assertEquals(-2147483648 - 1, exec("int x = (int) -2147483648L; --x; return x;"));
assertEquals(-2147483648 - 1, exec("int x = (int) -2147483648L; x--; return x;")); assertEquals(-2147483648 - 1, exec("int x = (int) -2147483648L; x--; return x;"));
// long // long
assertEquals(9223372036854775807L + 1L, exec("long x = 9223372036854775807L; ++x; return x;")); assertEquals(9223372036854775807L + 1L, exec("long x = 9223372036854775807L; ++x; return x;"));
assertEquals(9223372036854775807L + 1L, exec("long x = 9223372036854775807L; x++; return x;")); assertEquals(9223372036854775807L + 1L, exec("long x = 9223372036854775807L; x++; return x;"));
assertEquals(-9223372036854775807L - 1L - 1L, exec("long x = -9223372036854775807L - 1L; --x; return x;")); assertEquals(-9223372036854775807L - 1L - 1L, exec("long x = -9223372036854775807L - 1L; --x; return x;"));
assertEquals(-9223372036854775807L - 1L - 1L, exec("long x = -9223372036854775807L - 1L; x--; return x;")); assertEquals(-9223372036854775807L - 1L - 1L, exec("long x = -9223372036854775807L - 1L; x--; return x;"));
} }
public void testAddition() throws Exception { public void testAddition() throws Exception {
assertEquals(2147483647 + 2147483647, exec("int x = 2147483647; int y = 2147483647; return x + y;")); assertEquals(2147483647 + 2147483647, exec("int x = 2147483647; int y = 2147483647; return x + y;"));
assertEquals(9223372036854775807L + 9223372036854775807L, exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x + y;")); assertEquals(9223372036854775807L + 9223372036854775807L, exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x + y;"));
} }
public void testAdditionConst() throws Exception { public void testAdditionConst() throws Exception {
assertEquals(2147483647 + 2147483647, exec("return 2147483647 + 2147483647;")); assertEquals(2147483647 + 2147483647, exec("return 2147483647 + 2147483647;"));
assertEquals(9223372036854775807L + 9223372036854775807L, exec("return 9223372036854775807L + 9223372036854775807L;")); assertEquals(9223372036854775807L + 9223372036854775807L, exec("return 9223372036854775807L + 9223372036854775807L;"));
} }
public void testSubtraction() throws Exception { public void testSubtraction() throws Exception {
assertEquals(-10 - 2147483647, exec("int x = -10; int y = 2147483647; return x - y;")); assertEquals(-10 - 2147483647, exec("int x = -10; int y = 2147483647; return x - y;"));
assertEquals(-10L - 9223372036854775807L, exec("long x = -10L; long y = 9223372036854775807L; return x - y;")); assertEquals(-10L - 9223372036854775807L, exec("long x = -10L; long y = 9223372036854775807L; return x - y;"));
} }
public void testSubtractionConst() throws Exception { public void testSubtractionConst() throws Exception {
assertEquals(-10 - 2147483647, exec("return -10 - 2147483647;")); assertEquals(-10 - 2147483647, exec("return -10 - 2147483647;"));
assertEquals(-10L - 9223372036854775807L, exec("return -10L - 9223372036854775807L;")); assertEquals(-10L - 9223372036854775807L, exec("return -10L - 9223372036854775807L;"));
} }
public void testMultiplication() throws Exception { public void testMultiplication() throws Exception {
assertEquals(2147483647 * 2147483647, exec("int x = 2147483647; int y = 2147483647; return x * y;")); assertEquals(2147483647 * 2147483647, exec("int x = 2147483647; int y = 2147483647; return x * y;"));
assertEquals(9223372036854775807L * 9223372036854775807L, exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x * y;")); assertEquals(9223372036854775807L * 9223372036854775807L, exec("long x = 9223372036854775807L; long y = 9223372036854775807L; return x * y;"));
} }
public void testMultiplicationConst() throws Exception { public void testMultiplicationConst() throws Exception {
assertEquals(2147483647 * 2147483647, exec("return 2147483647 * 2147483647;")); assertEquals(2147483647 * 2147483647, exec("return 2147483647 * 2147483647;"));
assertEquals(9223372036854775807L * 9223372036854775807L, exec("return 9223372036854775807L * 9223372036854775807L;")); assertEquals(9223372036854775807L * 9223372036854775807L, exec("return 9223372036854775807L * 9223372036854775807L;"));
@ -175,17 +175,17 @@ public class IntegerOverflowEnabledTests extends ScriptTestCase {
assertEquals((-2147483647 - 1) / -1, exec("int x = -2147483648; int y = -1; return x / y;")); assertEquals((-2147483647 - 1) / -1, exec("int x = -2147483648; int y = -1; return x / y;"));
assertEquals((-9223372036854775807L - 1L) / -1L, exec("long x = -9223372036854775808L; long y = -1L; return x / y;")); assertEquals((-9223372036854775807L - 1L) / -1L, exec("long x = -9223372036854775808L; long y = -1L; return x / y;"));
} }
public void testDivisionConst() throws Exception { public void testDivisionConst() throws Exception {
assertEquals((-2147483647 - 1) / -1, exec("return (-2147483648) / -1;")); assertEquals((-2147483647 - 1) / -1, exec("return (-2147483648) / -1;"));
assertEquals((-9223372036854775807L - 1L) / -1L, exec("return (-9223372036854775808L) / -1L;")); assertEquals((-9223372036854775807L - 1L) / -1L, exec("return (-9223372036854775808L) / -1L;"));
} }
public void testNegationOverflow() throws Exception { public void testNegationOverflow() throws Exception {
assertEquals(-(-2147483647 - 1), exec("int x = -2147483648; x = -x; return x;")); assertEquals(-(-2147483647 - 1), exec("int x = -2147483648; x = -x; return x;"));
assertEquals(-(-9223372036854775807L - 1L), exec("long x = -9223372036854775808L; x = -x; return x;")); assertEquals(-(-9223372036854775807L - 1L), exec("long x = -9223372036854775808L; x = -x; return x;"));
} }
public void testNegationOverflowConst() throws Exception { public void testNegationOverflowConst() throws Exception {
assertEquals(-(-2147483647 - 1), exec("int x = -(-2147483648); return x;")); assertEquals(-(-2147483647 - 1), exec("int x = -(-2147483648); return x;"));
assertEquals(-(-9223372036854775807L - 1L), exec("long x = -(-9223372036854775808L); return x;")); assertEquals(-(-9223372036854775807L - 1L), exec("long x = -(-9223372036854775808L); return x;"));

View File

@ -48,7 +48,7 @@ public abstract class ScriptTestCase extends ESTestCase {
/** Compiles and returns the result of {@code script} with access to {@code vars} */ /** Compiles and returns the result of {@code script} with access to {@code vars} */
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
return exec(script, vars, Collections.singletonMap(PlanAScriptEngineService.NUMERIC_OVERFLOW, Boolean.toString(random().nextBoolean()))); return exec(script, vars, Collections.singletonMap(CompilerSettings.NUMERIC_OVERFLOW, Boolean.toString(random().nextBoolean())));
} }
/** Compiles and returns the result of {@code script} with access to {@code vars} and compile-time parameters */ /** Compiles and returns the result of {@code script} with access to {@code vars} and compile-time parameters */

View File

@ -19,6 +19,7 @@
package org.elasticsearch.plan.a; package org.elasticsearch.plan.a;
import java.text.ParseException;
import java.util.Collections; import java.util.Collections;
public class WhenThingsGoWrongTests extends ScriptTestCase { public class WhenThingsGoWrongTests extends ScriptTestCase {
@ -40,7 +41,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
fail("should have hit cce"); fail("should have hit cce");
} catch (ClassCastException expected) {} } catch (ClassCastException expected) {}
} }
public void testBogusParameter() { public void testBogusParameter() {
try { try {
exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue")); exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue"));
@ -49,4 +50,83 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
assertTrue(expected.getMessage().contains("Unrecognized compile-time parameter")); assertTrue(expected.getMessage().contains("Unrecognized compile-time parameter"));
} }
} }
public void testInfiniteLoops() {
try {
exec("boolean x = true; while (x) {}");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("while (true) {int y = 5}");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("while (true) { boolean x = true; while (x) {} }");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("while (true) { boolean x = false; while (x) {} }");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("boolean x = true; for (;x;) {}");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("for (;;) {int x = 5}");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("def x = true; do {int y = 5;} while (x)");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
try {
exec("try { int x } catch (PlanAError error) {}");
fail("should have hit ParseException");
} catch (RuntimeException expected) {
assertTrue(expected.getMessage().contains(
"unexpected token ['PlanAError'] was expecting one of [TYPE]."));
}
}
public void testLoopLimits() {
exec("for (int x = 0; x < 9999; ++x) {}");
try {
exec("for (int x = 0; x < 10000; ++x) {}");
fail("should have hit PlanAError");
} catch (PlanAError expected) {
assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached."));
}
}
} }