This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-456 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 622c955af213e4d522bd04334322edefcd17b289 Author: Henrib <[email protected]> AuthorDate: Fri Feb 13 12:46:28 2026 +0100 JEXL-456: perform cleanup in parent parser if child parser emits a parsing exception; --- .../apache/commons/jexl3/parser/JexlParser.java | 10 ++- .../org/apache/commons/jexl3/Issues400Test.java | 74 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) 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 6a64a7db..1dff648e 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java @@ -777,7 +777,7 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse final Scope unitScope = blockScopes.get(unit); // follow through potential capture if (blockScope != unitScope) { - final int declared = blockScope.getCaptureDeclaration(lexical); + final int declared = blockScope != null ? blockScope.getCaptureDeclaration(lexical) : -1; if (declared >= 0) { lexical = declared; } @@ -961,7 +961,13 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse */ @Override public ASTJexlScript jxltParse(final JexlInfo info, final JexlFeatures features, final String src, final Scope scope) { - return new Parser(this).parse(info, features, src, scope); + JexlFeatures previous = getFeatures(); + try { + return new Parser(this).parse(info, features, src, scope); + } catch (JexlException.Parsing ex) { + cleanup(previous); + throw ex; + } } /** diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 4ebdce4d..3fcb7514 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -1067,5 +1067,79 @@ public class Issues400Test { Assertions.assertEquals("World Hello World ! ~", writer.toString()); } + @Test + void testIssue350_456_strict() { + final JexlEngine jexl = new JexlBuilder().strict(true).create(); + final JxltEngine jxlt = jexl.createJxltEngine(); + // creation/parse is OK + final JxltEngine.Template template = jxlt.createTemplate("$$ var foo = 'foo';\n$$ var bar = 'bar';\n${foo + bar}"); + final StringWriter writer = new StringWriter(); + template.evaluate(null, writer); + Assertions.assertEquals("foobar", writer.toString()); + } + + @Test + void testIssue350_456_notStrict() { + final JexlEngine jexl = new JexlBuilder().strict(false).create(); + final JxltEngine jxlt = jexl.createJxltEngine(); + // creation/parse is OK + final JxltEngine.Template template = jxlt.createTemplate("$$ var foo = 'foo';\n$$ var bar = 'bar';\n${foo + bar}"); + final StringWriter writer = new StringWriter(); + template.evaluate(null, writer); + Assertions.assertEquals("foobar", writer.toString()); + } + + @Test + void testIssue350_456_strictWithVariable() { + final JexlEngine jexl = new JexlBuilder().strict(true).create(); + final JxltEngine jxlt = jexl.createJxltEngine(); + // creation/parse is OK + final JxltEngine.Template template = jxlt.createTemplate("$$ var foo = 'foo';\n$$ var bar = 'bar';\n${foo + bar}"); + // add a '$$' global context variable + JexlContext ctxt = new MapContext(); + ctxt.set("$$", ""); + final StringWriter writer = new StringWriter(); + template.evaluate(ctxt, writer); + Assertions.assertEquals("foobar", writer.toString()); + } + + @Test + void testIssue36x_456_var() { + final JexlEngine jexl = new JexlBuilder().create(); + final JxltEngine jxlt = jexl.createJxltEngine(); + // OK + jexl.createScript("var foo = 0;\nfoo = 42;"); + try { + jxlt.createTemplate("$$ var foo = 'foo'; if (true) { var foo = 'bar'; var err =&; }"); + Assertions.fail("should have thrown a parsing error in '&'"); + } catch (JexlException xjexl) { + // parsing error in '&' + Assertions.assertTrue(xjexl.getMessage().contains("&")); + } + // JEXL-456: java.lang.NullPointerException: Cannot invoke "org.apache.commons.jexl3.internal.Scope.getCaptureDeclaration(int)" because "blockScope" is null + jexl.createScript("var foo = 0;\nfoo = 42;"); + // JEXL-456: same error with the template creation below + jxlt.createTemplate("$$ var foo = 'foo'; foo = 'bar';"); + } + + @Test + void testIssue36x_456_let() { + final JexlEngine jexl = new Engine32(new JexlBuilder().strict(true)); + // OK + jexl.createScript("let foo = 0;\nfoo = 42;"); + final JxltEngine jxlt = jexl.createJxltEngine(); + try { + jxlt.createTemplate("$$ var err =&;"); + Assertions.fail("should have thrown a parsing error in '&'"); + } catch (JexlException xjexl) { + // parsing error in '&' + Assertions.assertTrue(xjexl.getMessage().contains("&")); + } + // JEXL-456: parsing error in 'foo: variable is already declared' + jexl.createScript("let foo = 0;\nfoo = 42;"); + // JEXL-456: same error with the template creation below + jxlt.createTemplate("$$ let foo = 0;\nfoo = 42;"); + } + }
