You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2019/12/11 21:28:17 UTC

[commons-jexl] branch master updated (38ec7c7 -> 7cbcc66)

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

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


    from 38ec7c7  JEXL: simplified error reporting, a (proprietary) very long script with a syntax error was taking >10' to check; culprit was a jj_rescan_ that got... lost.
     new b15ac35  JEXL-298: size/empty overloads could break operator resolution and return null (instead of a number/boolean), fix and test Task #JEXL-298 - Unable to call 'empty' and 'size' member methods with parameters
     new 7cbcc66  JEXL-307: added lexical shade flag to features, aligned with options semantics Task #JEXL-307 - Variable redeclaration option

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  55 +++++++++--
 .../org/apache/commons/jexl3/JexlFeatures.java     |  27 +++++-
 .../java/org/apache/commons/jexl3/JexlOptions.java |   4 +-
 .../apache/commons/jexl3/internal/Operators.java   | 104 +++++++++++++--------
 .../org/apache/commons/jexl3/internal/Script.java  |   9 +-
 .../jexl3/internal/introspection/Uberspect.java    |  16 ++--
 .../apache/commons/jexl3/parser/JexlParser.java    |  18 ++--
 .../commons/jexl3/ArithmeticOperatorTest.java      |   1 -
 .../org/apache/commons/jexl3/ArithmeticTest.java   |  47 ++++++++--
 .../java/org/apache/commons/jexl3/LexicalTest.java |  15 +++
 10 files changed, 220 insertions(+), 76 deletions(-)


[commons-jexl] 01/02: JEXL-298: size/empty overloads could break operator resolution and return null (instead of a number/boolean), fix and test Task #JEXL-298 - Unable to call 'empty' and 'size' member methods with parameters

Posted by he...@apache.org.
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

commit b15ac35b53438f733afdca5b1015f248f6669c4a
Author: henrib <he...@apache.org>
AuthorDate: Wed Dec 11 22:23:57 2019 +0100

    JEXL-298: size/empty overloads could break operator resolution and return null (instead of a number/boolean), fix and test
    Task #JEXL-298 - Unable to call 'empty' and 'size' member methods with parameters
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  55 +++++++++--
 .../apache/commons/jexl3/internal/Operators.java   | 104 +++++++++++++--------
 .../jexl3/internal/introspection/Uberspect.java    |  16 ++--
 .../commons/jexl3/ArithmeticOperatorTest.java      |   1 -
 .../org/apache/commons/jexl3/ArithmeticTest.java   |  47 ++++++++--
 5 files changed, 161 insertions(+), 62 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index d9778fc..f92d426 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -20,6 +20,7 @@ package org.apache.commons.jexl3;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
