From 237e2cf05905b79ccb2a3998011e342c2c9f11d4 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Thu, 6 Jun 2024 11:21:36 -0700 Subject: [PATCH 1/8] feat: supports execution timeout in interprete mode --- .../java/com/googlecode/aviator/Options.java | 20 +++++++++++-- .../code/interpreter/InterpretContext.java | 17 +++++++++++ .../aviator/exception/TimeoutException.java | 29 +++++++++++++++++++ .../aviator/runtime/RuntimeUtils.java | 5 ++++ .../function/system/BigIntFunction.java | 4 ++- .../function/system/DoubleFunction.java | 4 ++- .../runtime/function/system/LongFunction.java | 4 ++- ...orEvaluatorInstanceInterpreteUnitTest.java | 29 +++++++++++++++++++ .../AviatorEvaluatorInstanceUnitTest.java | 1 - .../system/BigIntFunctionUnitTest.java | 29 +++++++++---------- .../system/DoubleFunctionUnitTest.java | 29 +++++++++---------- .../function/system/LongFunctionUnitTest.java | 29 +++++++++---------- 12 files changed, 149 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/googlecode/aviator/exception/TimeoutException.java create mode 100644 src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java diff --git a/src/main/java/com/googlecode/aviator/Options.java b/src/main/java/com/googlecode/aviator/Options.java index 3e81201c..e06ab5e0 100644 --- a/src/main/java/com/googlecode/aviator/Options.java +++ b/src/main/java/com/googlecode/aviator/Options.java @@ -117,9 +117,20 @@ 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). + * + * @since 5.5.0 + * + */ + EVAL_TIMEOUT_MS; /** @@ -200,6 +211,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 +255,7 @@ public Value intoValue(final Object val) { return COMPILE_VALUE; } } + case EVAL_TIMEOUT_MS: case MAX_LOOP_COUNT: return new Value(((Number) val).intValue()); case ALLOWED_CLASS_SET: @@ -278,6 +291,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 +370,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/interpreter/InterpretContext.java b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java index 040cc2eb..42263e0c 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; @@ -26,6 +28,8 @@ public class InterpretContext { private final InterpretExpression expression; private boolean reachEnd; private final boolean trace; + private long startNs = -1; + private long evalTimeoutNs = 0; public InterpretContext(final InterpretExpression exp, final List instruments, @@ -34,6 +38,11 @@ public InterpretContext(final InterpretExpression exp, final List instrument this.instruments = instruments.toArray(this.instruments); this.env = env; this.trace = RuntimeUtils.isTracedEval(env); + long ms = RuntimeUtils.getEvalTimeoutMs(env); + if (ms > 0) { + this.evalTimeoutNs = TimeUnit.NANOSECONDS.convert(ms, TimeUnit.MILLISECONDS); + this.startNs = System.nanoTime(); + } next(); } @@ -137,6 +146,14 @@ public void dispatch(final boolean next) { return; } + // Check whether it's execution is timed out. + if (this.startNs > 0) { + if (System.nanoTime() - this.startNs > this.evalTimeoutNs) { + throw new TimeoutException("Expression execution timed out, exceeded: " + + RuntimeUtils.getEvalTimeoutMs(env) + " ms"); + } + } + if (this.pc != null) { if (this.trace) { RuntimeUtils.printlnTrace(this.env, " " + this.pc + " " + descOperandsStack()); 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..d46fc37d --- /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.5.0 + */ +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/runtime/RuntimeUtils.java b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java index f2895c9a..9348abda 100644 --- a/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java +++ b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java @@ -131,6 +131,11 @@ public static final boolean isTracedEval(final Map env) { return getInstance(env).getOptionValue(Options.TRACE_EVAL).bool; } + // Returns the eval timeout value in milliseconds + public static final int getEvalTimeoutMs(final Map env) { + return getInstance(env).getOptionValue(Options.EVAL_TIMEOUT_MS).number; + } + 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/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/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java new file mode 100644 index 00000000..8ab2397d --- /dev/null +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java @@ -0,0 +1,29 @@ +package com.googlecode.aviator; + +import org.junit.Before; +import org.junit.Test; +import com.googlecode.aviator.exception.TimeoutException; + +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 + } + + @Test(expected = TimeoutException.class) + public void testEvalTimeout() { + this.instance.setOption(Options.EVAL_TIMEOUT_MS, 1); + + this.instance.execute("while(true) { }"); + } + +} diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java index baae2dc9..c53bcc02 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java @@ -680,7 +680,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/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); + } } From 559aca6d44bda42e972c85a316015b638017fa6f Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 09:47:51 -0700 Subject: [PATCH 2/8] fix: returns the imported class or package names when getVariableFullNames, #597 --- .../com/googlecode/aviator/code/OptimizeCodeGenerator.java | 4 ++++ .../com/googlecode/aviator/parser/ExpressionParser.java | 3 +-- src/main/java/com/googlecode/aviator/utils/Constants.java | 1 + .../com/googlecode/aviator/AviatorEvaluatorUnitTest.java | 7 +++++++ 4 files changed, 13 insertions(+), 2 deletions(-) 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/parser/ExpressionParser.java b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java index 62b37d8e..528531ef 100644 --- a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java +++ b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java @@ -1602,8 +1602,7 @@ private void className() { if (expectChar('*')) { 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/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/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() { From 450218b101e97d02b0b6434fc580052ddba3fada Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 09:49:39 -0700 Subject: [PATCH 3/8] fix: adds back checking variable name --- .../java/com/googlecode/aviator/parser/ExpressionParser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java index 528531ef..fa6e28dd 100644 --- a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java +++ b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java @@ -1602,6 +1602,7 @@ private void className() { if (expectChar('*')) { wildcard(); } else { + checkVariableName(this.lookhead); getCodeGenerator().onConstant(this.lookhead.withMeta(Constants.USE_CLASS_PKG, true)); } move(true); From 253693f37b17406cce744f7ffe7bc38fd9cf782b Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 11:12:41 -0700 Subject: [PATCH 4/8] fix: timeout for interpreter --- .../aviator/AviatorEvaluatorInstance.java | 2 +- .../com/googlecode/aviator/BaseExpression.java | 5 +++-- .../code/interpreter/InterpretContext.java | 6 ++---- .../runtime/function/LambdaFunction.java | 4 ++-- .../java/com/googlecode/aviator/utils/Env.java | 18 +++++++++++++++++- .../com/googlecode/aviator/utils/Utils.java | 4 ++++ ...torEvaluatorInstanceInterpreteUnitTest.java | 3 +-- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java index 2dddd77b..6de2dc02 100644 --- a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java +++ b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java @@ -407,7 +407,7 @@ private Map executeModule(final Expression exp, final String abP final Map module = exp.newEnv("exports", exports, "path", abPath); Map env = exp.newEnv("__MODULE__", module, "exports", exports); exp.execute(env); - exports.configure(this, exp); + exports.configure(this, exp, Utils.currentTimeNanos()); 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..a50531dc 100644 --- a/src/main/java/com/googlecode/aviator/BaseExpression.java +++ b/src/main/java/com/googlecode/aviator/BaseExpression.java @@ -28,6 +28,7 @@ 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 @@ -334,13 +335,13 @@ protected Env newEnv(final Map map, final boolean direct) { } else { env = new Env(map); } - env.configure(this.instance, this); + env.configure(this.instance, this, Utils.currentTimeNanos()); return env; } protected Env genTopEnv(final Map map) { if (map instanceof Env) { - ((Env) map).configure(this.instance, this); + ((Env) map).configure(this.instance, this, Utils.currentTimeNanos()); } Env env = newEnv(map, this.instance.getOptionValue(Options.USE_USER_ENV_AS_TOP_ENV_DIRECTLY).bool); 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 42263e0c..f226dfe4 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java @@ -28,7 +28,6 @@ public class InterpretContext { private final InterpretExpression expression; private boolean reachEnd; private final boolean trace; - private long startNs = -1; private long evalTimeoutNs = 0; @@ -41,7 +40,6 @@ public InterpretContext(final InterpretExpression exp, final List instrument long ms = RuntimeUtils.getEvalTimeoutMs(env); if (ms > 0) { this.evalTimeoutNs = TimeUnit.NANOSECONDS.convert(ms, TimeUnit.MILLISECONDS); - this.startNs = System.nanoTime(); } next(); } @@ -147,8 +145,8 @@ public void dispatch(final boolean next) { } // Check whether it's execution is timed out. - if (this.startNs > 0) { - if (System.nanoTime() - this.startNs > this.evalTimeoutNs) { + if (this.evalTimeoutNs > 0 && this.env.getStartNs() > 0) { + if (System.nanoTime() - this.env.getStartNs() > this.evalTimeoutNs) { throw new TimeoutException("Expression execution timed out, exceeded: " + RuntimeUtils.getEvalTimeoutMs(env) + " ms"); } 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/utils/Env.java b/src/main/java/com/googlecode/aviator/utils/Env.java index d21342d0..3d0e5267 100644 --- a/src/main/java/com/googlecode/aviator/utils/Env.java +++ b/src/main/java/com/googlecode/aviator/utils/Env.java @@ -75,6 +75,9 @@ public class Env implements Map, Serializable { public static final Map EMPTY_ENV = Collections.emptyMap(); + // The execution start timestamp in nanoseconds. + private 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); + } + + public void setStartNs(long startNs) { + if (this.startNs == -1) { + 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/AviatorEvaluatorInstanceInterpreteUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java index 8ab2397d..77ae7be0 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java @@ -12,6 +12,7 @@ public class AviatorEvaluatorInstanceInterpreteUnitTest extends AviatorEvaluator public void setup() { super.setup(); this.instance.setOption(Options.EVAL_MODE, EvalMode.INTERPRETER); + this.instance.setOption(Options.EVAL_TIMEOUT_MS, 50); } @Test @@ -21,8 +22,6 @@ public void testTraceEval() throws Exception { @Test(expected = TimeoutException.class) public void testEvalTimeout() { - this.instance.setOption(Options.EVAL_TIMEOUT_MS, 1); - this.instance.execute("while(true) { }"); } From ed1008cc33061cd47fa8b349220ce03914f26604 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 14:35:31 -0700 Subject: [PATCH 5/8] feat: supports execution timeout in asm mode --- .../googlecode/aviator/BaseExpression.java | 2 ++ .../java/com/googlecode/aviator/Options.java | 25 +++++++++++++++++-- .../aviator/code/asm/ASMCodeGenerator.java | 15 +++++++++++ .../aviator/code/interpreter/IR.java | 9 +++++++ .../code/interpreter/InterpretContext.java | 16 +++--------- .../code/interpreter/ir/BranchIfIR.java | 7 ++++++ .../code/interpreter/ir/BranchUnlessIR.java | 5 ++++ .../aviator/code/interpreter/ir/GotoIR.java | 5 ++++ .../code/interpreter/ir/OperatorIR.java | 5 +++- .../aviator/code/interpreter/ir/SendIR.java | 5 ++++ .../aviator/runtime/RuntimeUtils.java | 25 ++++++++++++++++--- .../aviator/runtime/op/OperationRuntime.java | 1 - .../com/googlecode/aviator/utils/Env.java | 2 +- ...orEvaluatorInstanceCompatibleUnitTest.java | 5 ++++ ...orEvaluatorInstanceInterpreteUnitTest.java | 8 ------ .../AviatorEvaluatorInstanceUnitTest.java | 7 ++++++ 16 files changed, 113 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/googlecode/aviator/BaseExpression.java b/src/main/java/com/googlecode/aviator/BaseExpression.java index a50531dc..485605c5 100644 --- a/src/main/java/com/googlecode/aviator/BaseExpression.java +++ b/src/main/java/com/googlecode/aviator/BaseExpression.java @@ -24,6 +24,7 @@ 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; @@ -240,6 +241,7 @@ public Map newEnv(final Object... args) { public abstract Object executeDirectly(final Map env); + @Override public Object execute(Map map) { if (map == null) { diff --git a/src/main/java/com/googlecode/aviator/Options.java b/src/main/java/com/googlecode/aviator/Options.java index e06ab5e0..7097736e 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; @@ -125,7 +126,17 @@ public enum Options { * * 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). + * 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. * * @since 5.5.0 * @@ -146,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(); @@ -255,7 +268,15 @@ public Value intoValue(final Object val) { return COMPILE_VALUE; } } - case EVAL_TIMEOUT_MS: + case EVAL_TIMEOUT_MS: { + Value value = new Value(((Number) val).intValue()); + // Cached the converted result. + if (value.number > 0) { + value.cachedNumber = + (int) TimeUnit.NANOSECONDS.convert(value.number, TimeUnit.MILLISECONDS); + } + return value; + } case MAX_LOOP_COUNT: return new Value(((Number) val).intValue()); case ALLOWED_CLASS_SET: 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 f226dfe4..af295de0 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretContext.java @@ -28,7 +28,6 @@ public class InterpretContext { private final InterpretExpression expression; private boolean reachEnd; private final boolean trace; - private long evalTimeoutNs = 0; public InterpretContext(final InterpretExpression exp, final List instruments, @@ -37,10 +36,6 @@ public InterpretContext(final InterpretExpression exp, final List instrument this.instruments = instruments.toArray(this.instruments); this.env = env; this.trace = RuntimeUtils.isTracedEval(env); - long ms = RuntimeUtils.getEvalTimeoutMs(env); - if (ms > 0) { - this.evalTimeoutNs = TimeUnit.NANOSECONDS.convert(ms, TimeUnit.MILLISECONDS); - } next(); } @@ -144,15 +139,10 @@ public void dispatch(final boolean next) { return; } - // Check whether it's execution is timed out. - if (this.evalTimeoutNs > 0 && this.env.getStartNs() > 0) { - if (System.nanoTime() - this.env.getStartNs() > this.evalTimeoutNs) { - throw new TimeoutException("Expression execution timed out, exceeded: " - + RuntimeUtils.getEvalTimeoutMs(env) + " ms"); - } - } - 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/runtime/RuntimeUtils.java b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java index 9348abda..ff42ea96 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) { + long execTimeoutNs = getEvalTimeoutNs(env); + if (execTimeoutNs > 0) { + if (env instanceof Env) { + long startNs = ((Env) env).getStartNs(); + if (startNs > 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,9 +150,9 @@ public static final boolean isTracedEval(final Map env) { return getInstance(env).getOptionValue(Options.TRACE_EVAL).bool; } - // Returns the eval timeout value in milliseconds - public static final int getEvalTimeoutMs(final Map env) { - return getInstance(env).getOptionValue(Options.EVAL_TIMEOUT_MS).number; + // 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) { 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/Env.java b/src/main/java/com/googlecode/aviator/utils/Env.java index 3d0e5267..163f5f4f 100644 --- a/src/main/java/com/googlecode/aviator/utils/Env.java +++ b/src/main/java/com/googlecode/aviator/utils/Env.java @@ -76,7 +76,7 @@ public class Env implements Map, Serializable { public static final Map EMPTY_ENV = Collections.emptyMap(); // The execution start timestamp in nanoseconds. - private long startNs = -1; + private transient long startNs = -1; /** * Constructs an env instance with empty state. diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java index 7c310a77..55c73ee0 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java @@ -23,6 +23,11 @@ public void testMaxLoopCount() { super.testMaxLoopCount(); } + @Test + public void testEvalTimeout() { + // ignore + } + @Override @Test public void testIssue476() { diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java index 77ae7be0..53eae37b 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceInterpreteUnitTest.java @@ -2,7 +2,6 @@ import org.junit.Before; import org.junit.Test; -import com.googlecode.aviator.exception.TimeoutException; public class AviatorEvaluatorInstanceInterpreteUnitTest extends AviatorEvaluatorInstanceUnitTest { @@ -12,17 +11,10 @@ public class AviatorEvaluatorInstanceInterpreteUnitTest extends AviatorEvaluator public void setup() { super.setup(); this.instance.setOption(Options.EVAL_MODE, EvalMode.INTERPRETER); - this.instance.setOption(Options.EVAL_TIMEOUT_MS, 50); } @Test public void testTraceEval() throws Exception { // ignore } - - @Test(expected = TimeoutException.class) - public void testEvalTimeout() { - this.instance.execute("while(true) { }"); - } - } diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java index c53bcc02..4e8133de 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,11 @@ public void testIssue476() { assertEquals(expr.getVariableFullNames(), Arrays.asList("x")); } + @Test(expected = TimeoutException.class) + public void testEvalTimeout() { + this.instance.execute("while(true) { }"); + } + @SuppressWarnings("unchecked") @Test public void testIssue466() { From 35416de7a1247dfb203620315fe9104f554400d1 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 14:37:51 -0700 Subject: [PATCH 6/8] chore: upgrade spring and jdk versions, only support jdk>=8 --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 From 111980cd0ae89bb64a5eb6629135f6c02a9dcfe2 Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 15:51:09 -0700 Subject: [PATCH 7/8] feat: don't check timeout when loading modules --- .../aviator/AviatorEvaluatorInstance.java | 5 ++-- .../googlecode/aviator/BaseExpression.java | 29 ++++++++++++++----- .../java/com/googlecode/aviator/Options.java | 7 ++--- .../aviator/exception/TimeoutException.java | 2 +- .../aviator/runtime/RuntimeUtils.java | 10 +++---- .../com/googlecode/aviator/utils/Env.java | 6 ++-- .../AviatorEvaluatorInstanceUnitTest.java | 15 ++++++++++ 7 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java index 6de2dc02..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, Utils.currentTimeNanos()); + ((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 485605c5..fc5cba1b 100644 --- a/src/main/java/com/googlecode/aviator/BaseExpression.java +++ b/src/main/java/com/googlecode/aviator/BaseExpression.java @@ -241,13 +241,16 @@ public Map newEnv(final Object... args) { public abstract Object executeDirectly(final Map env); - @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); @@ -330,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, Utils.currentTimeNanos()); + 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, Utils.currentTimeNanos()); + ((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); @@ -357,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 7097736e..b1111d88 100644 --- a/src/main/java/com/googlecode/aviator/Options.java +++ b/src/main/java/com/googlecode/aviator/Options.java @@ -136,9 +136,9 @@ public enum Options { * * * So if the expression doesn't contains these clauses or trapped into a function invocation, the - * behavior may be not expected. + * behavior may be not expected. Try its best, but no promises. * - * @since 5.5.0 + * @since 5.4.2 * */ EVAL_TIMEOUT_MS; @@ -272,8 +272,7 @@ public Value intoValue(final Object val) { Value value = new Value(((Number) val).intValue()); // Cached the converted result. if (value.number > 0) { - value.cachedNumber = - (int) TimeUnit.NANOSECONDS.convert(value.number, TimeUnit.MILLISECONDS); + value.cachedNumber = TimeUnit.NANOSECONDS.convert(value.number, TimeUnit.MILLISECONDS); } return value; } diff --git a/src/main/java/com/googlecode/aviator/exception/TimeoutException.java b/src/main/java/com/googlecode/aviator/exception/TimeoutException.java index d46fc37d..7cdebbc0 100644 --- a/src/main/java/com/googlecode/aviator/exception/TimeoutException.java +++ b/src/main/java/com/googlecode/aviator/exception/TimeoutException.java @@ -4,7 +4,7 @@ * The expression execution is timed out. * * @author dennis(killme2008@gmail.com) - * @since 5.5.0 + * @since 5.4.2 */ public class TimeoutException extends ExpressionRuntimeException { diff --git a/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java index ff42ea96..18f64415 100644 --- a/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java +++ b/src/main/java/com/googlecode/aviator/runtime/RuntimeUtils.java @@ -105,11 +105,11 @@ public static Sequence seq(final Object o, final Map env) { } public static void checkExecutionTimedOut(final Map env) { - long execTimeoutNs = getEvalTimeoutNs(env); - if (execTimeoutNs > 0) { - if (env instanceof Env) { - long startNs = ((Env) env).getStartNs(); - if (startNs > 0) { + 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"); diff --git a/src/main/java/com/googlecode/aviator/utils/Env.java b/src/main/java/com/googlecode/aviator/utils/Env.java index 163f5f4f..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; @@ -78,6 +77,7 @@ public class Env implements Map, Serializable { // The execution start timestamp in nanoseconds. private transient long startNs = -1; + /** * Constructs an env instance with empty state. */ @@ -163,8 +163,8 @@ public void configure(final AviatorEvaluatorInstance instance, final Expression setStartNs(startNs); } - public void setStartNs(long startNs) { - if (this.startNs == -1) { + private void setStartNs(long startNs) { + if (this.startNs == -1 && startNs > 0) { this.startNs = startNs; } } diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java index 4e8133de..d656c61d 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceUnitTest.java @@ -74,6 +74,21 @@ 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() { From d6e7022317054c5ad94799864741b30195a0cb8a Mon Sep 17 00:00:00 2001 From: Dennis Zhuang Date: Fri, 7 Jun 2024 16:00:12 -0700 Subject: [PATCH 8/8] fix: test --- .../aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java index 55c73ee0..2512e710 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorInstanceCompatibleUnitTest.java @@ -34,6 +34,11 @@ public void testIssue476() { // ignore } + @Test + public void testEvalTimeoutAndTryAgain() throws Exception { + // ignore + } + @Override @Test public void testClassAllowList() {