diff --git a/pom.xml b/pom.xml index 64e28e3a..9dff8c8e 100755 --- a/pom.xml +++ b/pom.xml @@ -11,14 +11,14 @@ aviator 5.4.2-SNAPSHOT aviator - A lightweight, high performance expression evaluator for java + A high performance scripting language hosted on the JVM https://github.com/killme2008/aviator 2010 dennis zhuang - http://fnil.net/ + https://github.com/killme2008 8 @@ -51,7 +51,7 @@ org.springframework spring-context - 4.3.16.RELEASE + 5.3.36 provided @@ -118,8 +118,8 @@ maven-compiler-plugin 3.8.1 - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java index 2dddd77b..40fa7bcd 100644 --- a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java +++ b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java @@ -404,10 +404,11 @@ private Map loadScript0(final String abPath) throws IOException @SuppressWarnings("unchecked") private Map executeModule(final Expression exp, final String abPath) { final Env exports = new Env(); + exports.configure(this, exp, -1); final Map module = exp.newEnv("exports", exports, "path", abPath); Map env = exp.newEnv("__MODULE__", module, "exports", exports); - exp.execute(env); - exports.configure(this, exp); + ((BaseExpression) exp).execute(env, false); + return (Map) module.get("exports"); } diff --git a/src/main/java/com/googlecode/aviator/BaseExpression.java b/src/main/java/com/googlecode/aviator/BaseExpression.java index f664a948..fc5cba1b 100644 --- a/src/main/java/com/googlecode/aviator/BaseExpression.java +++ b/src/main/java/com/googlecode/aviator/BaseExpression.java @@ -24,10 +24,12 @@ import com.googlecode.aviator.parser.VariableMeta; import com.googlecode.aviator.runtime.FunctionArgument; import com.googlecode.aviator.runtime.LambdaFunctionBootstrap; +import com.googlecode.aviator.runtime.RuntimeUtils; import com.googlecode.aviator.runtime.function.LambdaFunction; import com.googlecode.aviator.utils.Constants; import com.googlecode.aviator.utils.Env; import com.googlecode.aviator.utils.Reflector; +import com.googlecode.aviator.utils.Utils; /** * Base expression @@ -241,10 +243,14 @@ public Map newEnv(final Object... args) { @Override public Object execute(Map map) { + return this.execute(map, true); + } + + protected Object execute(Map map, boolean checkExecutionTimeout) { if (map == null) { map = Collections.emptyMap(); } - Env env = genTopEnv(map); + Env env = genTopEnv(map, checkExecutionTimeout); EnvProcessor envProcessor = this.instance.getEnvProcessor(); if (envProcessor != null) { envProcessor.beforeExecute(env, this); @@ -327,23 +333,25 @@ public List getVariableNames() { return this.varNames; } - protected Env newEnv(final Map map, final boolean direct) { + protected Env newEnv(final Map map, final boolean direct, + boolean checkExecutionTimeout) { Env env; if (direct) { env = new Env(map, map == Collections.EMPTY_MAP ? new HashMap() : map); } else { env = new Env(map); } - env.configure(this.instance, this); + env.configure(this.instance, this, getExecutionStartNs(checkExecutionTimeout)); return env; } - protected Env genTopEnv(final Map map) { + protected Env genTopEnv(final Map map, boolean checkExecutionTimeout) { if (map instanceof Env) { - ((Env) map).configure(this.instance, this); + ((Env) map).configure(this.instance, this, getExecutionStartNs(checkExecutionTimeout)); } Env env = - newEnv(map, this.instance.getOptionValue(Options.USE_USER_ENV_AS_TOP_ENV_DIRECTLY).bool); + newEnv(map, this.instance.getOptionValue(Options.USE_USER_ENV_AS_TOP_ENV_DIRECTLY).bool, + checkExecutionTimeout); if (this.compileEnv != null && !this.compileEnv.isEmpty()) { env.putAll(this.compileEnv); @@ -354,8 +362,16 @@ protected Env genTopEnv(final Map map) { return env; } + private long getExecutionStartNs(boolean checkExecutionTimeout) { + long startNs = -1; + if (checkExecutionTimeout && this.instance.getOptionValue(Options.EVAL_TIMEOUT_MS).number > 0) { + startNs = Utils.currentTimeNanos(); + } + return startNs; + } + protected Env newEnv(final Map map) { - return newEnv(map, false); + return newEnv(map, false, true); } public Map getLambdaBootstraps() { diff --git a/src/main/java/com/googlecode/aviator/Options.java b/src/main/java/com/googlecode/aviator/Options.java index 3e81201c..b1111d88 100644 --- a/src/main/java/com/googlecode/aviator/Options.java +++ b/src/main/java/com/googlecode/aviator/Options.java @@ -2,6 +2,7 @@ import java.math.MathContext; import java.util.Set; +import java.util.concurrent.TimeUnit; import com.googlecode.aviator.utils.Utils; @@ -117,9 +118,30 @@ public enum Options { /** * Whether the compiled expression is serializable. If true, the compiled expression will - * implement {@link #jva.io.Serializable} and can be encoded/decoded by java serialization. + * implement {@link jva.io.Serializable} and can be encoded/decoded by java serialization. */ - SERIALIZABLE; + SERIALIZABLE, + + /** + * + * The expression execution timeout value in milliseconds. If the execution time exceeds this + * value, it will throw a {@link com.googlecode.aviator.exception.TimeoutException}. A value of + * zero or less indicates no timeout limitation, the default value is zero (no limitation).
+ *
+ * Note: this limitation is not strict and may hurt performance, it is only checked before: + *
    + *
  • Operator evaluating, such as add, sub etc.
  • + *
  • Jumping in branches, such as loop and conditional clauses etc.
  • + *
  • Function invocation
  • + *
