This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-459 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit d6c1a0463eb020771375f6eb5d0e1a10abeaf115 Author: Henrib <[email protected]> AuthorDate: Tue May 26 19:46:17 2026 +0200 JEXL-459: fix empty/size functions to handle exceptions properly; --- RELEASE-NOTES.txt | 1 + src/changes/changes.xml | 1 + .../org/apache/commons/jexl3/JexlArithmetic.java | 28 ++++++++++++++++++++++ .../java/org/apache/commons/jexl3/JexlBuilder.java | 4 ++-- .../apache/commons/jexl3/internal/Interpreter.java | 21 +++++++--------- .../org/apache/commons/jexl3/Issues400Test.java | 18 ++++++++++++++ 6 files changed, 59 insertions(+), 14 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f760c168..0cef219e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -14,6 +14,7 @@ This is a feature and maintenance release. Java 8 or later is required. Bugs Fixed in 3.6.3: ==================== +o JEXL-459: Empty/size functions swallow all exceptions with no trace. o JEXL-458: Improve permissions expressivity. o JEXL-457: Reduce default exposure for RESTRICTED JexlPermissions. o JEXL-456: Change in template parser behavior. diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8243b110..93ee419f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -30,6 +30,7 @@ <release version="3.6.3" date="YYYY-MM-DD" description="This is a feature and maintenance release. Java 8 or later is required."> <!-- FIX --> + <action dev="henrib" type="fix" issue="JEXL-459" due-to="Mirek Hankus">Empty/size functions swallow all exceptions with no trace.</action> <action dev="henrib" type="fix" issue="JEXL-458" due-to="Daniil Averin">Improve permissions expressivity</action> <action dev="henrib" type="fix" issue="JEXL-457" due-to="Daniil Averin">Reduce default exposure for RESTRICTED JexlPermissions</action> <action dev="henrib" type="fix" issue="JEXL-456" due-to="Vincent Bussol">Change in template parser behavior.</action> diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java index 66f3b432..5399f2fa 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java +++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java @@ -30,11 +30,13 @@ import java.math.MathContext; import java.util.Collection; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.function.ToLongFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.jexl3.introspection.JexlMethod; +import org.apache.commons.logging.Log; /** * Perform arithmetic, implements JexlOperator methods. @@ -1005,6 +1007,32 @@ public class JexlArithmetic { throw new ArithmeticException("Object "+(incr < 0? "decrement":"increment")+":(" + val + ")"); } + /** + * Evaluates a supplier argument, eventually catching and logging any JexlException. + * + * <p>Used primarily by {@link #empty(Object)} and {@link #size(Object)} to guard argument evaluation. + * If evaluation succeeds, returns the supplier's result. If a {@link JexlException} occurs, logs a + * warning and returns the supplier itself.</p> + * + * <p>This method is public to allow overriding. Implementations that change the exception handling + * behavior must still return the supplier on failure to maintain the contract.</p> + * + * @param logger the logger for warning messages; may be null + * @param arg the supplier providing the argument to evaluate + * @return the evaluated result on success, or the supplier itself on JexlException + * @since 3.6.3 + */ + public Object evaluate(Log logger, Supplier<Object> arg) { + try { + return arg.get(); + } catch (JexlException e) { + if (logger != null && logger.isWarnEnabled()) { + logger.warn(e.getMessage(), e); + } + } + return arg; + } + /** * Check for emptiness of various types: Number, Collection, Array, Map, String. * diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java index 66b1d8df..dd410f6c 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java +++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java @@ -736,7 +736,7 @@ public class JexlBuilder { * Sets whether the engine will throw JexlException during evaluation when an error is triggered. * <p>When <em>not</em> silent, the engine throws an exception when the evaluation triggers an exception or an * error.</p> - * <p>It is recommended to use <em>silent(true)</em> as an explicit default.</p> + * <p>It is recommended to use <em>silent(false)</em> as an explicit default.</p> * * @param flag true means no JexlException will occur, false allows them * @return this builder @@ -795,7 +795,7 @@ public class JexlBuilder { } /** - * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or + * Sets whether the engine considers unknown variables, methods, functions, and constructors as errors or * evaluates them as null. * <p>When <em>not</em> strict, operators or functions using null operands return null on evaluation. When * strict, those raise exceptions.</p> 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 db0189f6..9b418ec0 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Queue; import java.util.concurrent.Callable; import java.util.function.Consumer; +import java.util.function.Supplier; import org.apache.commons.jexl3.JexlArithmetic; import org.apache.commons.jexl3.JexlContext; @@ -1314,12 +1315,10 @@ public class Interpreter extends InterpreterBase { @Override protected Object visit(final ASTEmptyFunction node, final Object data) { - try { - final Object value = node.jjtGetChild(0).jjtAccept(this, data); - return operators.empty(node, value); - } catch (final JexlException xany) { - return true; - } + final JexlNode arg = node.jjtGetChild(0); + final Supplier<Object> eval = () -> arg.jjtAccept(this, data); + Object value = arithmetic.evaluate(logger, eval); + return value == eval ? true : operators.empty(node, value); } @Override @@ -2008,12 +2007,10 @@ public class Interpreter extends InterpreterBase { @Override protected Object visit(final ASTSizeFunction node, final Object data) { - try { - final Object val = node.jjtGetChild(0).jjtAccept(this, data); - return operators.size(node, val); - } catch (final JexlException xany) { - return 0; - } + final JexlNode arg = node.jjtGetChild(0); + final Supplier<Object> eval = () -> arg.jjtAccept(this, data); + Object value = arithmetic.evaluate(logger, eval); + return value == eval ? 0 : operators.size(node, value); } @Override diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 6cab8ce3..f3e8ca66 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.StringWriter; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.math.MathContext; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -45,6 +46,7 @@ import java.util.function.Function; import org.apache.commons.jexl3.internal.Debugger; import org.apache.commons.jexl3.internal.Engine32; +import org.apache.commons.jexl3.internal.Operator; import org.apache.commons.jexl3.internal.Scope; import org.apache.commons.jexl3.internal.TemplateEngine; import org.apache.commons.jexl3.introspection.JexlPermissions; @@ -53,6 +55,7 @@ import org.apache.commons.jexl3.parser.ASTJexlScript; import org.apache.commons.jexl3.parser.JexlScriptParser; import org.apache.commons.jexl3.parser.Parser; import org.apache.commons.jexl3.parser.StringProvider; +import org.apache.commons.logging.Log; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -1183,5 +1186,20 @@ public class Issues400Test { assertEquals("42", writer.toString()); } + + @Test + void test459() { + CaptureLog capture = new CaptureLog("test459"); + final JexlBuilder builder = new JexlBuilder().logger(capture).safe(true).strict(false).silent(true); + final JexlEngine jexl = builder.create(); + String expr = "empty('aaa'.substring(400,500))"; + JexlScript script = jexl.createScript(expr); + Object result = script.execute(null); + assertEquals(true, result); + List<String> errors = capture.getCapturedMessages(); + assertEquals(1, errors.size()); + assertTrue(errors.get(0).contains("substring")); + } + }
