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/11/08 12:46:18 UTC

[commons-jexl] branch JEXL-384 updated (49e48422 -> 7c1cb9b7)

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

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


    from 49e48422 JEXL-384: revert code related to JEXL-381
     add bc3b6d7f Bump checkstyle from 10.3.4 to 10.4
     add b30da121 Merge pull request #137 from apache/dependabot/maven/com.puppycrawl.tools-checkstyle-10.4
     add ae76290a JEXL-381: change permissions default, update tests, add javadoc;
     add 9245f2dc JEXL-381: attempt to fix cyclic permission init;
     add 5e7ed3cd JEXL-381: removed unused import;
     add fcc0d5f7 JEXL-381: removed unused import;
     add 9083d623 JEXL-381: expose setting JexlEngine used by scripting; expose setting default JexlBuilder permissions;
     add dc190a90 JEXL-381: rebased;
     add 2e62ceee JEXL-381: expose setting JexlEngine used by scripting; expose setting default JexlBuilder permissions;
     add 2b027b46 JEXL-381: added import/namespace pragma feature to enable/disable syntax; - added JexlUberspect#getClassByName that verifies permissions, use it when resolving namespaces; - updated restricted permissions set based on Dmitri feedback;
     add eb4f860d JEXL-381: change permissions default, update tests, add javadoc;
     add 3bae35a0 JEXL-381: attempt to fix cyclic permission init;
     add 09079e14 JEXL-381: removed unused import;
     add 66eaa30a JEXL-381: removed unused import;
     add a42411b5 JEXL-381: expose setting JexlEngine used by scripting; expose setting default JexlBuilder permissions;
     add 63cbdc94 JEXL-381: change permissions default, update tests, add javadoc;
     add 7e814623 JEXL-381: attempt to fix cyclic permission init;
     add 34cfe7b4 JEXL-381: removed unused import;
     add 145fa390 JEXL-381: removed unused import;
     add 2fd5464c JEXL-381: expose setting JexlEngine used by scripting; expose setting default JexlBuilder permissions;
     add 65cf3e3f Merge remote-tracking branch 'origin/JEXL-381' into JEXL-381
     add ed73a52c Merge branch 'master' into JEXL-381
     add 81bda507 Merge remote-tracking branch 'origin/JEXL-381' into JEXL-381
     add 8760868c JEXL-381: added import/namespace pragma feature to enable/disable syntax; - added JexlUberspect#getClassByName that verifies permissions, use it when resolving namespaces; - updated restricted permissions set based on Dmitri feedback;
     add 241f9615 Merge remote-tracking branch 'origin/JEXL-381' into JEXL-381
     add 3c4c1ecd Merge pull request #132 from apache/JEXL-381
     new 913608a8 JEXL-384: enforce strictness of operators by checking null arguments in all operator calls;
     new d664f657 JEXL-384: refine strictness by introducing strict-cast flag in arithmetic only used by cast operators and set as false if strict is false or isStrict method is overridden; - added 'condition' operator to use in if/while/for; - simplified test accordingly;
     new 7c1cb9b7 Merge remote-tracking branch 'origin/JEXL-384' into JEXL-384