+ * + * So if the expression doesn't contains these clauses or trapped into a function invocation, the + * behavior may be not expected. Try its best, but no promises. + * + * @since 5.4.2 + * + */ + EVAL_TIMEOUT_MS; /** @@ -135,6 +157,8 @@ public static class Value { public Set featureSet; public Set> classes; public EvalMode evalMode; + // Temporal cached number value to avoid expensive calculation. + public long cachedNumber; public Value(final EvalMode evalMode) { super(); @@ -200,6 +224,7 @@ public Object intoObject(final Value val) { return val.bool; case MAX_LOOP_COUNT: case OPTIMIZE_LEVEL: + case EVAL_TIMEOUT_MS: return val.number; case FEATURE_SET: return val.featureSet; @@ -243,6 +268,14 @@ public Value intoValue(final Object val) { return COMPILE_VALUE; } } + case EVAL_TIMEOUT_MS: { + Value value = new Value(((Number) val).intValue()); + // Cached the converted result. + if (value.number > 0) { + value.cachedNumber = TimeUnit.NANOSECONDS.convert(value.number, TimeUnit.MILLISECONDS); + } + return value; + } case MAX_LOOP_COUNT: return new Value(((Number) val).intValue()); case ALLOWED_CLASS_SET: @@ -278,6 +311,7 @@ public boolean isValidValue(final Object val) { final int level = ((Integer) val).intValue(); return val instanceof Integer && (level == AviatorEvaluator.EVAL || level == AviatorEvaluator.COMPILE); + case EVAL_TIMEOUT_MS: case MAX_LOOP_COUNT: return val instanceof Long || val instanceof Integer; case MATH_CONTEXT: @@ -356,6 +390,8 @@ public Value getDefaultValueObject() { return NULL_CLASS_SET; case EVAL_MODE: return getDefaultEvalMode(); + case EVAL_TIMEOUT_MS: + return ZERO_VALUE; } return null; } diff --git a/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java index 0060fd89..b8c93ffb 100644 --- a/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java @@ -373,6 +373,10 @@ public Expression getResult(final boolean unboxObject) { if (SymbolTable.isReservedKeyword((Variable) token)) { continue; } + // Ignore class name or package name + if (token.getMeta(Constants.USE_CLASS_PKG, false)) { + continue; + } String varName = token.getLexeme(); VariableMeta meta = variables.get(varName); diff --git a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java index b90a8c26..3af29665 100644 --- a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java @@ -453,6 +453,7 @@ private void visitRightBranch(final Token lookhead, final int ints, public void onTernaryBoolean(final Token lookhead) { loadEnv(); visitLineNumber(lookhead); + checkExecutionTimeout(); visitBoolean(); Label l0 = makeLabel(); Label l1 = makeLabel(); @@ -472,6 +473,7 @@ private void pushLabel1(final Label l1) { @Override public void onTernaryLeft(final Token lookhead) { + checkExecutionTimeout(); this.mv.visitJumpInsn(GOTO, peekLabel1()); visitLabel(popLabel0()); visitLineNumber(lookhead); @@ -484,6 +486,7 @@ private Label peekLabel1() { @Override public void onTernaryRight(final Token lookhead) { + checkExecutionTimeout(); visitLabel(popLabel1()); visitLineNumber(lookhead); this.popOperand(); // pop one boolean @@ -570,6 +573,7 @@ public void onNeq(final Token lookhead) { private void doCompareAndJump(final Token lookhead, final int ints, final OperatorType opType) { visitLineNumber(lookhead); + this.checkExecutionTimeout(); loadEnv(); visitCompare(ints, opType); this.popOperand(); @@ -641,6 +645,7 @@ public void onNot(final Token lookhead) { private void visitBinOperator(final Token token, final OperatorType opType, final String methodName) { visitLineNumber(token); + this.checkExecutionTimeout(); if (!OperationRuntime.hasRuntimeContext(this.compileEnv, opType)) { // swap arguments for regular-expression match operator. if (opType == OperatorType.MATCH) { @@ -1288,6 +1293,9 @@ public void genNewLambdaCode(final LambdaFunctionBootstrap bootstrap) { @Override public void onMethodName(final Token lookhead) { + + checkExecutionTimeout(); + String outtterMethodName = "lambda"; if (lookhead.getType() != TokenType.Delegate) { outtterMethodName = lookhead.getLexeme(); @@ -1318,6 +1326,13 @@ public void onMethodName(final Token lookhead) { this.methodMetaDataStack.push(new MethodMetaData(lookhead, outtterMethodName)); } + private void checkExecutionTimeout() { + loadEnv(); + this.mv.visitMethodInsn(INVOKESTATIC, RUNTIME_UTILS, "checkExecutionTimedOut", + "(Ljava/util/Map;)V"); + this.popOperand(); + } + private void loadAviatorFunction(final String outterMethodName, final String innerMethodName) { Map name2Index = this.labelNameIndexMap.get(this.currentLabel); // Is it stored in local? diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/IR.java b/src/main/java/com/googlecode/aviator/code/interpreter/IR.java index 82585b25..67cc5f4c 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/IR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/IR.java @@ -10,4 +10,13 @@ */ public interface IR extends Serializable { void eval(InterpretContext context); + + /** + * Returns true when the IR execution cost may be expensive + * + * @return + */ + default boolean mayBeCost() { + return false; + } } diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java index 040cc2eb..af295de0 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java @@ -2,7 +2,9 @@ import java.util.ArrayDeque; import java.util.List; +import java.util.concurrent.TimeUnit; import com.googlecode.aviator.InterpretExpression; +import com.googlecode.aviator.exception.TimeoutException; import com.googlecode.aviator.lexer.token.Token; import com.googlecode.aviator.parser.VariableMeta; import com.googlecode.aviator.runtime.RuntimeUtils; @@ -138,6 +140,9 @@ public void dispatch(final boolean next) { } if (this.pc != null) { + if (this.pc.mayBeCost()) { + RuntimeUtils.checkExecutionTimedOut(env); + } if (this.trace) { RuntimeUtils.printlnTrace(this.env, " " + this.pc + " " + descOperandsStack()); } diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchIfIR.java b/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchIfIR.java index 52a0c5e9..702ada30 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchIfIR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchIfIR.java @@ -41,6 +41,13 @@ public void eval(final InterpretContext context) { } } + + + @Override + public boolean mayBeCost() { + return true; + } + @Override public String toString() { return "branch_if " + this.pc + " [" + this.label + "] " + this.sourceInfo; diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchUnlessIR.java b/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchUnlessIR.java index 05398384..cd690572 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchUnlessIR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/ir/BranchUnlessIR.java @@ -42,6 +42,11 @@ public void eval(final InterpretContext context) { } } + @Override + public boolean mayBeCost() { + return true; + } + @Override public String toString() { return "branch_unless " + this.pc + " [" + this.label + "] " + this.sourceInfo; diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/ir/GotoIR.java b/src/main/java/com/googlecode/aviator/code/interpreter/ir/GotoIR.java index 6663ff8c..49a9b388 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/ir/GotoIR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/ir/GotoIR.java @@ -30,6 +30,11 @@ public Label getLabel() { return this.label; } + @Override + public boolean mayBeCost() { + return true; + } + @Override public void eval(final InterpretContext context) { context.jumpTo(this.pc); diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/ir/OperatorIR.java b/src/main/java/com/googlecode/aviator/code/interpreter/ir/OperatorIR.java index bf483841..65f445fe 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/ir/OperatorIR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/ir/OperatorIR.java @@ -110,7 +110,10 @@ public void eval(final InterpretContext context) { context.dispatch(); } - + @Override + public boolean mayBeCost() { + return true; + } public OperatorType getOp() { return this.op; diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/ir/SendIR.java b/src/main/java/com/googlecode/aviator/code/interpreter/ir/SendIR.java index 78496c4f..04d9ce3b 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/ir/SendIR.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/ir/SendIR.java @@ -138,6 +138,11 @@ public void eval(final InterpretContext context) { context.dispatch(); } + @Override + public boolean mayBeCost() { + return true; + } + @Override public String toString() { return "send " + (this.name == null ? "" : this.name) + ", " + this.arity + ", " diff --git a/src/main/java/com/googlecode/aviator/exception/TimeoutException.java b/src/main/java/com/googlecode/aviator/exception/TimeoutException.java new file mode 100644 index 00000000..7cdebbc0 --- /dev/null +++ b/src/main/java/com/googlecode/aviator/exception/TimeoutException.java @@ -0,0 +1,29 @@ +package com.googlecode.aviator.exception; + +/** + * The expression execution is timed out. + * + * @author dennis(killme2008@gmail.com) + * @since 5.4.2 + */ +public class TimeoutException extends ExpressionRuntimeException { + + private static final long serialVersionUID = -3749680912179160158L; + + public TimeoutException() { + super(); + } + + public TimeoutException(final String message, final Throwable cause) { + super(message, cause); + } + + public TimeoutException(final String message) { + super(message); + } + + public TimeoutException(final Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java index 62b37d8e..fa6e28dd 100644 --- a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java +++ b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java @@ -1603,7 +1603,7 @@ private void className() { wildcard(); } else { checkVariableName(this.lookhead); - getCodeGenerator().onConstant(this.lookhead); + getCodeGenerator().onConstant(this.lookhead.withMeta(Constants.USE_CLASS_PKG, true)); } move(true); } diff --git a/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java index f2895c9a..18f64415 100644 --- a/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java +++ b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java @@ -3,9 +3,11 @@ import java.io.IOException; import java.math.MathContext; import java.util.Map; +import java.util.concurrent.TimeUnit; import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.Options; +import com.googlecode.aviator.exception.TimeoutException; import com.googlecode.aviator.runtime.function.LambdaFunction; import com.googlecode.aviator.runtime.function.internal.UnpackingArgsFunction; import com.googlecode.aviator.runtime.type.AviatorFunction; @@ -19,6 +21,7 @@ import com.googlecode.aviator.runtime.type.seq.LimitedSequence; import com.googlecode.aviator.runtime.type.seq.MapSequence; import com.googlecode.aviator.utils.Env; +import com.googlecode.aviator.utils.Utils; /** * Runtime utils @@ -101,6 +104,22 @@ public static Sequence seq(final Object o, final Map env) { return seq; } + public static void checkExecutionTimedOut(final Map env) { + if (env instanceof Env) { + long startNs = ((Env) env).getStartNs(); + if (startNs > 0) { + long execTimeoutNs = getEvalTimeoutNs(env); + if (execTimeoutNs > 0) { + if (Utils.currentTimeNanos() - startNs > execTimeoutNs) { + throw new TimeoutException("Expression execution timed out, exceeded: " + + getInstance(env).getOptionValue(Options.EVAL_TIMEOUT_MS).number + " ms"); + } + } + } + } + + } + /** * Ensure the object is not null, cast null into AviatorNil. * @@ -131,6 +150,11 @@ public static final boolean isTracedEval(final Map env) { return getInstance(env).getOptionValue(Options.TRACE_EVAL).bool; } + // Returns the eval timeout value in nanoseconds + public static final long getEvalTimeoutNs(final Map env) { + return getInstance(env).getOptionValue(Options.EVAL_TIMEOUT_MS).cachedNumber; + } + public static AviatorFunction getFunction(final Object object, final Map env) { if (object instanceof AviatorFunction) { return (AviatorFunction) object; diff --git a/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java b/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java index 85136b70..7e1ddcf2 100644 --- a/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java +++ b/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java @@ -241,9 +241,9 @@ protected Map newEnv(final Map parentEnv, Env env = null; if (!this.inheritEnv) { final Env contextEnv = new Env(parentEnv, this.context); - contextEnv.configure(this.context.getInstance(), this.expression); + contextEnv.configure(this.context.getInstance(), this.expression, this.context.getStartNs()); env = new Env(contextEnv); - env.configure(this.context.getInstance(), this.expression); + env.configure(this.context.getInstance(), this.expression, this.context.getStartNs()); } else { // assert (parentEnv == this.context); env = (Env) parentEnv; diff --git a/src/main/java/com/googlecode/aviator/runtime/function/system/BigIntFunction.java b/src/main/java/com/googlecode/aviator/runtime/function/system/BigIntFunction.java index e3b5b015..43112c2a 100644 --- a/src/main/java/com/googlecode/aviator/runtime/function/system/BigIntFunction.java +++ b/src/main/java/com/googlecode/aviator/runtime/function/system/BigIntFunction.java @@ -36,7 +36,9 @@ public AviatorObject call(final Map env, final AviatorObject arg } else if (obj instanceof Character) { return AviatorBigInt.valueOf(new BigInteger(String.valueOf(obj))); } else { - throw new ClassCastException("Could not cast " + (obj != null ? obj.getClass().getName() : "null") + " to bigint, AviatorObject is " + arg1); + throw new ClassCastException( + "Could not cast " + (obj != null ? obj.getClass().getName() : "null") + + " to bigint, AviatorObject is " + arg1); } case String: return AviatorBigInt.valueOf(new BigInteger((String) arg1.getValue(env))); diff --git a/src/main/java/com/googlecode/aviator/runtime/function/system/DoubleFunction.java b/src/main/java/com/googlecode/aviator/runtime/function/system/DoubleFunction.java index 0a44f73c..0e592a7e 100644 --- a/src/main/java/com/googlecode/aviator/runtime/function/system/DoubleFunction.java +++ b/src/main/java/com/googlecode/aviator/runtime/function/system/DoubleFunction.java @@ -34,7 +34,9 @@ public AviatorObject call(Map env, AviatorObject arg1) { } else if (obj instanceof Character) { return new AviatorDouble(Double.parseDouble(String.valueOf(obj))); } else { - throw new ClassCastException("Could not cast " + (obj != null ? obj.getClass().getName() : "null") + " to double, AviatorObject is" + arg1 ); + throw new ClassCastException( + "Could not cast " + (obj != null ? obj.getClass().getName() : "null") + + " to double, AviatorObject is" + arg1); } case String: return new AviatorDouble(Double.parseDouble((String) arg1.getValue(env))); diff --git a/src/main/java/com/googlecode/aviator/runtime/function/system/LongFunction.java b/src/main/java/com/googlecode/aviator/runtime/function/system/LongFunction.java index 07ff6fd6..a9425ff0 100644 --- a/src/main/java/com/googlecode/aviator/runtime/function/system/LongFunction.java +++ b/src/main/java/com/googlecode/aviator/runtime/function/system/LongFunction.java @@ -34,7 +34,9 @@ public AviatorObject call(Map env, AviatorObject arg1) { } else if (obj instanceof Character) { return AviatorLong.valueOf(Long.valueOf(String.valueOf(obj))); } else { - throw new ClassCastException("Could not cast " + (obj != null ? obj.getClass().getName() : "null") + " to long, AviatorObject is " + arg1); + throw new ClassCastException( + "Could not cast " + (obj != null ? obj.getClass().getName() : "null") + + " to long, AviatorObject is " + arg1); } case String: return AviatorLong.valueOf(Long.valueOf((String) arg1.getValue(env))); diff --git a/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java b/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java index f8828455..cf4ce6d7 100644 --- a/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java +++ b/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java @@ -128,7 +128,6 @@ public static AviatorObject eval(final AviatorObject left, final Map env, final OperatorType opType) { - AviatorFunction func = RuntimeUtils.getInstance(env).getOpFunction(opType); AviatorObject ret = eval0(left, right, env, opType, func); if (RuntimeUtils.isTracedEval(env)) { diff --git a/src/main/java/com/googlecode/aviator/utils/Constants.java b/src/main/java/com/googlecode/aviator/utils/Constants.java index 6b55f67b..5e7f00fe 100644 --- a/src/main/java/com/googlecode/aviator/utils/Constants.java +++ b/src/main/java/com/googlecode/aviator/utils/Constants.java @@ -44,6 +44,7 @@ public class Constants { // Whether string has interpolation point. public static final String INTER_META = "hasInterpolation"; public static final String UNPACK_ARGS = "unpackingArgs"; + public static final String USE_CLASS_PKG = "useClassOrPkg"; public static final Pattern SPLIT_PAT = Pattern.compile("\\."); // runtime metadata keys diff --git a/src/main/java/com/googlecode/aviator/utils/Env.java b/src/main/java/com/googlecode/aviator/utils/Env.java index d21342d0..3a551fcb 100644 --- a/src/main/java/com/googlecode/aviator/utils/Env.java +++ b/src/main/java/com/googlecode/aviator/utils/Env.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.Expression; import com.googlecode.aviator.Feature; @@ -75,6 +74,10 @@ public class Env implements Map, Serializable { public static final Map EMPTY_ENV = Collections.emptyMap(); + // The execution start timestamp in nanoseconds. + private transient long startNs = -1; + + /** * Constructs an env instance with empty state. */ @@ -100,6 +103,10 @@ public void setmOverrides(final Map mOverrides) { this.mOverrides = mOverrides; } + public long getStartNs() { + return startNs; + } + public List getImportedSymbols() { return this.importedSymbols; } @@ -148,9 +155,18 @@ public void setInstance(final AviatorEvaluatorInstance instance) { this.instance = instance; } - public void configure(final AviatorEvaluatorInstance instance, final Expression exp) { + // Configure the env. + public void configure(final AviatorEvaluatorInstance instance, final Expression exp, + long startNs) { this.instance = instance; this.expression = exp; + setStartNs(startNs); + } + + private void setStartNs(long startNs) { + if (this.startNs == -1 && startNs > 0) { + this.startNs = startNs; + } } private String findSymbol(final String name) throws ClassNotFoundException { diff --git a/src/main/java/com/googlecode/aviator/utils/Utils.java b/src/main/java/com/googlecode/aviator/utils/Utils.java index a714a1d4..97ceef1e 100644 --- a/src/main/java/com/googlecode/aviator/utils/Utils.java +++ b/src/main/java/com/googlecode/aviator/utils/Utils.java @@ -29,6 +29,10 @@ private Utils() { } + public static long currentTimeNanos() { + return System.nanoTime(); + } + private static final ThreadLocal MESSAGE_DIGEST_LOCAL = new ThreadLocal() { diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java index 7c310a77..2512e710 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java @@ -23,12 +23,22 @@ public void testMaxLoopCount() { super.testMaxLoopCount(); } + @Test + public void testEvalTimeout() { + // ignore + } + @Override @Test public void testIssue476() { // ignore } + @Test + public void testEvalTimeoutAndTryAgain() throws Exception { + // ignore + } + @Override @Test public void testClassAllowList() { diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java new file mode 100644 index 00000000..53eae37b --- /dev/null +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java @@ -0,0 +1,20 @@ +package com.googlecode.aviator; + +import org.junit.Before; +import org.junit.Test; + +public class AviatorEvaluatorInstanceInterpreteUnitTest extends AviatorEvaluatorInstanceUnitTest { + + + @Override + @Before + public void setup() { + super.setup(); + this.instance.setOption(Options.EVAL_MODE, EvalMode.INTERPRETER); + } + + @Test + public void testTraceEval() throws Exception { + // ignore + } +} diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java index baae2dc9..d656c61d 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java @@ -38,6 +38,7 @@ import com.googlecode.aviator.exception.CompileExpressionErrorException; import com.googlecode.aviator.exception.ExpressionRuntimeException; import com.googlecode.aviator.exception.ExpressionSyntaxErrorException; +import com.googlecode.aviator.exception.TimeoutException; import com.googlecode.aviator.exception.UnsupportedFeatureException; import com.googlecode.aviator.lexer.token.OperatorType; import com.googlecode.aviator.runtime.function.AbstractFunction; @@ -57,6 +58,7 @@ public class AviatorEvaluatorInstanceUnitTest { @Before public void setup() { this.instance = AviatorEvaluator.newInstance(); + this.instance.setOption(Options.EVAL_TIMEOUT_MS, 100); } @Test @@ -67,6 +69,26 @@ public void testIssue476() { assertEquals(expr.getVariableFullNames(), Arrays.asList("x")); } + @Test(expected = TimeoutException.class) + public void testEvalTimeout() { + this.instance.execute("while(true) { }"); + } + + @Test + public void testEvalTimeoutAndTryAgain() throws Exception { + Expression exp = this.instance.compile("Thread.sleep(120); a + 1"); + try { + exp.execute(exp.newEnv("a", 2)); + } catch (TimeoutException e) { + assertTrue(e.getMessage().contains("Expression execution timed out, exceeded: 100 ms")); + // ignore + } + this.instance.setOption(Options.EVAL_TIMEOUT_MS, 200); + assertEquals(exp.execute(exp.newEnv("a", 2)), 3); + Thread.sleep(500); + assertEquals(exp.execute(exp.newEnv("a", 2)), 3); + } + @SuppressWarnings("unchecked") @Test public void testIssue466() { @@ -680,7 +702,6 @@ public void testTraceEval() throws Exception { this.instance.setOption(Options.TRACE_EVAL, false); this.instance.setTraceOutputStream(System.out); } - } @Test(expected = CompileExpressionErrorException.class) diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java index 31d35b28..ce673dc6 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java @@ -42,6 +42,13 @@ public void testCompileWithoutCache() { assertEquals(4, exp2.execute(null)); } + @Test + public void testIssue597() { + String s = "use java.lang.Thread;\n" + "Thread.sleep(2000);\n" + "return 1 > 0;"; + + Expression exp = AviatorEvaluator.compile(s); + assertTrue(exp.getVariableNames().isEmpty()); + } @Test public void testNewEnv() { diff --git a/src/test/java/com/googlecode/aviator/runtime/function/system/BigIntFunctionUnitTest.java b/src/test/java/com/googlecode/aviator/runtime/function/system/BigIntFunctionUnitTest.java index ecf2e382..bab7b7d2 100644 --- a/src/test/java/com/googlecode/aviator/runtime/function/system/BigIntFunctionUnitTest.java +++ b/src/test/java/com/googlecode/aviator/runtime/function/system/BigIntFunctionUnitTest.java @@ -2,26 +2,25 @@ import com.googlecode.aviator.runtime.type.AviatorJavaType; import org.junit.Test; - import java.util.HashMap; import java.util.Map; public class BigIntFunctionUnitTest { - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNullArgument() { - BigIntFunction bigIntFunction = new BigIntFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - bigIntFunction.call(null, aviatorJavaType); - } + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNullArgument() { + BigIntFunction bigIntFunction = new BigIntFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + bigIntFunction.call(null, aviatorJavaType); + } - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNotSupportArgument() { - BigIntFunction bigIntFunction = new BigIntFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - Map env = new HashMap<>(); - env.put("var", true); + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNotSupportArgument() { + BigIntFunction bigIntFunction = new BigIntFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + Map env = new HashMap<>(); + env.put("var", true); - bigIntFunction.call(env, aviatorJavaType); - } + bigIntFunction.call(env, aviatorJavaType); + } } diff --git a/src/test/java/com/googlecode/aviator/runtime/function/system/DoubleFunctionUnitTest.java b/src/test/java/com/googlecode/aviator/runtime/function/system/DoubleFunctionUnitTest.java index 2c153ae7..3c7e2d59 100644 --- a/src/test/java/com/googlecode/aviator/runtime/function/system/DoubleFunctionUnitTest.java +++ b/src/test/java/com/googlecode/aviator/runtime/function/system/DoubleFunctionUnitTest.java @@ -2,26 +2,25 @@ import com.googlecode.aviator.runtime.type.AviatorJavaType; import org.junit.Test; - import java.util.HashMap; import java.util.Map; public class DoubleFunctionUnitTest { - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNullArgument() { - DoubleFunction doubleFunction = new DoubleFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - doubleFunction.call(null, aviatorJavaType); - } + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNullArgument() { + DoubleFunction doubleFunction = new DoubleFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + doubleFunction.call(null, aviatorJavaType); + } - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNotSupportArgument() { - DoubleFunction doubleFunction = new DoubleFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - Map env = new HashMap<>(); - env.put("var", true); + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNotSupportArgument() { + DoubleFunction doubleFunction = new DoubleFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + Map env = new HashMap<>(); + env.put("var", true); - doubleFunction.call(env, aviatorJavaType); - } + doubleFunction.call(env, aviatorJavaType); + } } diff --git a/src/test/java/com/googlecode/aviator/runtime/function/system/LongFunctionUnitTest.java b/src/test/java/com/googlecode/aviator/runtime/function/system/LongFunctionUnitTest.java index 106f22ed..9865a8e2 100644 --- a/src/test/java/com/googlecode/aviator/runtime/function/system/LongFunctionUnitTest.java +++ b/src/test/java/com/googlecode/aviator/runtime/function/system/LongFunctionUnitTest.java @@ -2,26 +2,25 @@ import com.googlecode.aviator.runtime.type.AviatorJavaType; import org.junit.Test; - import java.util.HashMap; import java.util.Map; public class LongFunctionUnitTest { - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNullArgument() { - LongFunction longFunction = new LongFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - longFunction.call(null, aviatorJavaType); - } + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNullArgument() { + LongFunction longFunction = new LongFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + longFunction.call(null, aviatorJavaType); + } - @Test(expected = ClassCastException.class) - public void testCall_WithJavaTypeNotSupportArgument() { - LongFunction longFunction = new LongFunction(); - AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); - Map env = new HashMap<>(); - env.put("var", true); + @Test(expected = ClassCastException.class) + public void testCall_WithJavaTypeNotSupportArgument() { + LongFunction longFunction = new LongFunction(); + AviatorJavaType aviatorJavaType = new AviatorJavaType("var"); + Map env = new HashMap<>(); + env.put("var", true); - longFunction.call(env, aviatorJavaType); - } + longFunction.call(env, aviatorJavaType); + } }