This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 5904195  JEXL-274: added JexlBuilder option, interpreter code, tests 
and changes
5904195 is described below

commit 5904195b725baf93d9943d4c0ca39aa3e6d4b4a8
Author: henrib <hen...@apache.org>
AuthorDate: Tue Sep 18 13:35:45 2018 +0200

    JEXL-274: added JexlBuilder option, interpreter code, tests and changes
---
 RELEASE-NOTES.txt                                  |  5 +--
 .../java/org/apache/commons/jexl3/JexlBuilder.java | 20 +++++++++++
 .../org/apache/commons/jexl3/JexlException.java    | 30 ++++++++++++++++
 .../org/apache/commons/jexl3/internal/Engine.java  |  5 +++
 .../apache/commons/jexl3/internal/Interpreter.java | 41 ++++++++++++++++++++--
 src/site/xdoc/changes.xml                          |  3 ++
 .../org/apache/commons/jexl3/Issues200Test.java    | 28 +++++++++++++++
 7 files changed, 128 insertions(+), 4 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index d791ca1..334cab7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -30,9 +30,9 @@ What's new in 3.2:
 
 * Interpolated strings in identifiers, as in x.`${prefix}_${suffix}` that in 
many cases would be equivalent to
   x[prefix + '_' + suffix] or x[`${prefix}_${suffix}`].
-* Safe-navigation operator '?.' allowing lenient handling of non-existent or 
null properties so that an expression
+* Safe-navigation mode and operator '?.' allowing lenient handling of 
non-existent or null properties so that an expression
   like 'x?.y?.z' would behave as 'x.y.z' in nominal execution and return null 
instead of throwing an exception in error
-  cases.
+  cases. The safe() option on the builder enables the behavior making '.' and 
'[...]' behave as '?.'.
 * A set of syntactic restrictions can be applied to scripts ranging from not 
allowing side-effects to not allowing
   loops enabling fine control over what end-users may be able to enter as 
expressions/scripts.
 
@@ -40,6 +40,7 @@ New Features in 3.2:
 ====================
 
 * JEXL-275:      Allow safe navigation as option
+* JEXL-274:      Handle soft and hard stack overflow
 * JEXL-273:      Add do...while(...) loops
 * JEXL-264:      Allow space, quote & double-quote in identifiers
 * JEXL-260:      Automatically inject JexlContext in constructor call when 