The 3 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:
 pom.xml                                            |  7 +-
 .../java/org/apache/commons/jexl3/JexlBuilder.java | 86 +++++++++++++++++-----
 .../org/apache/commons/jexl3/JexlFeatures.java     | 85 +++++++++++++++++----
 .../org/apache/commons/jexl3/internal/Engine.java  | 34 ++++-----
 .../jexl3/internal/introspection/Introspector.java | 16 ++--
 .../jexl3/internal/introspection/Permissions.java  |  4 +-
 .../internal/introspection/PermissionsParser.java  |  3 +
 .../internal/introspection/SandboxUberspect.java   |  5 ++
 .../jexl3/internal/introspection/Uberspect.java    |  2 +-
 .../jexl3/introspection/JexlPermissions.java       | 73 +++++++++++++++++-
 .../commons/jexl3/introspection/JexlUberspect.java | 24 ++++--
 .../apache/commons/jexl3/parser/JexlParser.java    | 16 +++-
 .../commons/jexl3/scripting/JexlScriptEngine.java  | 72 ++++++++++++++----
 .../java/org/apache/commons/jexl3/PragmaTest.java  | 33 ++++++++-
 .../apache/commons/jexl3/PropertyAccessTest.java   |  3 +-
 .../jexl3/internal/introspection/NoJexlTest.java   |  7 +-
 .../internal/introspection/PermissionsTest.java    |  5 +-
 .../commons/jexl3/introspection/SandboxTest.java   |  7 +-
 .../commons/jexl3/jexl342/ReferenceUberspect.java  |  4 +
 .../jexl3/scripting/JexlScriptEngineTest.java      | 56 ++++++++++++--
 20 files changed, 438 insertions(+), 104 deletions(-)


[commons-jexl] 02/03: JEXL-384: refine strictness by introducing strict-cast flag in arithmetic only used by cast operators and set as false if strict is false or isStrict method is overridden; - added 'condition' operator to use in if/while/for; - simplified test accordingly;

Posted by he...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d664f6579c2bb8406c892fe1f4f2d76078e29976
Author: henrib <he...@apache.org>
AuthorDate: Sat Nov 5 18:36:29 2022 +0100

    JEXL-384: refine strictness by introducing strict-cast flag in arithmetic only used by cast operators and set as false if strict is false or isStrict method is overridden;
    - added 'condition' operator to use in if/while/for;
    - simplified test accordingly;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   | 92 +++++++++++++++-------
 .../org/apache/commons/jexl3/JexlOperator.java     |  9 ++-
 .../apache/commons/jexl3/internal/Interpreter.java | 16 ++--
 .../org/apache/commons/jexl3/Issues300Test.java    | 14 +---
 4 files changed, 86 insertions(+), 45 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index e813151a..9f6eca41 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -22,6 +22,7 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
