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);
+ }
+
}