possible
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java 
b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index 928069e..38786a8 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -99,6 +99,9 @@ public class JexlBuilder {
 
     /** The cache size. */
     private int cache = -1;
+    
+    /** The stack overflow limit. */
+    private int stackOverflow = Integer.MAX_VALUE;
 
     /** The maximum expression length to hit the expression cache. */
     private int cacheThreshold = CACHE_THRESHOLD;
@@ -424,6 +427,23 @@ public class JexlBuilder {
     }
 
     /**
+     * Sets the number of script/expression evaluations that can be stacked.
+     * @param size if not strictly positive, limit is reached when java 
StackOverflow is thrown.
+     * @return this builder
+     */
+    public JexlBuilder stackOverflow(int size) {
+        this.stackOverflow = size;
+        return this;
+    }
+
+    /**
+     * @return the cache size
+     */
+    public int stackOverflow() {
+        return stackOverflow;
+    }
+    
+    /**
      * @return a {@link JexlEngine} instance
      */
     public JexlEngine create() {
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java 
b/src/main/java/org/apache/commons/jexl3/JexlException.java
index 1d009d0..856d154 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -322,6 +322,36 @@ public class JexlException extends RuntimeException {
     }
 
     /**
+     * Thrown when reaching stack-overflow.
+     *
+     * @since 3.2
+     */
+    public static class StackOverflow extends JexlException {
+        /**
+         * Creates a new stack overflow exception instance.
+         *
+         * @param info  the location information
+         * @param name  the unknown method
+         * @param cause the exception causing the error
+         */
+        public StackOverflow(JexlInfo info, String name, Throwable cause) {
+            super(info, name, cause);
+        }
+
+        /**
+         * @return the specific detailed message
+         */
+        public String getDetail() {
+            return super.detailedMessage();
+        }
+
+        @Override
+        protected String detailedMessage() {
+            return "stack overflow " + getDetail();
+        }
+    }
+    
+    /**
      * Thrown when parsing fails due to an invalid assigment.
      *
      * @since 3.0
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java 
b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index bc9735a..4604c7b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -90,6 +90,10 @@ public class Engine extends JexlEngine {
      */
     protected final Map<String, Object> functions;
     /**
+     * The maximum stack height.
+     */
+    protected final int stackOverflow;
+    /**
      * Whether this engine considers unknown variables, methods and 
constructors as errors.
      */
     protected final boolean strict;
@@ -164,6 +168,7 @@ public class Engine extends JexlEngine {
         this.silent = conf.silent() == null ? false : conf.silent();
         this.cancellable = conf.cancellable() == null ? !silent && strict : 
conf.cancellable();
         this.debug = conf.debug() == null ? true : conf.debug();
+        this.stackOverflow = conf.stackOverflow() > 0? conf.stackOverflow() : 
Integer.MAX_VALUE;
         // core properties:
         JexlUberspect uber = conf.uberspect() == null ? 
getUberspect(conf.logger(), conf.strategy()) : conf.uberspect();
         ClassLoader loader = conf.loader();
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java 
b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 4837bc7..11402f3 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -112,9 +112,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Pattern;
 import java.util.concurrent.Callable;
-import org.apache.commons.jexl3.JxltEngine;
 
 
 /**
@@ -127,6 +125,8 @@ public class Interpreter extends InterpreterBase {
     protected final Operators operators;
     /** Cache executors. */
     protected final boolean cache;
+    /** Frame height. */
+    protected int fp = 0;
     /** Symbol values. */
     protected final Scope.Frame frame;
     /** The context to store/retrieve variables. */
@@ -137,6 +137,12 @@ public class Interpreter extends InterpreterBase {
     protected Map<String, Object> functors;
 
     /**
+     * The thread local interpreter.
+     */
+    protected static final java.lang.ThreadLocal<Interpreter> INTER =
+                       new java.lang.ThreadLocal<Interpreter>();
+
+    /**
      * Creates an interpreter.
      * @param engine   the engine creating this interpreter
      * @param aContext the context to evaluate expression
@@ -170,6 +176,17 @@ public class Interpreter extends InterpreterBase {
         functions = ii.functions;
         functors = ii.functors;
     }
+        
+    /**
+     * Swaps the current thread local interpreter.
+     * @param inter the interpreter or null
+     * @return the previous thread local interpreter
+     */
+    protected Interpreter putThreadInterpreter(Interpreter inter) {
+        Interpreter pinter = INTER.get();
+        INTER.set(inter);
+        return pinter;
+    }
 
     /**
      * Interpret the given script/expression.
@@ -183,13 +200,29 @@ public class Interpreter extends InterpreterBase {
     public Object interpret(JexlNode node) {
         JexlContext.ThreadLocal tcontext = null;
         JexlEngine tjexl = null;
+        Interpreter tinter = null;
         try {
+            tinter = putThreadInterpreter(this);
+            if (tinter != null) {
+                fp = tinter.fp + 1;
+            }
             if (context instanceof JexlContext.ThreadLocal) {
                 tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) 
context);
             }
             tjexl = jexl.putThreadEngine(jexl);
+            if (fp > jexl.stackOverflow) {
+                throw new JexlException.StackOverflow(node.jexlInfo(), "jexl 
(" + jexl.stackOverflow + ")", null);
+            }
             cancelCheck(node);
             return node.jjtAccept(this, null);
+        } catch(StackOverflowError xstack) {
+            JexlException xjexl = new 
JexlException.StackOverflow(node.jexlInfo(), "jvm", xstack);
+            if (!isSilent()) {
+                throw xjexl.clean();
+            }
+            if (logger.isWarnEnabled()) {
+                logger.warn(xjexl.getMessage(), xjexl.getCause());
+            }
         } catch (JexlException.Return xreturn) {
             return xreturn.getValue();
         } catch (JexlException.Cancel xcancel) {
@@ -220,6 +253,10 @@ public class Interpreter extends InterpreterBase {
             if (context instanceof JexlContext.ThreadLocal) {
                 jexl.putThreadLocal(tcontext);
             }
+            if (tinter != null) {
+                fp = tinter.fp - 1;
+            }
+            putThreadInterpreter(tinter);
         }
         return null;
     }
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 96c1ab3..7156f73 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -29,6 +29,9 @@
             <action dev="henrib" type="add" issue="JEXL-275">
                  Allow safe navigation as option
             </action>
+            <action dev="henrib" type="add" issue="JEXL-274">
+                Handle soft and hard stack overflow
+            </action>
             <action dev="Dmitri Blinov" type="add" issue="JEXL-175" 
due-to="Dmitri Blinov">
                  Add do...while(...) loops
             </action>
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index a4cfe4f..ea5a84e 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -568,4 +568,32 @@ public class Issues200Test extends JexlTestCase {
         Assert.assertTrue(result instanceof JexlScript);
     }
     
+    
+    @Test
+    public void test274() throws Exception {
+        JexlEngine jexl = new 
JexlBuilder().strict(true).safe(true).stackOverflow(5).create();
+        JexlContext ctxt = new MapContext();
+        JexlScript script= jexl.createScript("var f = (x)->{ x > 1? x * f(x - 
1) : x }; f(a)", "a");
+        Object result = null;
+        result = script.execute(ctxt, 3);
+        Assert.assertEquals(6, result);
+        try {
+            result = script.execute(ctxt, 32);
+            Assert.fail("should have overflown");
+        } catch(JexlException.StackOverflow xstack) {
+            // expected
+            String sxs = xstack.toString();
+            Assert.assertTrue(sxs.contains("jexl"));
+        }
+        jexl = new JexlBuilder().strict(true).create();
+        script= jexl.createScript("var f = (x)->{ x * f(x - 1) }; f(a)", "a");
+        try {
+            result = script.execute(ctxt, 32);
+            Assert.fail("should have overflown");
+        } catch(JexlException.StackOverflow xstack) {
+            // expected
+            String sxs = xstack.toString();
+            Assert.assertTrue(sxs.contains("jvm"));
+        }
+    }
 }

Reply via email to