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 2022/05/16 16:58:40 UTC

[commons-jexl] branch master updated: JEXL-369: move const/defined logic to lexical scope; - restore explicit lexical shade through features; - detect non-initialized const variables

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 4ddf1831 JEXL-369: move const/defined logic to lexical scope; - restore explicit lexical shade through features; - detect non-initialized const variables
4ddf1831 is described below

commit 4ddf18313cadee541132d19beac62fcaa02f7321
Author: henrib <he...@apache.org>
AuthorDate: Mon May 16 18:58:33 2022 +0200

    JEXL-369: move const/defined logic to lexical scope;
    - restore explicit lexical shade through features;
    - detect non-initialized const variables
---
 .../commons/jexl3/internal/LexicalFrame.java       |   2 +-
 .../commons/jexl3/internal/LexicalScope.java       | 143 ++++++++++++++++-----
 .../org/apache/commons/jexl3/internal/Scope.java   |  25 ----
 .../apache/commons/jexl3/parser/ASTIdentifier.java |  10 ++
 .../commons/jexl3/parser/JexlLexicalNode.java      |  40 +++++-
 .../apache/commons/jexl3/parser/JexlParser.java    |  55 +++++---
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  13 +-
 .../java/org/apache/commons/jexl3/LambdaTest.java  |  25 ++++
 .../java/org/apache/commons/jexl3/LexicalTest.java |  20 ++-
 9 files changed, 242 insertions(+), 91 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