@@ -80,6 +81,9 @@ public class JexlArithmetic {
     /** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
     private final boolean strict;
 
+    /** Whether this JexlArithmetic instance allows null as argument to cast methods - toXXX(). */
+    private final boolean strictCast;
+
     /** The big decimal math context. */
     private final MathContext mathContext;
 
@@ -119,6 +123,17 @@ public class JexlArithmetic {
             // ignore
         }
         this.ctor = actor;
+        boolean cast = strict;
+        // if isStrict is not overridden, we are in strict-cast mode
+        if (cast) {
+            try {
+                Method istrict = getClass().getMethod("isStrict", JexlOperator.class);
+                cast = JexlArithmetic.class == istrict.getDeclaringClass();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        this.strictCast = cast;
     }
 
     /**
@@ -371,7 +386,17 @@ public class JexlArithmetic {
      * @return true if strict, false if lenient
      */
     public boolean isStrict() {
-        return this.strict;
+        return strict;
+    }
+
+    /**
+     * Checks whether this JexlArithmetic instance
+     * strictly considers null as an error when used as operand of a cast method (toXXX())..
+     *
+     * @return true if strict-cast, false if lenient
+     */
+    public boolean isStrictCast() {
+        return strictCast;
     }
 
     /**
@@ -380,21 +405,32 @@ public class JexlArithmetic {
      * If null-safe (ie not-strict), the operator does accept null arguments even if the arithmetic itself is strict.</p>
      * <p>The default implementation considers equal/not-equal operators as null-safe so one can check for null as in
      * <code>if (myvar == null) {...}</code>. Note that this operator is used for equal and not-equal syntax. The complete
-     * list of operators that are not strict are (==, [], []=, ., .=). </p>
-     * <p>An arithmetic refining its strict behavior handling for more operators must declare which by overriding
-     * this method.</p>
+     * list of operators that are not strict are (==, [], []=, ., .=, empty, size, contains). </p>
+     * <p>
+     *     An arithmetic refining its strict behavior handling for more operators must declare which by overriding
+     * this method.
+     * </p>
+     * <p>
+     *     If this method is overridden, the arithmetic instance is <em>NOT</em> in strict-cast mode. Tp restore the
+     *     strict-cast behavior, override the {@link #isStrictCast()} method/
+     * </p>
      * @param operator the operator to check for null-argument(s) handling
      * @return true if operator considers null arguments as errors, false if operator has appropriate semantics
      * for null argument(s)
      */
     public boolean isStrict(JexlOperator operator) {
-        switch(operator) {
-            case EQ:
-            case ARRAY_GET:
-            case ARRAY_SET:
-            case PROPERTY_GET:
-            case PROPERTY_SET:
-                return false;
+        if (operator != null) {
+            switch (operator) {
+                case EQ:
+                case ARRAY_GET:
+                case ARRAY_SET:
+                case PROPERTY_GET:
+                case PROPERTY_SET:
+                case EMPTY:
+                case SIZE:
+                case CONTAINS:
+                    return false;
+            }
         }
         return isStrict();
     }
@@ -435,22 +471,25 @@ public class JexlArithmetic {
      * The result of +,/,-,*,% when both operands are null.
      *
      * @return Integer(0) if lenient
-     * @throws ArithmeticException if strict
+     * @throws ArithmeticException if strict-cast
      */
     protected Object controlNullNullOperands() {
-        if (isStrict()) {
+        if (isStrictCast()) {
             throw new NullOperand();
         }
         return 0;
     }
 
     /**
-     * Throw a NPE if arithmetic is strict.
+     * Throw a NPE if arithmetic is strict-cast.
+     * <p>This method is called by the cast methods ({@link #toBoolean(Object)}, {@link #toInteger(Object)},
+     * {@link #toDouble(Object)}, {@link #toString(Object)}, {@link #toBigInteger(Object)}, {@link #toBigDecimal(Object)})
+     * when they encounter a null argument.</p>
      *
      * @throws ArithmeticException if strict
      */
     protected void controlNullOperand() {
-        if (isStrict()) {
+        if (isStrictCast()) {
             throw new NullOperand();
         }
     }
@@ -556,7 +595,7 @@ public class JexlArithmetic {
         Number result = original;
         if (original instanceof BigDecimal) {
             final BigDecimal bigd = (BigDecimal) original;
-            // if it's bigger than a double it can't be narrowed
+            // if it is bigger than a double, it can not be narrowed
             if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0
                 || bigd.compareTo(BIGD_DOUBLE_MIN_VALUE) < 0) {
                 return original;
@@ -587,7 +626,7 @@ public class JexlArithmetic {
         } else {
             if (original instanceof BigInteger) {
                 final BigInteger bigi = (BigInteger) original;
-                // if it's bigger than a Long it can't be narrowed
+                // if it is bigger than a Long, it can not be narrowed
                 if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
                         || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
                     return original;
@@ -751,7 +790,6 @@ public class JexlArithmetic {
      */
     protected Object increment(Object val, int incr) {
         if (val == null) {
-            controlNullOperand();
             return incr;
         }
         if (val instanceof Integer) {
@@ -803,7 +841,7 @@ public class JexlArithmetic {
                             : left instanceof String && right instanceof String;
         if (!strconcat) {
             try {
-                // if both (non null) args fit as long
+                // if both (non-null) args fit as long
                 final Number ln = asLongNumber(left);
                 final Number rn = asLongNumber(right);
                 if (ln != null && rn != null) {
@@ -835,9 +873,7 @@ public class JexlArithmetic {
                 final BigInteger result = l.add(r);
                 return narrowBigInteger(left, right, result);
             } catch (final ArithmeticException nfe) {
-                if (left == null || right == null) {
-                    controlNullOperand();
-                }
+                // ignore and continue in sequence
             }
         }
         return (left == null? "" : toString(left)).concat(right == null ? "" : toString(right));
@@ -855,7 +891,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -908,7 +944,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -977,7 +1013,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as int
+        // if both (non-null) args fit as int
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -1021,7 +1057,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -1063,7 +1099,6 @@ public class JexlArithmetic {
      */
     public Object negate(final Object val) {
         if (val == null) {
-            controlNullOperand();
             return null;
         }
         if (val instanceof Integer) {
@@ -1120,7 +1155,6 @@ public class JexlArithmetic {
      */
     public Object positivize(final Object val) {
         if (val == null) {
-            controlNullOperand();
             return null;
         }
         if (val instanceof Short) {
@@ -1598,7 +1632,7 @@ public class JexlArithmetic {
             final String strval = val.toString();
             return !strval.isEmpty() && !"false".equals(strval);
         }
-        // non null value is true
+        // non-null value is true
         return true;
     }
 
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 2b93eb24..905f92a9 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -409,7 +409,14 @@ public enum JexlOperator {
      * <br><strong>Method:</strong> <code>Iterator&lt;Object&gt; forEach(R y);</code>.
      * @since 3.1
      */
-    FOR_EACH("for(...)", "forEach", 1);
+    FOR_EACH("for(...)", "forEach", 1),
+
+    /**
+     * Test condition in if, for, while.
+     * <br><strong>Method:</strong> <code>boolean testCondition(R y);</code>.
+     * @since 3.3
+     */
+    CONDITION("?", "testCondition", 1);
 
     /**
      * The operator symbol.
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 433440c0..d3433e1b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -536,6 +536,11 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    private boolean testPredicate(JexlNode node, Object condition) {
+        final Object predicate = operators.tryOverload(node, JexlOperator.CONDITION, condition);
+        return  arithmetic.toBoolean(predicate != JexlEngine.TRY_FAILED? predicate : condition);
+    }
+
     @Override
     protected Object visit(final ASTIfStatement node, final Object data) {
         final int n = 0;
@@ -544,8 +549,9 @@ public class Interpreter extends InterpreterBase {
             Object result = null;
             // pairs of { conditions , 'then' statement }
             for(int ifElse = 0; ifElse < (numChildren - 1); ifElse += 2) {
-                final Object condition = node.jjtGetChild(ifElse).jjtAccept(this, null);
-                if (arithmetic.toBoolean(condition)) {
+                final JexlNode testNode = node.jjtGetChild(ifElse);
+                final Object condition = testNode.jjtAccept(this, null);
+                if (testPredicate(testNode, condition)) {
                     // first objectNode is true statement
                     return node.jjtGetChild(ifElse + 1).jjtAccept(this, null);
                 }
@@ -753,7 +759,7 @@ public class Interpreter extends InterpreterBase {
             // last child is body
             final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
             // while(predicate())...
-            while (predicate == null || arithmetic.toBoolean(predicate.jjtAccept(this, data))) {
+            while (predicate == null || testPredicate(predicate, predicate.jjtAccept(this, data))) {
                 cancelCheck(node);
                 // the body
                 if (statement != null) {
@@ -785,7 +791,7 @@ public class Interpreter extends InterpreterBase {
         Object result = null;
         /* first objectNode is the condition */
         final JexlNode condition = node.jjtGetChild(0);
-        while (arithmetic.toBoolean(condition.jjtAccept(this, data))) {
+        while (testPredicate(condition, condition.jjtAccept(this, data))) {
             cancelCheck(node);
             if (node.jjtGetNumChildren() > 1) {
                 try {
@@ -819,7 +825,7 @@ public class Interpreter extends InterpreterBase {
                     //continue;
                 }
             }
-        } while (arithmetic.toBoolean(condition.jjtAccept(this, data)));
+        } while (testPredicate(condition, condition.jjtAccept(this, data)));
         return result;
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index f80ac72d..cab32c2e 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -1016,17 +1016,11 @@ public class Issues300Test {
             super(astrict);
         }
         @Override
-        public boolean toBoolean(final Object val) {
-            return val == null? false : super.toBoolean(val);
-        }
-        @Override
-        public Object not(final Object val) {
-            return val == null? true : super.not(val);
-        }
-        @Override
         public boolean isStrict(JexlOperator op) {
-            if (JexlOperator.NOT == op) {
-                return false;
+            switch(op) {
+                case NOT:
+                case CONDITION:
+                    return false;
             }
             return super.isStrict(op);
         }


[commons-jexl] 03/03: Merge remote-tracking branch 'origin/JEXL-384' into JEXL-384

Posted by he...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7c1cb9b7543ad3b357b7fcd2d06d441c21e63a18
Merge: d664f657 49e48422
Author: henrib <he...@apache.org>
AuthorDate: Tue Nov 8 13:46:15 2022 +0100

    Merge remote-tracking branch 'origin/JEXL-384' into JEXL-384



[commons-jexl] 01/03: JEXL-384: enforce strictness of operators by checking null arguments in all operator calls;

Posted by he...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 913608a807f296f4501f1e1be0d3bcd45f03ab27
Author: henrib <he...@apache.org>
AuthorDate: Fri Nov 4 17:10:27 2022 +0100

    JEXL-384: enforce strictness of operators by checking null arguments in all operator calls;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   |  13 +-
 .../apache/commons/jexl3/internal/Operators.java   |  30 +++-
 .../org/apache/commons/jexl3/Issues300Test.java    | 178 +++++++++++++++++++--
 .../java/org/apache/commons/jexl3/JexlTest.java    |  16 +-
 4 files changed, 211 insertions(+), 26 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index 4610d227..e813151a 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -379,7 +379,8 @@ public class JexlArithmetic {
      * <p>When an operator is strict, it does <em>not</em> accept null arguments when the arithmetic is strict.
      * If null-safe (ie not-strict), the operator does accept null arguments even if the arithmetic itself is strict.</p>
      * <p>The default implementation considers equal/not-equal operators as null-safe so one can check for null as in
-     * <code>if (myvar == null) {...}</code>. Note that this operator is used for equal and not-equal syntax.</p>
+     * <code>if (myvar == null) {...}</code>. Note that this operator is used for equal and not-equal syntax. The complete
+     * list of operators that are not strict are (==, [], []=, ., .=). </p>
      * <p>An arithmetic refining its strict behavior handling for more operators must declare which by overriding
      * this method.</p>
      * @param operator the operator to check for null-argument(s) handling
@@ -387,7 +388,15 @@ public class JexlArithmetic {
      * for null argument(s)
      */
     public boolean isStrict(JexlOperator operator) {
-        return operator == JexlOperator.EQ? false : isStrict();
+        switch(operator) {
+            case EQ:
+            case ARRAY_GET:
+            case ARRAY_SET:
+            case PROPERTY_GET:
+            case PROPERTY_SET:
+                return false;
+        }
+        return isStrict();
     }
 
     /**
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 f0da705e..b81d1540 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -88,18 +88,44 @@ public class Operators {
         return false;
     }
 
+    /**
+     * Throw a NPE if operator is strict and one of the arguments is null.
+     * @param arithmetic the JEXL arithmetic instance
+     * @param operator the operator to check
+     * @param args the operands
+     * @throws JexlArithmetic.NullOperand if operator is strict and an operand is null
+     */
+    protected void controlNullOperands(JexlArithmetic arithmetic, JexlOperator operator, Object...args) {
+        for (Object arg : args) {
+            // only check operator if necessary
+            if (arg == null) {
+                // check operator only once if it is not strict
+                if (arithmetic.isStrict(operator)) {
+                    throw new JexlArithmetic.NullOperand();
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
     /**
      * Attempts to call an operator.
      * <p>
-     * This takes care of finding and caching the operator method when appropriate
+     *     This performs the null argument control against the strictness of the operator.
+     * </p>
+     * <p>
+     * This takes care of finding and caching the operator method when appropriate.
+     * </p>
      * @param node     the syntactic node
      * @param operator the operator
      * @param args     the arguments
      * @return the result of the operator evaluation or TRY_FAILED
      */
     protected Object tryOverload(final JexlNode node, final JexlOperator operator, Object... args) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        controlNullOperands(arithmetic, operator, args);
         if (operators != null && operators.overloads(operator)) {
-            final JexlArithmetic arithmetic = interpreter.arithmetic;
             final boolean cache = interpreter.cache;
             try {
                 if (cache) {
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index 78a799e7..f80ac72d 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -41,7 +41,7 @@ import static org.junit.Assert.assertEquals;
  */
 public class Issues300Test {
     @Test
-    public void testIssue301a() {
+    public void test301a() {
         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new JexlArithmetic(false)).create();
         final String[] srcs = new String[]{
                 "var x = null; x.0", "var x = null; x[0]", "var x = [null,1]; x[0][0]"
@@ -61,7 +61,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssues301b() {
+    public void tests301b() {
         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new JexlArithmetic(false)).create();
         final Object[] xs = new Object[]{null, null, new Object[]{null, 1}};
         final String[] srcs = new String[]{
@@ -82,7 +82,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue302() {
+    public void test302() {
         final JexlContext jc = new MapContext();
         final String[] strs = new String[]{
                 "{if (0) 1 else 2; var x = 4;}",
@@ -100,7 +100,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue304() {
+    public void test304() {
         final JexlEngine jexlEngine = new JexlBuilder().strict(false).create();
         JexlExpression e304 = jexlEngine.createExpression("overview.limit.var");
 
@@ -150,7 +150,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue305() {
+    public void test305() {
         final JexlEngine jexl = new JexlBuilder().create();
         JexlScript e;
         e = jexl.createScript("{while(false) {}; var x = 1;}");
@@ -162,7 +162,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue306() {
+    public void test306() {
         final JexlContext ctxt = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("x.y ?: 2");
@@ -174,7 +174,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue306a() {
+    public void test306a() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("x.y ?: 2", "x");
         Object o = e.execute(null, new Object());
@@ -184,7 +184,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue306b() {
+    public void test306b() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("x?.y ?: 2", "x");
         final Object o1 = e.execute(null, new Object());
@@ -194,7 +194,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue306c() {
+    public void test306c() {
         final JexlEngine jexl = new JexlBuilder().safe(true).create();
         final JexlScript e = jexl.createScript("x.y ?: 2", "x");
         Object o = e.execute(null, new Object());
@@ -204,7 +204,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue306d() {
+    public void test306d() {
         final JexlEngine jexl = new JexlBuilder().safe(true).create();
         final JexlScript e = jexl.createScript("x.y[z.t] ?: 2", "x");
         Object o = e.execute(null, new Object());
@@ -214,7 +214,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue309a() {
+    public void test309a() {
         final String src = "<html lang=\"en\">\n"
                 + "  <body>\n"
                 + "    <h1>Hello World!</h1>\n"
@@ -233,7 +233,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue309b() {
+    public void test309b() {
         final String src = "<html lang=\"en\">\n"
                 + "  <body>\n"
                 + "    <h1>Hello World!</h1>\n"
@@ -252,7 +252,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testIssue309c() {
+    public void test309c() {
         final String src = "<html lang=\"en\">\n"
                 + "  <body>\n"
                 + "    <h1>Hello World!</h1>\n"
@@ -715,7 +715,7 @@ public class Issues300Test {
 
     @Test
     public void test361b_32() {
-        JexlEngine jexl = new Engine32(new JexlBuilder().safe(false));
+        JexlEngine jexl = new Engine32(new JexlBuilder().safe(false).strict(false));
         Object result = run361b(jexl);
         Assert.assertNotNull(result);
     }
@@ -748,7 +748,7 @@ public class Issues300Test {
 
     @Test
     public void test361c_32() {
-        JexlEngine jexl = new Engine32(new JexlBuilder().safe(false));
+        JexlEngine jexl = new Engine32(new JexlBuilder().safe(false).strict(false));
         String result = run361c(jexl);
         Assert.assertNotNull(result);
     }
@@ -968,7 +968,7 @@ public class Issues300Test {
 
     public static class Context0930 extends MapContext {
         /**
-         * This allows using a JEXL lmabda as a filter.
+         * This allows using a JEXL lambda as a filter.
          * @param stream the stream
          * @param filter the lambda to use as filter
          * @return the filtered stream
@@ -979,7 +979,7 @@ public class Issues300Test {
     }
 
     @Test
-    public void testStackOvflw20220930() {
+    public void testSO20220930() {
         // fill some drivers in a list
         List<Driver0930> values = new ArrayList<>();
         for(int i = 0; i < 8; ++i) {
@@ -1010,4 +1010,148 @@ public class Issues300Test {
             }
         }
     }
+
+    public static class Arithmetic383 extends JexlArithmetic {
+        public Arithmetic383(boolean astrict) {
+            super(astrict);
+        }
+        @Override
+        public boolean toBoolean(final Object val) {
+            return val == null? false : super.toBoolean(val);
+        }
+        @Override
+        public Object not(final Object val) {
+            return val == null? true : super.not(val);
+        }
+        @Override
+        public boolean isStrict(JexlOperator op) {
+            if (JexlOperator.NOT == op) {
+                return false;
+            }
+            return super.isStrict(op);
+        }
+    }
+
+    @Test public void test383() {
+        JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new Arithmetic383(true)).create();
+        String src0 =  "if (a) 1; else 2;";
+        String src1 = "if (!a) 1; else 2;";
+        // local var
+        JexlScript s0 = jexl.createScript(src0, "a");
+        JexlScript s1 = jexl.createScript(src1, "a");
+        Assert.assertEquals(2, s0.execute(null, null));
+        Assert.assertEquals(1, s1.execute(null, null));
+        // global var undefined
+        s0 = jexl.createScript(src0);
+        s1 = jexl.createScript(src1);
+        try {
+            Assert.assertEquals(2, s0.execute(null, null));
+        } catch(JexlException.Variable xvar) {
+            Assert.assertEquals("a", xvar.getVariable());
+        }
+        try {
+            Assert.assertEquals(1, s1.execute(null, null));
+        } catch(JexlException.Variable xvar) {
+            Assert.assertEquals("a", xvar.getVariable());
+        }
+        // global var null
+        MapContext ctxt = new MapContext();
+        ctxt.set("a", null);
+        Assert.assertEquals(2, s0.execute(ctxt, null));
+        Assert.assertEquals(1, s1.execute(ctxt, null));
+    }
+
+    public static class Arithmetic384 extends JexlArithmetic {
+        public Arithmetic384(boolean astrict) {
+            super(astrict);
+        }
+
+        @Override
+        public boolean isStrict(JexlOperator op) {
+            if (JexlOperator.ADD == op) {
+                return false;
+            }
+            return super.isStrict(op);
+        }
+    }
+    @Test
+    public void test384a() {
+        JexlEngine jexl = new JexlBuilder()
+                .safe(false)
+                .strict(true)
+                .create();
+        // constant
+        for(String src0 : Arrays.asList("'ABC' + null", "null + 'ABC'")) {
+            JexlContext ctxt = new MapContext();
+            JexlScript s0 = jexl.createScript(src0);
+            try {
+                s0.execute(ctxt, null);
+                Assert.fail("null argument should throw");
+            } catch (JexlException xvar) {
+                Assert.assertTrue(xvar.toString().contains("+"));
+            }
+        }
+        // null local a
+        for(String src1 : Arrays.asList("'ABC' + a", "a + 'ABC'")) {
+            JexlContext ctxt = new MapContext();
+            JexlScript s1 = jexl.createScript(src1, "a");
+            try {
+                s1.execute(ctxt, null);
+                Assert.fail("null argument should throw");
+            } catch (JexlException.Variable xvar) {
+                Assert.assertEquals("a", xvar.getVariable());
+            }
+            // undefined a
+            s1 = jexl.createScript(src1);
+            try {
+                s1.execute(ctxt, null);
+                Assert.fail("null argument should throw");
+            } catch (JexlException.Variable xvar) {
+                Assert.assertEquals("a", xvar.getVariable());
+                Assert.assertTrue(xvar.isUndefined());
+            }
+            // null a
+            ctxt.set("a", null);
+            try {
+                s1.execute(ctxt, null);
+                Assert.fail("null argument should throw");
+            } catch (JexlException.Variable xvar) {
+                Assert.assertEquals("a", xvar.getVariable());
+                Assert.assertFalse(xvar.isUndefined());
+            }
+        }
+    }
+    @Test
+    public void test384b() {
+        // be explicit about + handling null
+        JexlEngine jexl = new JexlBuilder()
+                .arithmetic(new Arithmetic384(true))
+                .safe(false)
+                .strict(true)
+                .create();
+        // constant
+        for(String src0 : Arrays.asList("'ABC' + null", "null + 'ABC'")) {
+            JexlContext ctxt = new MapContext();
+            JexlScript s0 = jexl.createScript(src0);
+            Assert.assertEquals("ABC", s0.execute(ctxt));
+        }
+        // null local a
+        for(String src1 : Arrays.asList("'ABC' + a", "a + 'ABC'")) {
+            JexlContext ctxt = new MapContext();
+            JexlScript s1 = jexl.createScript(src1, "a");
+            Assert.assertEquals("ABC", s1.execute(ctxt, null));
+            // undefined a
+            s1 = jexl.createScript(src1);
+            try {
+                s1.execute(ctxt, null);
+                Assert.fail("null argument should throw");
+            } catch (JexlException.Variable xvar) {
+                Assert.assertEquals("a", xvar.getVariable());
+                Assert.assertTrue(xvar.isUndefined());
+            }
+            // null a
+            ctxt.set("a", null);
+            Assert.assertEquals("ABC", s1.execute(ctxt, null));
+        }
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index dcde99c3..11c11f2d 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.jexl3;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
@@ -39,12 +40,17 @@ import org.junit.Test;
  * @since 1.0
  */
 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
-public class JexlTest extends JexlTestCase {
-    protected static final String METHOD_STRING = "Method string";
-    protected static final String GET_METHOD_STRING = "GetMethod string";
+public final class JexlTest extends JexlTestCase {
+    static final String METHOD_STRING = "Method string";
+    static final String GET_METHOD_STRING = "GetMethod string";
 
     public JexlTest() {
-        super("JexlTest");
+        super("JexlTest",
+                new JexlBuilder()
+                        .strict(false)
+                        .imports(Arrays.asList("java.lang","java.math"))
+                        .permissions(null)
+                        .cache(128).create());
     }
 
     /**
@@ -158,7 +164,7 @@ public class JexlTest extends JexlTestCase {
         options.setStrict(false);
         jc.set("string", "");
         jc.set("array", new Object[0]);
-        jc.set("map", new HashMap<Object, Object>());
+        jc.set("map", new HashMap<>());
         jc.set("list", new ArrayList<Object>());
         jc.set("set", (new HashMap<Object, Object>()).keySet());
         jc.set("longstring", "thingthing");