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 5d47e727 JEXL-454, JEXL-453: - finally clause is always last child of 
try/catch/finally; - encoded 'special' switch-case values(NaN, null) need to be 
kept for eval;
5d47e727 is described below

commit 5d47e727a6999de4036f540d608ec1ec22e2d00a
Author: Henrib <[email protected]>
AuthorDate: Tue Feb 3 18:56:35 2026 +0100

    JEXL-454, JEXL-453:
    - finally clause is always last child of try/catch/finally;
    - encoded 'special' switch-case values(NaN, null) need to be kept for eval;
---
 .../apache/commons/jexl3/internal/Interpreter.java |   2 +-
 .../commons/jexl3/parser/ASTSwitchStatement.java   |   2 +-
 .../apache/commons/jexl3/parser/JexlParser.java    |  76 +---
 .../java/org/apache/commons/jexl3/SwitchTest.java  | 453 ++++++++++++---------
 4 files changed, 258 insertions(+), 275 deletions(-)

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 8d4b0e08..10102001 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -2186,7 +2186,7 @@ public class Interpreter extends InterpreterBase {
         // if we have a 'finally' block, no matter what, evaluate it: its 
control flow will
         // take precedence over what the 'catch' block might have thrown.
         if (node.hasFinallyClause()) {
-            final JexlNode finallyBody = node.jjtGetChild(nc);
+            final JexlNode finallyBody = 
node.jjtGetChild(node.jjtGetNumChildren() - 1);
             try {
                 finallyBody.jjtAccept(this, data);
             } catch (JexlException.Break | JexlException.Continue | 
JexlException.Return flowException) {
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
index b9e18c77..45b555bc 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java
@@ -102,7 +102,7 @@ public class ASTSwitchStatement extends JexlNode {
         dispatch.put(JexlParser.DFLT, switchIndex);
       } else {
         for (final Object constant : switchSet) {
-          if (dispatch.put(constant == null ? JexlParser.NIL : constant, 
switchIndex) != null) {
+          if (dispatch.put(constant, switchIndex) != null) {
             throw new ParseException("duplicate case in switch statement for 
value: " + constant);
           }
         }
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 83e73af7..6a64a7db 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -22,11 +22,9 @@ import java.io.StringReader;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -377,22 +375,6 @@ public abstract class JexlParser extends StringParser 
implements JexlScriptParse
         return value;
     }
 
-    /**
-     * Decodes a value of a switch predicate.
-     *
-     * @param value an encoded value, which is either a value or NAN (for NaN) 
or NIL (for null).
-     * @return the decoded value.
-     */
-    static Object switchDecode(final Object value) {
-        if (value == NIL) {
-            return null;
-        }
-        if (value == NAN) {
-            return Double.NaN;
-        }
-        return value;
-    }
-
     /**
      * Constructs a set of constants amenable to switch expression.
      */
@@ -400,62 +382,14 @@ public abstract class JexlParser extends StringParser 
implements JexlScriptParse
         return new SwitchSet();
     }
 
-    protected class SwitchSet implements Iterable<Object> {
-        private final Set<Object> values = new LinkedHashSet<>();
-
-        /**
-         * Adds a collection of values to the set.
-         *
-         * @param values the values to add
-         */
-        void addAll(final Collection<Object> values) {
-            values.forEach(this::add);
-        }
-
-        /**
-         * Adds a value to the set.
-         *
-         * @param value the value to add.
-         */
-        void add(final Object value) {
+    protected class SwitchSet extends LinkedHashSet<Object> {
+        @Override
+        public boolean add(final Object value) {
             final Object code = switchCode(value);
-            if (!values.add(code)) {
+            if (!super.add(code)) {
                 throw new JexlException.Parsing(info, "duplicate constant 
value: " + value);
             }
-        }
-
-        void clear() {
-            values.clear();
-        }
-
-        boolean isEmpty() {
-            return values.isEmpty();
-        }
-
-        int size() {
-            return values.size();
-        }
-
-        @Override
-        public Iterator<Object> iterator() {
-            return new Iterator<Object>() {
-                private final Iterator<Object> iter = values.iterator();
-
-                @Override
-                public boolean hasNext() {
-                    return iter.hasNext();
-                }
-
-                @Override
-                public Object next() {
-                    return switchDecode(iter.next());
-                }
-
-                @Override
-                public void remove() {
-                    iter.remove();
-                }
-            };
+            return true;
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/SwitchTest.java 
b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
index 569cc463..06483b61 100644
--- a/src/test/java/org/apache/commons/jexl3/SwitchTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
@@ -34,222 +34,271 @@ import org.junit.jupiter.api.Test;
 @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
 public class SwitchTest extends JexlTestCase {
 
-  public SwitchTest() {
-    super("SwitchTest");
-  }
-
-  @Test
-  void testSwitchExpression() {
-    final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
-    final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 2; 
case 2 -> 3; default -> 4; }; j", "i");
-    final JexlContext jc = new MapContext();
-    final Object o = e.execute(jc, 1);
-    assertEquals(2, o);
-  }
-
-  @Test
-  void testBrokenSwitchExpression0() {
-    final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
-    try {
-      jexl.createScript("var j = switch(i) { case 1 -> return 2;  }; j", "i");
-      fail("should not be able to create script with return in switch 
expression");
-    } catch (final JexlException.Parsing xparse) {
-      assertTrue(xparse.getMessage().contains("return"));
-    }
-    try {
-      jexl.createScript("var j = switch(i) { case 1 -> break; }; j", "i");
-      fail("should not be able to create script with break in switch 
expression");
-    } catch (final JexlException.Parsing xparse) {
-      assertTrue(xparse.getMessage().contains("break"));
-    }
-  }
-
-  @Test
-  void testSwitchStatement() {
-    final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
-    final JexlScript e = jexl.createScript("switch(i) { case 1: i += 2; case 
2: i += 3; default: i += 4; }; i + 33", "i");
-    final JexlContext jc = new MapContext();
-    final Object o = e.execute(jc, 2);
-    assertEquals(42, o);
-  }
-
-  @Test
-  void test440a() {
-    final JexlFeatures f = 
JexlFeatures.createDefault().ambiguousStatement(true);
-    final JexlEngine jexl = new 
JexlBuilder().features(f).safe(false).strict(true).create();
-    String src =
+    public SwitchTest() {
+        super("SwitchTest");
+    }
+
+    @Test
+    void testSwitchExpression() {
+        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
+        final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 
2; case 2 -> 3; default -> 4; }; j", "i");
+        final JexlContext jc = new MapContext();
+        final Object o = e.execute(jc, 1);
+        assertEquals(2, o);
+    }
+
+    @Test
+    void testBrokenSwitchExpression0() {
+        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
+        try {
+            jexl.createScript("var j = switch(i) { case 1 -> return 2;  }; j", 
"i");
+            fail("should not be able to create script with return in switch 
expression");
+        } catch (final JexlException.Parsing xparse) {
+            assertTrue(xparse.getMessage().contains("return"));
+        }
+        try {
+            jexl.createScript("var j = switch(i) { case 1 -> break; }; j", 
"i");
+            fail("should not be able to create script with break in switch 
expression");
+        } catch (final JexlException.Parsing xparse) {
+            assertTrue(xparse.getMessage().contains("break"));
+        }
+    }
+
+    @Test
+    void testSwitchStatement() {
+        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
+        final JexlScript e = jexl.createScript("switch(i) { case 1: i += 2; 
case 2: i += 3; default: i += 4; }; i + 33", "i");
+        final JexlContext jc = new MapContext();
+        final Object o = e.execute(jc, 2);
+        assertEquals(42, o);
+    }
+
+    @Test
+    void test440a() {
+        final JexlFeatures f = 
JexlFeatures.createDefault().ambiguousStatement(true);
+        final JexlEngine jexl = new 
JexlBuilder().features(f).safe(false).strict(true).create();
+        String src =
             "let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\n" + 
"default -> { let z = 4; z + x } } y";
-    JexlScript script = jexl.createScript(src, "x");
-    assertNotNull(script);
-    final String dbgStr = script.getParsedText();
-    assertNotNull(dbgStr);
-
-    Object result = script.execute(null, 10);
-    Assertions.assertEquals(3, result);
-    result = script.execute(null, 11);
-    Assertions.assertEquals(3, result);
-    result = script.execute(null, 20);
-    Assertions.assertEquals(4, result);
-    result = script.execute(null, 21);
-    Assertions.assertEquals(4, result);
-    result = script.execute(null, 38);
-    Assertions.assertEquals(42, result);
-    src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } y";
-    try {
-      script = jexl.createScript(src, "x");
-      fail("should not be able to create script with break in switch");
-    } catch (final JexlException.Parsing xparse) {
-      assertTrue(xparse.getMessage().contains("break"));
-    }
-    assertNotNull(script);
-  }
-
-  @Test
-  void test440b() {
-    final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
-    final String src =
-      "switch (x) {\n" +
-        " case 10 : return 3\n;" +
-        " case 20 : case 21 : return 4;\n" +
-        " case 32: break; \n" +
-        " default : return x + 4;\n" +
-      " }\n" +
-      " 169";
-    final JexlScript script = jexl.createScript(src, "x");
-    assertNotNull(script);
-    final String dbgStr = script.getParsedText();
-    assertNotNull(dbgStr);
-
-    Object result = script.execute(null, 10);
-    Assertions.assertEquals(3, result);
-    result = script.execute(null, 20);
-    Assertions.assertEquals(4, result);
-    result = script.execute(null, 21);
-    Assertions.assertEquals(4, result);
-    result = script.execute(null, 32);
-    Assertions.assertEquals(169, result);
-    result = script.execute(null, 38);
-    Assertions.assertEquals(42, result);
-  }
-
-  public enum Scope440 {
-    UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
-  }
-
-  @Test
-  void test440c() {
-    final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
-    final String src =
+        JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        final String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+
+        Object result = script.execute(null, 10);
+        Assertions.assertEquals(3, result);
+        result = script.execute(null, 11);
+        Assertions.assertEquals(3, result);
+        result = script.execute(null, 20);
+        Assertions.assertEquals(4, result);
+        result = script.execute(null, 21);
+        Assertions.assertEquals(4, result);
+        result = script.execute(null, 38);
+        Assertions.assertEquals(42, result);
+        src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } 
y";
+        try {
+            script = jexl.createScript(src, "x");
+            fail("should not be able to create script with break in switch");
+        } catch (final JexlException.Parsing xparse) {
+            assertTrue(xparse.getMessage().contains("break"));
+        }
+        assertNotNull(script);
+    }
+
+    @Test
+    void test440b() {
+        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).create();
+        final String src =
+            "switch (x) {\n" +
+                " case 10 : return 3\n;" +
+                " case 20 : case 21 : return 4;\n" +
+                " case 32: break; \n" +
+                " default : return x + 4;\n" +
+                " }\n" +
+                " 169";
+        final JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        final String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+
+        Object result = script.execute(null, 10);
+        Assertions.assertEquals(3, result);
+        result = script.execute(null, 20);
+        Assertions.assertEquals(4, result);
+        result = script.execute(null, 21);
+        Assertions.assertEquals(4, result);
+        result = script.execute(null, 32);
+        Assertions.assertEquals(169, result);
+        result = script.execute(null, 38);
+        Assertions.assertEquals(42, result);
+    }
+
+    public enum Scope440 {
+        UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
+    }
+
+    @Test
+    void test440c() {
+        final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+        final String src =
             "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; case 
Scope440.THIS -> 'this'; default -> 'OTHER'; } s";
-    final JexlScript script = jexl.createScript(src, "x");
-    assertNotNull(script);
-    final String dbgStr = script.getParsedText();
-    assertNotNull(dbgStr);
-
-    Object result = script.execute(null, Scope440.UNDEFINED);
-    Assertions.assertEquals("undefined", result);
-    result = script.execute(null, Scope440.THIS);
-    Assertions.assertEquals("this", result);
-    result = script.execute(null, 21);
-    Assertions.assertEquals("OTHER", result);
-  }
-
-  @Test
-  void test440d() {
-    final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
-    final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 
'undefined'; } s";
-    final JexlScript script = jexl.createScript(src, "x");
-    try {
-      script.execute(null, Scope440.THIS);
-      fail("should not be able to execute script with switch expression with 
no default");
-    } catch (final JexlException xjexl) {
-      assertTrue(xjexl.getMessage().contains("switch"));
-    }
-  }
-
-  @Test
-  void test440e() {
-    final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
-    final String src = "function f(x) { switch (x) { case Scope440.UNDEFINED : 
return 'undefined'; } } f(x)";
-    final JexlScript script = jexl.createScript(src, "x");
-    final Object result = script.execute(null, Scope440.UNDEFINED);
-    Assertions.assertEquals("undefined", result);
-    try {
-      script.execute(null, Scope440.THIS);
-      fail("should not be able to execute script with switch expression with 
no default");
-    } catch (final JexlException xjexl) {
-      assertTrue(xjexl.getMessage().contains("switch"));
-    }
-  }
-
-  @Test
-  void testSwitchStatement1() {
-    // a one statement script
-    final String src = "switch (x) {\n"
+        final JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        final String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+
+        Object result = script.execute(null, Scope440.UNDEFINED);
+        Assertions.assertEquals("undefined", result);
+        result = script.execute(null, Scope440.THIS);
+        Assertions.assertEquals("this", result);
+        result = script.execute(null, 21);
+        Assertions.assertEquals("OTHER", result);
+    }
+
+    @Test
+    void test440d() {
+        final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+        final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 
'undefined'; } s";
+        final JexlScript script = jexl.createScript(src, "x");
+        try {
+            script.execute(null, Scope440.THIS);
+            fail("should not be able to execute script with switch expression 
with no default");
+        } catch (final JexlException xjexl) {
+            assertTrue(xjexl.getMessage().contains("switch"));
+        }
+    }
+
+    @Test
+    void test440e() {
+        final JexlEngine jexl = new 
JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+        final String src = "function f(x) { switch (x) { case 
Scope440.UNDEFINED : return 'undefined'; } } f(x)";
+        final JexlScript script = jexl.createScript(src, "x");
+        final Object result = script.execute(null, Scope440.UNDEFINED);
+        Assertions.assertEquals("undefined", result);
+        try {
+            script.execute(null, Scope440.THIS);
+            fail("should not be able to execute script with switch expression 
with no default");
+        } catch (final JexlException xjexl) {
+            assertTrue(xjexl.getMessage().contains("switch"));
+        }
+    }
+
+    @Test
+    void testSwitchStatement1() {
+        // a one statement script
+        final String src = "switch (x) {\n"
             + "  case 1: return 'one';\n"
             + "  case 2: return 'two';\n"
             + "  case 3: return 'three';\n"
             + "  default: return 'many';\n"
             + "}";
-    runSwitch(src);
-    final String src2 = "if (true) { " + src + " }";
-    runSwitch(src2);
-  }
-
-  @Test
-  void testSwitchExpression1() {
-    // a one statement script that uses the expression syntax
-    final String src = "switch (x) {\n"
+        runSwitch(src);
+        final String src2 = "if (true) { " + src + " }";
+        runSwitch(src2);
+    }
+
+    @Test
+    void testSwitchExpression1() {
+        // a one statement script that uses the expression syntax
+        final String src = "switch (x) {\n"
             + "  case 1 -> 'one';\n"
             + "  case 2 -> 'two';\n"
             + "  case 3 -> 'three';\n"
             + "  default -> 'many';\n"
             + "}";
-    runSwitch(src);
-    final String src2 = "if (true) { " + src + " }";
-    runSwitch(src2);
-  }
-
-  void runSwitch(final String src) {
-    final JexlEngine jexl = new JexlBuilder().create();
-    final JexlScript script = jexl.createScript(src, "x");
-    Object result;
-    result = script.execute(null, 1);
-    Assertions.assertEquals("one", result);
-    result = script.execute(null, 2);
-    Assertions.assertEquals("two", result);
-    result = script.execute(null, 3);
-    Assertions.assertEquals("three", result);
-    result = script.execute(null, 4);
-    Assertions.assertEquals("many", result);
-    result = script.execute(null, 42);
-    Assertions.assertEquals("many", result);
-    final Debugger debugger = new Debugger();
-    assertTrue(debugger.debug(script));
-    final String dbgStr = debugger.toString();
-    assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbgStr));
-  }
-
-  @Test
-  void testSwitchExpressionFail() {
-    final List<String> err = Arrays.asList("break", "continue");
-    for (final String keyword : err) {
-      switchExpressionFailKeyword(keyword);
-    }
-  }
-  private void switchExpressionFailKeyword(final String keyword) {
-    // a one statement script that uses the expression syntax
-    final String src = "switch (x) {\n"
+        runSwitch(src);
+        final String src2 = "if (true) { " + src + " }";
+        runSwitch(src2);
+    }
+
+    void runSwitch(final String src) {
+        final JexlEngine jexl = new JexlBuilder().create();
+        final JexlScript script = jexl.createScript(src, "x");
+        Object result;
+        result = script.execute(null, 1);
+        Assertions.assertEquals("one", result);
+        result = script.execute(null, 2);
+        Assertions.assertEquals("two", result);
+        result = script.execute(null, 3);
+        Assertions.assertEquals("three", result);
+        result = script.execute(null, 4);
+        Assertions.assertEquals("many", result);
+        result = script.execute(null, 42);
+        Assertions.assertEquals("many", result);
+        final Debugger debugger = new Debugger();
+        assertTrue(debugger.debug(script));
+        final String dbgStr = debugger.toString();
+        assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbgStr));
+    }
+
+    @Test
+    void testSwitchExpressionFail() {
+        final List<String> err = Arrays.asList("break", "continue");
+        for (final String keyword : err) {
+            switchExpressionFailKeyword(keyword);
+        }
+    }
+
+    private void switchExpressionFailKeyword(final String keyword) {
+        // a one statement script that uses the expression syntax
+        final String src = "switch (x) {\n"
             + "  case 1 -> 'one';\n"
             + "  case 2 -> " + keyword + ";\n"
             + "  default -> 'many';\n"
             + "}";
-    final JexlEngine jexl = new JexlBuilder().create();
-    try {
-      jexl.createScript(src, "x");
-      fail("should not be able to create script with " + keyword + " in switch 
expression");
-    } catch (final JexlException.Parsing xparse) {
-      assertTrue(xparse.getMessage().contains(keyword));
-    }
-  }
+        final JexlEngine jexl = new JexlBuilder().create();
+        try {
+            jexl.createScript(src, "x");
+            fail("should not be able to create script with " + keyword + " in 
switch expression");
+        } catch (final JexlException.Parsing xparse) {
+            assertTrue(xparse.getMessage().contains(keyword));
+        }
+    }
+
+    @Test
+    void testTryCatchFinally() {
+        JexlEngine jexl = new JexlBuilder().create();
+        final String src = "try { return 42 } catch(const xerror){} finally { 
return 169 }";
+        final JexlScript script = jexl.createScript(src);
+        assertNotNull(script);
+        final Object result = script.execute(null);
+        assertEquals(169, result);
+    }
+
+    @Test
+    void testSwitchNaN0() {
+        // a one statement script
+        final String src = "switch (x) {\n"
+            + "  case NaN: return 'NotANumber';\n"
+            + "  case 1: return 'one';\n"
+            + "  case 2: return 'two';\n"
+            + "  case 3: return 'three';\n"
+            + "  default: return 'many';\n"
+            + "}";
+        runSwitchNaN(src);
+    }
+
+    @Test
+    void testSwitchNaN1() {
+        // a one statement script
+        final String src = "switch (x) {\n"
+            + "  case NaN -> 'NotANumber';\n"
+            + "  case 1 -> 'one';\n"
+            + "  case 2 -> 'two';\n"
+            + "  case 3 -> 'three';\n"
+            + "  default -> 'many';\n"
+            + "}";
+        runSwitchNaN(src);
+    }
+
+    static void runSwitchNaN(final String src) {
+        final JexlEngine jexl = new JexlBuilder().create();
+        final JexlScript script = jexl.createScript(src, "x");
+        Object result = script.execute(null, Double.NaN);
+        assertEquals("NotANumber", result);
+        result = script.execute(null, 1);
+        assertEquals("one", result);
+        result = script.execute(null, 42);
+        assertEquals("many", result);
+    }
+
 }

Reply via email to