@@ -81,9 +82,14 @@ public class JexlArithmetic {
 
     /** The big decimal scale. */
     private final int mathScale;
-
+    
+    /** The dynamic constructor. */
+    private final Constructor<? extends JexlArithmetic> ctor;
+    
     /**
      * Creates a JexlArithmetic.
+     * <p>If you derive your own arithmetic, implement the
+     * other constructor that may be needed when dealing with options.
      *
      * @param astrict whether this arithmetic is strict or lenient
      */
@@ -93,6 +99,7 @@ public class JexlArithmetic {
 
     /**
      * Creates a JexlArithmetic.
+     * <p>The constructor to define in derived classes.
      *
      * @param astrict     whether this arithmetic is lenient or strict
      * @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
@@ -102,6 +109,13 @@ public class JexlArithmetic {
         this.strict = astrict;
         this.mathContext = bigdContext == null ? MathContext.DECIMAL128 : bigdContext;
         this.mathScale = bigdScale == Integer.MIN_VALUE ? BIGD_SCALE : bigdScale;
+        Constructor<? extends JexlArithmetic> actor = null;
+        try {
+            actor = getClass().getConstructor(boolean.class, MathContext.class, int.class);
+        } catch (Exception xany) {
+            // ignore
+        }
+        this.ctor = actor;
     }
     
     /**
@@ -192,6 +206,13 @@ public class JexlArithmetic {
      * @since 3.1
      */
     protected JexlArithmetic createWithOptions(boolean astrict, MathContext bigdContext, int bigdScale) {
+        if (ctor != null) {
+            try {
+                return ctor.newInstance(astrict, bigdContext, bigdScale);
+            } catch (Exception xany) {
+                // it was worth the try
+            }
+        }
         return new JexlArithmetic(astrict, bigdContext, bigdScale);
     }
 
@@ -1015,8 +1036,7 @@ public class JexlArithmetic {
      * @since 3.2
      */
     public Boolean empty(Object object) {
-        Boolean e = isEmpty(object);
-        return e == null ? Boolean.FALSE : e;
+        return object == null || isEmpty(object, false);
     }
 
     /**
@@ -1026,9 +1046,17 @@ public class JexlArithmetic {
      * @return the boolean or null if there is no arithmetic solution
      */
     public Boolean isEmpty(Object object) {
-        if (object == null) {
-            return Boolean.TRUE;
-        }
+        return isEmpty(object, object == null);
+    }
+    
+    /**
+     * Check for emptiness of various types: Number, Collection, Array, Map, String.
+     *
+     * @param object the object to check the emptiness of
+     * @param def the default value if object emptyness can not be determined
+     * @return the boolean or null if there is no arithmetic solution
+     */
+    public Boolean isEmpty(Object object, Boolean def) {
         if (object instanceof Number) {
             double d = ((Number) object).doubleValue();
             return Double.isNaN(d) || d == 0.d ? Boolean.TRUE : Boolean.FALSE;
@@ -1047,16 +1075,27 @@ public class JexlArithmetic {
             return ((Map<?, ?>) object).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
         }
         // we can not determine for sure...
-        return null;
+        return def;
     }
 
     /**
      * Calculate the <code>size</code> of various types: Collection, Array, Map, String.
      *
      * @param object the object to get the size of
-     * @return the size of object or null if there is no arithmetic solution
+     * @return the <i>size</i> of object, 0 if null, 1 if there is no <i>better</i> solution
      */
     public Integer size(Object object) {
+        return size(object, object == null? 0 : 1);
+    }
+    
+    /**
+     * Calculate the <code>size</code> of various types: Collection, Array, Map, String.
+     *
+     * @param object the object to get the size of
+     * @param def the default value if object size can not be determined
+     * @return the size of object or null if there is no arithmetic solution
+     */
+    public Integer size(Object object, Integer def) {
         if (object instanceof CharSequence) {
             return ((CharSequence) object).length();
         }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index be52c79..3fad9dd 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -16,10 +16,12 @@
  */
 package org.apache.commons.jexl3.internal;
 
+import java.lang.reflect.Method;
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlOperator;
+import org.apache.commons.jexl3.internal.introspection.MethodExecutor;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.parser.JexlNode;
@@ -59,6 +61,32 @@ public class Operators {
     }
 
     /**
+     * Checks whether a method returns an int or an Integer.
+     * @param vm the JexlMethod (may be null)
+     * @return true of false
+     */
+    private boolean returnsInteger(JexlMethod vm) {
+        if (vm !=null) {
+            Class<?> rc = vm.getReturnType();
+            return Integer.TYPE.equals(rc) || Integer.class.equals(rc);
+        }
+        return false;
+    }
+        
+    /**
+     * Checks whether a method is a JexlArithmetic method.
+     * @param vm the JexlMethod (may be null)
+     * @return true of false
+     */
+    private boolean isArithmetic(JexlMethod jm) {
+        if (jm instanceof MethodExecutor) {
+            Method method = ((MethodExecutor) jm).getMethod();
+            return JexlArithmetic.class.equals(method.getDeclaringClass());
+        }
+        return false;
+    }
+    
+    /**
      * Attempts to call an operator.
      * <p>
      * This takes care of finding and caching the operator method when appropriate
@@ -83,7 +111,7 @@ public class Operators {
                     }
                 }
                 JexlMethod vm = operators.getOperator(operator, args);
-                if (vm != null) {
+                if (vm != null && !isArithmetic(vm)) {
                     Object result = vm.invoke(arithmetic, args);
                     if (cache) {
                         node.jjtSetValue(vm);
@@ -314,31 +342,30 @@ public class Operators {
      * @param object the object to check the emptyness of
      * @return the evaluation result
      */
-    protected Object empty(JexlNode node, Object object) {
+    protected boolean empty(JexlNode node, Object object) {
         if (object == null) {
-            return Boolean.TRUE;
-        }
-        Object result = Operators.this.tryOverload(node, JexlOperator.EMPTY, object);
-        if (result != JexlEngine.TRY_FAILED) {
-            return result;
+            return true;
         }
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
-        result = arithmetic.isEmpty(object);
-        if (result == null) {
-            final JexlUberspect uberspect = interpreter.uberspect;
-            result = false;
-            // check if there is an isEmpty method on the object that returns a
-            // boolean and if so, just use it
-            JexlMethod vm = uberspect.getMethod(object, "isEmpty", Interpreter.EMPTY_PARAMS);
-            if (returnsBoolean(vm)) {
-                try {
-                    result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
-                } catch (Exception xany) {
-                    interpreter.operatorError(node, JexlOperator.EMPTY, xany);
+        Object result = tryOverload(node, JexlOperator.EMPTY, object);
+        if (result == JexlEngine.TRY_FAILED) {
+            final JexlArithmetic arithmetic = interpreter.arithmetic;
+            result = arithmetic.isEmpty(object, null);
+            if (result == null) {
+                final JexlUberspect uberspect = interpreter.uberspect;
+                result = false;
+                // check if there is an isEmpty method on the object that returns a
+                // boolean and if so, just use it
+                JexlMethod vm = uberspect.getMethod(object, "isEmpty", Interpreter.EMPTY_PARAMS);
+                if (returnsBoolean(vm)) {
+                    try {
+                        result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
+                    } catch (Exception xany) {
+                        interpreter.operatorError(node, JexlOperator.EMPTY, xany);
+                    }
                 }
             }
         }
-        return result;
+        return result instanceof Boolean ? (Boolean) result : true;
     }
 
     /**
@@ -350,29 +377,28 @@ public class Operators {
      * @param object the object to get the size of
      * @return the evaluation result
      */
-    protected Object size(JexlNode node, Object object) {
+    protected int size(JexlNode node, Object object) {
         if (object == null) {
             return 0;
         }
-        Object result = Operators.this.tryOverload(node, JexlOperator.SIZE, object);
-        if (result != JexlEngine.TRY_FAILED) {
-            return result;
-        }
-        final JexlArithmetic arithmetic = interpreter.arithmetic;
-        result = arithmetic.size(object);
-        if (result == null) {
-            final JexlUberspect uberspect = interpreter.uberspect;
-            // check if there is a size method on the object that returns an
-            // integer and if so, just use it
-            JexlMethod vm = uberspect.getMethod(object, "size", Interpreter.EMPTY_PARAMS);
-            if (vm != null && (Integer.TYPE.equals(vm.getReturnType()) || Integer.class.equals(vm.getReturnType()))) {
-                try {
-                    result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
-                } catch (Exception xany) {
-                    interpreter.operatorError(node, JexlOperator.SIZE, xany);
+        Object result = tryOverload(node, JexlOperator.SIZE, object);
+        if (result == JexlEngine.TRY_FAILED) {
+            final JexlArithmetic arithmetic = interpreter.arithmetic;
+            result = arithmetic.size(object, null);
+            if (result == null) {
+                final JexlUberspect uberspect = interpreter.uberspect;
+                // check if there is a size method on the object that returns an
+                // integer and if so, just use it
+                JexlMethod vm = uberspect.getMethod(object, "size", Interpreter.EMPTY_PARAMS);
+                if (returnsInteger(vm)) {
+                    try {
+                        result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
+                    } catch (Exception xany) {
+                        interpreter.operatorError(node, JexlOperator.SIZE, xany);
+                    }
                 }
             }
         }
-        return result;
+        return result instanceof Number ? ((Number) result).intValue() : 0;
     }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index abc3e13..7c674d6 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -457,12 +457,16 @@ public class Uberspect implements JexlUberspect {
                                 if (parms.length != op.getArity()) {
                                     continue;
                                 }
-                                // keep only methods that are not overrides
-                                try {
-                                    JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
-                                } catch (NoSuchMethodException xmethod) {
-                                    // method was not found in JexlArithmetic; this is an operator definition
-                                    ops.add(op);
+                                // filter method that is an actual overload:
+                                // - not inherited (not declared by base class)
+                                // - nor overriden (not present in base class)
+                                if (!JexlArithmetic.class.equals(method.getDeclaringClass())) {
+                                    try {
+                                        JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
+                                    } catch (NoSuchMethodException xmethod) {
+                                        // method was not found in JexlArithmetic; this is an operator definition
+                                        ops.add(op);
+                                    }
                                 }
                             }
                         }
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index 342e6d0..7310a9c 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -55,7 +55,6 @@ public class ArithmeticOperatorTest extends JexlTestCase {
 
     /**
      * Create the named test.
-     * @param name test name
      */
     public ArithmeticOperatorTest() {
         super("ArithmeticOperatorTest");
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 1635bb8..bd59ad4 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -25,6 +25,7 @@ import java.util.Map;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.math.MathContext;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -1153,8 +1154,12 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     public static class XmlArithmetic extends JexlArithmetic {
-        public XmlArithmetic(boolean lenient) {
-            super(lenient);
+        public XmlArithmetic(boolean astrict) {
+            super(astrict);
+        }
+        
+        public XmlArithmetic(boolean astrict, MathContext bigdContext, int bigdScale) {
+            super(astrict, bigdContext, bigdScale);
         }
 
         public boolean empty(org.w3c.dom.Element elt) {
@@ -1166,20 +1171,46 @@ public class ArithmeticTest extends JexlTestCase {
         }
     }
 
-    @Test
     /**
      * Inspired by JEXL-16{1,2}.
      */
+    @Test
     public void testXmlArithmetic() throws Exception {
-        JexlEngine jexl = new JexlBuilder().arithmetic(new XmlArithmetic(false)).create();
-        JexlScript e0 = jexl.createScript("x.empty()", "x");
-        JexlScript e1 = jexl.createScript("empty(x)", "x");
-        JexlScript s0 = jexl.createScript("x.size()", "x");
-        JexlScript s1 = jexl.createScript("size(x)", "x");
         Document xml;
         Node x;
         Boolean empty;
         int size;
+        JexlEvalContext ctxt = new JexlEvalContext();
+        JexlEngine jexl = new JexlBuilder().strict(true).safe(false).arithmetic(new XmlArithmetic(false)).create();
+        JexlScript e0 = jexl.createScript("x.empty()", "x");
+        JexlScript e1 = jexl.createScript("empty(x)", "x");
+        JexlScript s0 = jexl.createScript("x.size()", "x");
+        JexlScript s1 = jexl.createScript("size(x)", "x");
+        
+        empty = (Boolean) e1.execute(null, (Object) null);
+        Assert.assertTrue(empty);
+        size = (Integer) s1.execute(null, (Object) null);
+        Assert.assertEquals(0, size);
+            
+        try {
+            Object xx = e0.execute(null, (Object) null);
+            Assert.assertNull(xx);
+        } catch (JexlException.Variable xvar) {
+            Assert.assertNotNull(xvar);
+        }
+        try {
+            Object xx = s0.execute(null, (Object) null);
+            Assert.assertNull(xx);
+        } catch (JexlException.Variable xvar) {
+            Assert.assertNotNull(xvar);
+        }
+        JexlOptions options = ctxt.getEngineOptions();
+        options.setSafe(true);
+        Object x0 = e0.execute(ctxt, (Object) null);
+        Assert.assertNull(x0);
+        Object x1 = s0.execute(ctxt, (Object) null);
+        Assert.assertNull(x1);
+        
         xml = getDocument("<node info='123'/>");
         x = xml.getLastChild();
         empty = (Boolean) e0.execute(null, x);


[commons-jexl] 02/02: JEXL-307: added lexical shade flag to features, aligned with options semantics Task #JEXL-307 - Variable redeclaration option

Posted by he...@apache.org.
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

commit 7cbcc66519325d23bb16ee6e2da63c9030ef7923
Author: henrib <he...@apache.org>
AuthorDate: Wed Dec 11 22:27:46 2019 +0100

    JEXL-307: added lexical shade flag to features, aligned with options semantics
    Task #JEXL-307 - Variable redeclaration option
---
 .../org/apache/commons/jexl3/JexlFeatures.java     | 27 ++++++++++++++++++++--
 .../java/org/apache/commons/jexl3/JexlOptions.java |  4 ++--
 .../org/apache/commons/jexl3/internal/Script.java  |  9 ++++++--
 .../apache/commons/jexl3/parser/JexlParser.java    | 18 ++++++++-------
 .../java/org/apache/commons/jexl3/LexicalTest.java | 15 ++++++++++++
 5 files changed, 59 insertions(+), 14 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 12d1e6e..e2ec0f7 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -30,6 +30,7 @@ import java.util.TreeSet;
  * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
  * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
  * <li>Lexical: lexical scope, prevents redefining local variables 
+ * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
  * <li>Side Effect : assigning/modifying values on any variables or left-value
  * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
  * <li>New Instance: creating an instance using new(...)
@@ -52,7 +53,7 @@ public final class JexlFeatures {
     private static final String[] F_NAMES = {
         "register", "reserved variable", "local variable", "assign/modify",
         "global assign/modify", "array reference", "create instance", "loop", "function",
-        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical"
+        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -82,8 +83,10 @@ public final class JexlFeatures {
     public static final int ANNOTATION = 12;
     /** Script feature ordinal. */
     public static final int SCRIPT = 13;
-    /** Script feature ordinal. */
+    /** Lexical feature ordinal. */
     public static final int LEXICAL = 14;
+    /** Lexical shade feature ordinal. */
+    public static final int LEXICAL_SHADE = 15;
 
     /**
      * Creates an all-features-enabled instance.
@@ -490,4 +493,24 @@ public final class JexlFeatures {
     public boolean isLexical() {
         return getFeature(LEXICAL);
     }
+        
+    /**
+     * Sets whether syntactic lexical shade is enabled.
+     *
+     * @param flag true means syntactic lexical shade is in effect and implies lexical scope
+     * @return this features instance
+     */
+    public JexlFeatures lexicalShade(boolean flag) {
+        setFeature(LEXICAL_SHADE, flag);
+        if (flag) {
+            setFeature(LEXICAL, true);
+        }
+        return this;
+    }
+    
+    
+    /** @return whether lexical shade feature is enabled */
+    public boolean isLexicalShade() {
+        return getFeature(LEXICAL_SHADE);
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 7d3df9e..943aa72 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -274,8 +274,8 @@ public final class JexlOptions {
     /**
      * Sets whether the engine strictly shades global variables.
      * Local symbols shade globals after definition and creating global
-     * variables is prohibited evaluation.
-     * If setting to lexical shade, lexical is also set.
+     * variables is prohibited during evaluation.
+     * If setting to lexical shade, lexical scope is also set.
      * @param flag true if creation is allowed, false otherwise
      */
     public void setLexicalShade(boolean flag) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java
index 4aa692e..5b1f53d 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -110,8 +110,13 @@ public class Script implements JexlScript, JexlExpression {
         JexlOptions opts = jexl.createOptions(this, context);
         // when parsing lexical, try hard to run lexical
         JexlFeatures features = script.getFeatures();
-        if (features != null && features.isLexical()) {
-            opts.setLexical(true);
+        if (features != null) {
+            if (features.isLexical()) {
+                opts.setLexical(true);
+            }
+            if (features.isLexicalShade()) {
+                opts.setLexicalShade(true);
+            }
         }
         return opts;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index d32feb5..8a5d51b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -276,23 +276,25 @@ public abstract class JexlParser extends StringParser {
         if (frame != null) {
             Integer symbol = frame.getSymbol(name);
             if (symbol != null) {
-                // can not reuse a local as a global
+                boolean declared = true;
                 if (getFeatures().isLexical()) {
-                    // one of the lexical blocks above must declare it
-                    if (!block.hasSymbol(symbol)) {
-                        boolean declared = false;
+                    declared = block.hasSymbol(symbol);
+                    // one of the lexical blocks above should declare it
+                    if (!declared) {
                         for (LexicalUnit u : blocks) {
                             if (u.hasSymbol(symbol)) {
                                 declared = true;
                                 break;
                             }
                         }
-                        if (!declared) {
-                            throw new JexlException(identifier, name + ": variable is not defined");
-                        }
                     }
                 }
-                identifier.setSymbol(symbol, name);
+                if (declared) {
+                    identifier.setSymbol(symbol, name);
+                } else if (getFeatures().isLexicalShade()) {
+                    // can not reuse a local as a global
+                    throw new JexlException(identifier, name + ": variable is not defined");
+                }
             }
         }
         return name;
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index df3e8ec..cbdde4f 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -503,6 +503,7 @@ public class LexicalTest {
     public void testForVariable0() throws Exception {
         JexlFeatures f = new JexlFeatures();
         f.lexical(true);
+        f.lexicalShade(true);
         JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
         try {
             JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0}; return x");
@@ -516,6 +517,7 @@ public class LexicalTest {
     public void testForVariable1() throws Exception {
         JexlFeatures f = new JexlFeatures();
         f.lexical(true);
+        f.lexicalShade(true);
         JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
         try {
             JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0} for(var x : 1..3) { var c = 0}; return x");
@@ -530,6 +532,7 @@ public class LexicalTest {
     public void testUndeclaredVariable() throws Exception {
         JexlFeatures f = new JexlFeatures();
         f.lexical(true);
+        f.lexicalShade(true);
         JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
         try {
             JexlScript script = jexl.createScript("{var x = 0}; return x");
@@ -539,4 +542,16 @@ public class LexicalTest {
            Assert.assertTrue(ex instanceof JexlException);
         }
     }
+    
+    @Test
+    public void testLexical6a1() throws Exception {
+        String str = "i = 0; { var i = 32; }; i";
+        JexlFeatures f = new JexlFeatures();
+        f.lexical(true);
+        JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        JexlScript e = jexl.createScript(str);
+        JexlContext ctxt = new MapContext();
+        Object o = e.execute(ctxt);
+        Assert.assertEquals(0, o);
+    }
 }