index 6bf8cbdd..59fd6315 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java
@@ -54,7 +54,7 @@ public class LexicalFrame extends LexicalScope {
      * @param src the frame to copy
      */
     public LexicalFrame(final LexicalFrame src) {
-        super(src.symbols, src.moreSymbols);
+        super(src);
         frame = src.frame;
         previous = src.previous;
         stack = src.stack != null ? new ArrayDeque<>(src.stack) : null;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
index 4059e55e..5551551a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
@@ -20,7 +20,10 @@ import java.util.BitSet;
 
 /**
  * The set of symbols declared in a lexical scope.
- * <p>The symbol identifiers are determined by the functional scope.
+ * <p>The symbol identifiers are determined by the functional scope.</p>
+ * <p>We use 3 bits per symbol; bit 0 sets the actual symbol as lexical (let),
+ * bit 1 as a const, bit 2 as a defined (valued) const.
+ * There are actually only 4 used states: 0, 1, 3, 7</p>
  */
 public class LexicalScope {
     /**
@@ -28,7 +31,16 @@ public class LexicalScope {
      */
     protected static final int LONGBITS = 64;
     /**
-     * The mask of symbols in the frame.
+     * Bits per symbol.
+     * Declared, const, defined.
+     */
+    protected static final int BITS_PER_SYMBOL = 3;
+    /**
+     * Number of symbols.
+     */
+    protected int count = 0;
+    /**
+     * The mask of symbols in the scope.
      */
     protected long symbols = 0L;
     /**
@@ -36,6 +48,7 @@ public class LexicalScope {
      */
     protected BitSet moreSymbols = null;
 
+
     /**
      * Create a scope.
      */
@@ -44,21 +57,20 @@ public class LexicalScope {
 
     /**
      * Frame copy ctor base.
-     *
-     * @param s  the symbols mask
-     * @param ms the more symbols bitset
      */
-    protected LexicalScope(final long s, final BitSet ms) {
-        symbols = s;
+    protected LexicalScope(LexicalScope other) {
+        BitSet ms;
+        symbols = other.symbols;
+        ms = other.moreSymbols;
         moreSymbols = ms != null ? (BitSet) ms.clone() : null;
     }
 
     /**
-     * Ensure more symbpls can be stored.
+     * Ensures more symbols can be stored.
      *
      * @return the set of more symbols
      */
-    protected final BitSet moreSymbols() {
+    private BitSet moreSymbols() {
         if (moreSymbols == null) {
             moreSymbols = new BitSet();
         }
@@ -66,32 +78,30 @@ public class LexicalScope {
     }
 
     /**
-     * Checks whether a symbol has already been declared.
-     *
-     * @param symbol the symbol
-     * @return true if declared, false otherwise
+     * Whether a given bit (not symbol) is set.
+     * @param bit the bit
+     * @return true if set
      */
-    public boolean hasSymbol(final int symbol) {
-        if (symbol < LONGBITS) {
-            return (symbols & (1L << symbol)) != 0L;
+    private boolean isSet(final int bit) {
+        if (bit < LONGBITS) {
+            return (symbols & (1L << bit)) != 0L;
         }
-        return moreSymbols != null && moreSymbols.get(symbol - LONGBITS);
+        return moreSymbols != null && moreSymbols.get(bit - LONGBITS);
     }
 
     /**
-     * Adds a symbol in this scope.
-     *
-     * @param symbol the symbol
-     * @return true if registered, false if symbol was already registered
+     * Sets a given bit (not symbol).
+     * @param bit the bit
+     * @return true if it was actually set, false if it was set before
      */
-    public boolean addSymbol(final int symbol) {
-        if (symbol < LONGBITS) {
-            if ((symbols & (1L << symbol)) != 0L) {
+    private boolean set(final int bit) {
+        if (bit < LONGBITS) {
+            if ((symbols & (1L << bit)) != 0L) {
                 return false;
             }
-            symbols |= (1L << symbol);
+            symbols |= (1L << bit);
         } else {
-            final int s = symbol - LONGBITS;
+            final int s = bit - LONGBITS;
             final BitSet ms = moreSymbols();
             if (ms.get(s)) {
                 return false;
@@ -101,6 +111,77 @@ public class LexicalScope {
         return true;
     }
 
+    /**
+     * Checks whether a symbol has already been declared.
+     *
+     * @param symbol the symbol
+     * @return true if declared, false otherwise
+     */
+    public boolean hasSymbol(final int symbol) {
+        final int bit = symbol << BITS_PER_SYMBOL;
+        return isSet(bit);
+    }
+
+    /**
+     * Checks whether a symbol is declared as a constant.
+     *
+     * @param symbol the symbol
+     * @return true if declared as constant, false otherwise
+     */
+    public boolean isConstant(final int symbol) {
+        final int bit = (symbol << BITS_PER_SYMBOL) | 1;
+        return isSet(bit);
+    }
+
+    /**
+     * Checks whether a const symbol has been defined, ie has a value.
+     *
+     * @param symbol the symbol
+     * @return true if defined, false otherwise
+     */
+    public boolean isDefined(final int symbol) {
+        final int bit = (symbol << BITS_PER_SYMBOL) | 2;
+        return isSet(bit);
+
+    }
+
+    /**
+     * Adds a symbol in this scope.
+     *
+     * @param symbol the symbol
+     * @return true if registered, false if symbol was already registered
+     */
+    public boolean addSymbol(final int symbol) {
+        final int bit = (symbol << BITS_PER_SYMBOL) ;
+        if (set(bit)) {
+            count += 1;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Adds a constant in this scope.
+     *
+     * @param symbol the symbol
+     * @return true if registered, false if symbol was already registered
+     */
+    public boolean addConstant(final int symbol) {
+        final int bit = (symbol << BITS_PER_SYMBOL) | 1;
+        return set(bit);
+    }
+
+    /**
+     * Defines a constant in this scope.
+     *
+     * @param symbol the symbol
+     * @return true if registered, false if symbol was already registered
+     */
+    public boolean defineSymbol(final int symbol) {
+        final int bit = (symbol << BITS_PER_SYMBOL) | 2;
+        return set(bit);
+    }
+
     /**
      * Clear all symbols.
      *
@@ -112,14 +193,16 @@ public class LexicalScope {
             long clean = symbols;
             while (clean != 0L) {
                 final int s = Long.numberOfTrailingZeros(clean);
-                clean &= ~(1L << s);
-                cleanSymbol.accept(s);
+                // call clean for symbol definition (7 as a mask for 3 bits,1+2+4)
+                clean &= ~(7L << s);
+                cleanSymbol.accept(s >> BITS_PER_SYMBOL);
             }
         }
         symbols = 0L;
         if (moreSymbols != null) {
             if (cleanSymbol != null) {
-                for (int s = moreSymbols.nextSetBit(0); s != -1; s = moreSymbols.nextSetBit(s + 1)) {
+                // step over const and definition (3 bits per symbol)
+                for (int s = moreSymbols.nextSetBit(0); s != -1; s = moreSymbols.nextSetBit(s + BITS_PER_SYMBOL)) {
                     cleanSymbol.accept(s + LONGBITS);
                 }
             }
@@ -131,6 +214,6 @@ public class LexicalScope {
      * @return the number of symbols defined in this scope.
      */
     public int getSymbolCount() {
-        return Long.bitCount(symbols) + (moreSymbols == null ? 0 : moreSymbols.cardinality());
+        return count;
     }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
index 77b045fc..ee14bbd8 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -66,10 +66,6 @@ public final class Scope {
      * The map of local captured variables to parent scope variables, ie closure.
      */
     private Map<Integer, Integer> capturedVariables = null;
-    /**
-     * Const symbols.
-     */
-    private LexicalScope constVariables = null;
     /**
      * Let symbols.
      */
@@ -178,27 +174,6 @@ public final class Scope {
         return lexicalVariables != null && s >= 0 && lexicalVariables.hasSymbol(s);
     }
 
-    /**
-     * Marks a symbol as a const.
-     * @param s the symbol
-     * @return true if the symbol was not already present in the constant set
-     */
-    public boolean addConstant(int s) {
-        if (constVariables == null) {
-            constVariables = new LexicalScope();
-        }
-        return constVariables.addSymbol(s);
-    }
-
-    /**
-     * Checks whether a symbol is declared through a const.
-     * @param s the symbol
-     * @return true if symbol was declared through const
-     */
-    public boolean isConstant(int s) {
-        return constVariables != null && s >= 0 && constVariables.hasSymbol(s);
-    }
-
     /**
      * Checks whether a given symbol is captured.
      * @param symbol the symbol number
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
index 944b5915..88748750 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
@@ -34,6 +34,8 @@ public class ASTIdentifier extends JexlNode {
     private static final int LEXICAL = 3;
     /** The const variable flag. */
     private static final int CONST = 4;
+    /** The defined variable flag. */
+    private static final int DEFINED = 5;
 
     ASTIdentifier(final int id) {
         super(id);
@@ -125,6 +127,14 @@ public class ASTIdentifier extends JexlNode {
         flags = set(CONST, flags, f);
     }
 
+    public boolean isDefined() {
+        return isSet(DEFINED, flags);
+    }
+
+    public void setDefined(final boolean f) {
+        flags = set(DEFINED, flags, f);
+    }
+
     public String getName() {
         return name;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java b/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
index 8d1a5f17..475f11cc 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlLexicalNode.java
@@ -23,7 +23,7 @@ import org.apache.commons.jexl3.internal.LexicalScope;
  * @since 3.2
  */
 public class JexlLexicalNode extends JexlNode implements JexlParser.LexicalUnit {
-    private LexicalScope locals = null;
+    private LexicalScope lexicalScope = null;
 
     public JexlLexicalNode(final int id) {
         super(id);
@@ -35,24 +35,50 @@ public class JexlLexicalNode extends JexlNode implements JexlParser.LexicalUnit
 
     @Override
     public boolean declareSymbol(final int symbol) {
-        if (locals == null) {
-            locals  = new LexicalScope();
+        if (lexicalScope == null) {
+            lexicalScope = new LexicalScope();
         }
-        return locals.addSymbol(symbol);
+        return lexicalScope.addSymbol(symbol);
+    }
+
+    @Override
+    public boolean isConstant(final int symbol) {
+        return lexicalScope != null && lexicalScope.isConstant(symbol);
+    }
+
+    @Override
+    public boolean isDefined(final int symbol) {
+        return  lexicalScope != null && lexicalScope.isDefined(symbol);
+    }
+
+    @Override
+    public void setConstant(int symbol) {
+        lexicalScope.addConstant(symbol);
+    }
+
+    @Override
+    public void setDefined(int symbol) {
+        lexicalScope.defineSymbol(symbol);
     }
 
     @Override
     public int getSymbolCount() {
-        return locals == null? 0 : locals.getSymbolCount();
+        return lexicalScope == null? 0 : lexicalScope.getSymbolCount();
     }
 
     @Override
     public boolean hasSymbol(final int symbol) {
-        return locals != null && locals.hasSymbol(symbol);
+        return lexicalScope != null && lexicalScope.hasSymbol(symbol);
     }
 
     @Override
     public LexicalScope getLexicalScope() {
-        return locals;
+        return lexicalScope;
+    }
+
+
+    @Override
+    public void jjtClose() {
+
     }
 }
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 615de42b..7f971216 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -26,7 +26,6 @@ import org.apache.commons.jexl3.internal.Scope;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
-import java.lang.reflect.Constructor;
 import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.Deque;
@@ -99,6 +98,8 @@ public abstract class JexlParser extends StringParser {
          * @return true if declaration was successful, false if symbol was already declared
          */
         boolean declareSymbol(int symbol);
+        void setConstant(int symbol);
+        void setDefined(int symbol);
 
         /**
          * Checks whether a symbol is declared in this lexical unit.
@@ -106,6 +107,8 @@ public abstract class JexlParser extends StringParser {
          * @return true if declared, false otherwise
          */
         boolean hasSymbol(int symbol);
+        boolean isConstant(int symbol);
+        boolean isDefined(int symbol);
 
         /**
          * @return the number of local variables declared in this unit
@@ -313,32 +316,41 @@ public abstract class JexlParser extends StringParser {
             final Integer symbol = scope.getSymbol(name);
             if (symbol != null) {
                 identifier.setLexical(scope.isLexical(symbol));
-                identifier.setConstant(scope.isConstant(symbol));
                 boolean declared = true;
                 if (scope.isCapturedSymbol(symbol)) {
                     // captured are declared in all cases
                     identifier.setCaptured(true);
                 } else {
-                    declared = block.hasSymbol(symbol);
+                    LexicalUnit unit = block;
+                    declared = unit.hasSymbol(symbol);
                     // one of the lexical blocks above should declare it
                     if (!declared) {
                         for (final LexicalUnit u : blocks) {
                             if (u.hasSymbol(symbol)) {
+                                unit = u;
                                 declared = true;
                                 break;
                             }
                         }
                     }
-                    if (!declared && info instanceof JexlNode.Info) {
+                    if (declared) {
+                        if (unit.isConstant(symbol)) {
+                            identifier.setConstant(true);
+                            if (!unit.isDefined(symbol)) {
+                                throw new JexlException.Parsing(info, name + ": variable is not defined").clean();
+                            }
+                            identifier.setDefined(true);
+                        }
+                    } else if (info instanceof JexlNode.Info) {
                         declared = isSymbolDeclared((JexlNode.Info) info, symbol);
                     }
                 }
                 identifier.setSymbol(symbol, name);
                 if (!declared) {
                     identifier.setShaded(true);
-                    if (identifier.isLexical() || getFeatures().isLexicalShade()) {
+                    if (/*identifier.isLexical() ||*/ getFeatures().isLexicalShade()) {
                         // can not reuse a local as a global
-                        throw new JexlException.Parsing(info, name + ": variable is not defined").clean();
+                        throw new JexlException.Parsing(info, name + ": variable is not declared").clean();
                     }
                 }
             }
@@ -386,7 +398,7 @@ public abstract class JexlParser extends StringParser {
      * @param variable the identifier used to declare
      * @param token      the variable name toekn
      */
-    protected void declareFunction(final ASTVar variable, final Token token, Scope scope) {
+    protected void declareFunction(final ASTVar variable, final Token token) {
         final String name = token.image;
         // function foo() ... <=> const foo = ()->...
         if (scope == null) {
@@ -394,11 +406,16 @@ public abstract class JexlParser extends StringParser {
         }
         final int symbol = scope.declareVariable(name, true, true);
         variable.setSymbol(symbol, name);
+        variable.setLexical(true);
         if (scope.isCapturedSymbol(symbol)) {
             variable.setCaptured(true);
         }
-        // lexical feature error
-        if (!declareSymbol(symbol)) {
+        // function is const fun...
+        if (declareSymbol(symbol)) {
+            scope.addLexical(symbol);
+            block.setConstant(symbol);
+            block.setDefined(symbol);
+        } else {
             if (getFeatures().isLexical()) {
                 throw new JexlException(variable, name + ": variable is already declared");
             }
@@ -440,7 +457,7 @@ public abstract class JexlParser extends StringParser {
             if (lexical) {
                 scope.addLexical(symbol);
                 if (constant) {
-                    scope.addConstant(symbol);
+                    block.setConstant(symbol);
                 }
             }
         }
@@ -472,7 +489,8 @@ public abstract class JexlParser extends StringParser {
         } else if (lexical) {
             scope.addLexical(symbol);
             if (constant) {
-                scope.addConstant(symbol);
+                block.setConstant(symbol);
+                block.setDefined(symbol); // worst case is no argument, parameter will bind to null
             }
         }
     }
@@ -612,10 +630,17 @@ public abstract class JexlParser extends StringParser {
             }
             if (lv instanceof ASTIdentifier) {
                 ASTIdentifier var = (ASTIdentifier) lv;
-                if (!(var instanceof ASTVar)) { // if not a declaration...
-                    int symbol = var.getSymbol();
-                    if (symbol >= 0 && scope.isConstant(symbol)) {
-                        throw new JexlException.Assignment(var.jexlInfo(), var.getName()).clean();
+                int symbol = var.getSymbol();
+                boolean isconst = symbol >= 0 && block != null && block.isConstant(symbol);
+                if (isconst) {
+                    if (!(var instanceof ASTVar)) { // if not a declaration...
+                        if (block.isDefined(symbol)) {
+                            throw new JexlException.Assignment(var.jexlInfo(), var.getName()).clean();
+                        } else {
+                            block.setDefined(symbol);
+                        }
+                    } else {
+                        block.setDefined(symbol);
                     }
                 }
             }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index bfc476e6..af16272a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -455,12 +455,12 @@ void DeclareVar(boolean lexical, boolean constant) #Var :
 {
     t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical, constant); }
 }
-void DeclareFunction(Scope scope) #Var :
+void DeclareFunction() #Var :
 {
     Token t;
 }
 {
-    t=<IDENTIFIER> { declareFunction(jjtThis, t, scope); }
+    t=<IDENTIFIER> { declareFunction(jjtThis, t); }
 }
 
 void Pragma() #void :
@@ -898,15 +898,14 @@ void Lambda() #JexlLambda :
 {
    Token arrow;
    Token name;
-   Scope scope = getScope();
-   pushScope();
 }
 {
-  { pushUnit(jjtThis); } <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction(scope))? Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); popScope(); }
+  <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction())? {
+   pushScope(); pushUnit(jjtThis); } Parameters() ( LOOKAHEAD(3) Block() | Expression()) { popUnit(jjtThis); popScope(); }
   |
-  { pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
+  { pushScope(); pushUnit(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
   |
-  { pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
+  { pushScope(); pushUnit(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); popUnit(jjtThis); popScope(); }
 }
 
 
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index ea32a7e9..de7dab86 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -437,11 +437,36 @@ public class LambdaTest extends JexlTestCase {
         Assert.assertEquals(simpleWhitespace(src), parsed);
     }
 
+    @Test
+    public void testConst0() {
+        final JexlFeatures f = new JexlFeatures();
+        final JexlEngine jexl = new JexlBuilder().strict(true).create();
+        final JexlScript script = jexl.createScript(
+                "{ const x = 10; x + 1 }; { let x = 20; x = 22}");
+        final JexlContext jc = new MapContext();
+        final Object result = script.execute(jc);
+        Assert.assertEquals(22, result);
+    }
+
+    @Test
+    public void testConst1() {
+        final JexlFeatures f = new JexlFeatures();
+        final JexlEngine jexl = new JexlBuilder().strict(true).create();
+        try {
+            final JexlScript script = jexl.createScript(
+                    "const x;  x + 1");
+            Assert.fail("should fail, x is not defined");
+        } catch(JexlException.Parsing xparse) {
+            Assert.assertTrue(xparse.getMessage().contains("x"));
+        }
+    }
+
     @Test public void testNamedFuncIsConst() {
         String src = "function foo(x) { x + x }; var foo ='nonononon'";
         JexlEngine jexl = createEngine();
         try {
             JexlScript script = jexl.createScript(src);
+            Assert.fail("should fail, foo is already defined");
         } catch(JexlException.Parsing xparse) {
             Assert.assertTrue(xparse.getMessage().contains("foo"));
         }
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index 058050fe..9271c554 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -29,6 +29,8 @@ import org.apache.commons.jexl3.internal.Script;
 import org.junit.Assert;
 import org.junit.Test;
 
+import static org.apache.commons.jexl3.JexlTestCase.createEngine;
+
 /**
  * Test cases for lexical option and feature.
  */
@@ -525,7 +527,10 @@ public class LexicalTest {
 
     @Test
     public void testForVariable0a() {
-        final JexlEngine jexl = new JexlBuilder().strict(true).create();
+        final JexlFeatures f = new JexlFeatures();
+        f.lexical(true);
+        f.lexicalShade(true);
+        final JexlEngine jexl = createEngine(f);
         try {
             final JexlScript script = jexl.createScript("for(let x : 1..3) { let c = 0}; return x");
             Assert.fail("Should not have been parsed");
@@ -539,7 +544,7 @@ public class LexicalTest {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
-        final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        final JexlEngine jexl = createEngine(f);
         try {
             final JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0}; return x");
             Assert.fail("Should not have been parsed");
@@ -553,7 +558,7 @@ public class LexicalTest {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
-        final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        final JexlEngine jexl = createEngine(f);
         try {
             final JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0} for(var x : 1..3) { var c = 0}; return x");
             Assert.fail("Should not have been parsed");
@@ -565,7 +570,10 @@ public class LexicalTest {
 
     @Test
     public void testForVariable1b() {
-        final JexlEngine jexl = new JexlBuilder().strict(true).create();
+        final JexlFeatures f = new JexlFeatures();
+        f.lexical(true);
+        f.lexicalShade(true);
+        final JexlEngine jexl = createEngine(f);
         try {
             final JexlScript script = jexl.createScript("for(let x : 1..3) { let c = 0} for(let x : 1..3) { var c = 0}; return x");
             Assert.fail("Should not have been parsed");
@@ -580,7 +588,7 @@ public class LexicalTest {
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
         f.lexicalShade(true);
-        final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        final JexlEngine jexl = createEngine(f);
         try {
             final JexlScript script = jexl.createScript("{var x = 0}; return x");
             Assert.fail("Should not have been parsed");
@@ -595,7 +603,7 @@ public class LexicalTest {
         final String str = "i = 0; { var i = 32; }; i";
         final JexlFeatures f = new JexlFeatures();
         f.lexical(true);
-        final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        final JexlEngine jexl = createEngine(f);
         final JexlScript e = jexl.createScript(str);
         final JexlContext ctxt = new MapContext();
         final Object o = e.execute(ctxt);