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 5451c305 JEXL-410: added createAll() method to create a fully enabled set of JexlFeatures; - updated tests, Javadoc, release notes & changes; 5451c305 is described below commit 5451c30530af8dc1fc145bbee9ce23df73041b9c Author: Henri Biestro <hbies...@cloudera.com> AuthorDate: Fri Oct 20 17:45:22 2023 +0200 JEXL-410: added createAll() method to create a fully enabled set of JexlFeatures; - updated tests, Javadoc, release notes & changes; --- RELEASE-NOTES.txt | 7 +- src/changes/changes.xml | 9 ++ .../org/apache/commons/jexl3/JexlFeatures.java | 103 ++++++++++++++------- .../org/apache/commons/jexl3/FeaturesTest.java | 51 +++++++++- 4 files changed, 132 insertions(+), 38 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 109ee9e0..b00c1a95 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -19,7 +19,7 @@ Its goal is to expose scripting features usable by technical operatives or consu https://commons.apache.org/jexl/ ======================================================================================================================== -Release 3.2.1 +Release 3.3.1 ======================================================================================================================== Version 3.3.1 is a maintenance release. @@ -30,12 +30,15 @@ Version 3.3.1 is source and binary compatible with 3.3. New Features in 3.3.1: ==================== +* JEXL-408: Using JexlFeatures is tedious * JEXL-404: Support array-access safe navigation (x?[y]) * JEXL-401: Captured variables should be read-only * JEXL-398: Allow 'trailing commas' or ellipsis while defining array, map and set literals Bugs Fixed in 3.3.1: =================== +* JEXL-410: JexlFeatures: ctor does not enable all features +* JEXL-409: Disable LEXICAL should disable LEXICAL_SHADE * JEXL-405: Recursive functions corrupt evaluation frame if reassigned * JEXL-403: Exception while evaluating template literal used in array assignment in loop. * JEXL-402: parse failed with empty return value. @@ -57,7 +60,7 @@ accessible through scripts has a real impact on your application security and st an informed review and conscious choice on your end. To mitigate the change, you can revert to the previous behavior with one line of code (see JexlPermissions, JexlBuilder and JexlScriptEngine) or use this opportunity to reduce exposure. Whether Files, URLs, networking, processes, -class-loaders or reflection classes or whether loops or side-effects are accessible are part of your choice to make. +class-loaders or reflection classes or whether loops or side effects are accessible are part of your choice to make. What's new in 3.3: ================== diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0916150f..97e5fa12 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -29,6 +29,9 @@ <body> <release version="3.3.1" date="20YY-MM-DD"> <!-- ADD --> + <action dev="henrib" type="add" issue="JEXL-408" due-to="sebb"> + Using JexlFeatures is tedious + </action> <action dev="henrib" type="add" issue="JEXL-404" due-to="Xu Pengcheng"> Support array-access safe navigation (x?[y]) </action> @@ -39,6 +42,12 @@ Allow 'trailing commas' or ellipsis while defining array, map and set literals </action> <!-- FIX --> + <action dev="henrib" type="fix" issue="JEXL-410" due-to="sebb"> + JexlFeatures: ctor does not enable all features + </action> + <action dev="henrib" type="fix" issue="JEXL-409" due-to="sebb"> + Disable LEXICAL should disable LEXICAL_SHADE + </action> <action dev="henrib" type="fix" issue="JEXL-405"> Recursive functions corrupt evaluation frame if reassigned </action> diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index 02d3e26a..1e91af8e 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -16,8 +16,10 @@ */ package org.apache.commons.jexl3; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.TreeSet; @@ -25,8 +27,13 @@ import java.util.function.Predicate; /** * A set of language feature options. + * <p> * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a * subclass of JexlException.Parsing) when disabled. + * </p> + * <p>It is recommended to be explicit in choosing the features you need rather than rely on the default + * constructor: the 2 convenience methods {@link JexlFeatures#createNone()} and {@link JexlFeatures#createAll()} + * are the recommended starting points to selectively enable or disable chosen features.</p> * <ul> * <li>Registers: register syntax (#number), used internally for {g,s}etProperty * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names @@ -63,7 +70,7 @@ public final class JexlFeatures { /** The namespace names. */ private Predicate<String> nameSpaces; /** The false predicate. */ - public static final Predicate<String> TEST_STR_FALSE = s->false; + public static final Predicate<String> TEST_STR_FALSE = s -> false; /** Te feature names (for toString()). */ private static final String[] F_NAMES = { "register", "reserved variable", "local variable", "assign/modify", @@ -74,15 +81,15 @@ public final class JexlFeatures { }; /** Registers feature ordinal. */ private static final int REGISTER = 0; - /** Reserved name feature ordinal. */ - public static final int RESERVED = 1; // set to indicate that the resevedNames collection is non-empty; not read + /** Reserved future feature ordinal (unused as of 3.3.1). */ + public static final int RESERVED = 1; /** Locals feature ordinal. */ public static final int LOCAL_VAR = 2; /** Side effects feature ordinal. */ public static final int SIDE_EFFECT = 3; /** Global side effects feature ordinal. */ public static final int SIDE_EFFECT_GLOBAL = 4; - /** Array get is allowed on expr. */ + /** Expressions allowed in array reference ordinal. */ public static final int ARRAY_REF_EXPR = 5; /** New-instance feature ordinal. */ public static final int NEW_INSTANCE = 6; @@ -123,7 +130,7 @@ public final class JexlFeatures { * The default features flag mask. * <p>Meant for compatibility with scripts written before 3.3.1</p> */ - private static final long DEFAULT_FEATURES = + public static final long DEFAULT_FEATURES = 1L << LOCAL_VAR | 1L << SIDE_EFFECT | 1L << SIDE_EFFECT_GLOBAL @@ -146,14 +153,28 @@ public final class JexlFeatures { * The canonical scripting (since 3.3.1) features flag mask based on the original default. * <p>Adds lexical, lexical-shade and const-capture but removes comparator-names and pragma-anywhere</p> */ - private static final long SCRIPT_FEATURES = - DEFAULT_FEATURES + public static final long SCRIPT_FEATURES = + ( DEFAULT_FEATURES | 1L << LEXICAL | 1L << LEXICAL_SHADE - | 1L << CONST_CAPTURE + | 1L << CONST_CAPTURE ) // these parentheses are necessary :-) & ~(1L << COMPARATOR_NAMES) & ~(1L << PRAGMA_ANYWHERE); + /** + * All features. + */ + public static final long ALL_FEATURES = (1L << (CONST_CAPTURE + 1)) - 1L; + + /** + * Creates an all features enabled set. + * @return a new instance of all features set + * @since 3.3.1 + */ + public static JexlFeatures createAll() { + return new JexlFeatures(ALL_FEATURES, null, null); + } + /** * Creates an empty feature set. * <p>This is the strictest base-set since no feature is allowed, suitable as-is only @@ -161,31 +182,32 @@ public final class JexlFeatures { * @return a new instance of an empty features set * @since 3.3.1 */ - public static JexlFeatures create() { + public static JexlFeatures createNone() { return new JexlFeatures(0L, null, null); } /** - * Creates a default features set suitable for basic scripting needs. - * <p>Meant for legacy (before 3.3) scripting checks.</p> + * Creates a default features set suitable for basic but complete scripting needs. + * <p>Maximizes compatibility with older version scripts (before 3.3), new projects should + * use {@link JexlFeatures#createScript()} or equivalent features as a base.</p> * <p>The following scripting features are enabled:</p> * <ul> - * <li>local variable {@link JexlFeatures#supportsLocalVar()}</li> - * <li>side effect {@link JexlFeatures#supportsSideEffect()}</li> - * <li>global side effect {@link JexlFeatures#supportsSideEffectGlobal()}</li> - * <li>array reference expression {@link JexlFeatures#supportsStructuredLiteral()}</li> - * <li>new instance {@link JexlFeatures#supportsNewInstance()} </li> - * <li>loop {@link JexlFeatures#supportsLoops()} </li> - * <li>lambda {@link JexlFeatures#supportsLambda()}</li> - * <li>method call {@link JexlFeatures#supportsMethodCall()}</li> - * <li>structured literal {@link JexlFeatures#supportsStructuredLiteral()}</li> - * <li>pragma {@link JexlFeatures#supportsPragma()}</li> - * <li>annotation {@link JexlFeatures#supportsAnnotation()}</li> - * <li>script {@link JexlFeatures#supportsScript()}</li> - * <li>comparator names {@link JexlFeatures#supportsComparatorNames()}</li> - * <li>namespace pragma {@link JexlFeatures#supportsNamespacePragma()}</li> - * <li>import pragma {@link JexlFeatures#supportsImportPragma()}</li> - * <li>pragma anywhere {@link JexlFeatures#supportsPragmaAnywhere()} </li> + * <li>local variable, {@link JexlFeatures#supportsLocalVar()}</li> + * <li>side effect, {@link JexlFeatures#supportsSideEffect()}</li> + * <li>global side effect, {@link JexlFeatures#supportsSideEffectGlobal()}</li> + * <li>array reference expression, {@link JexlFeatures#supportsStructuredLiteral()}</li> + * <li>new instance, {@link JexlFeatures#supportsNewInstance()} </li> + * <li>loop, {@link JexlFeatures#supportsLoops()}</li> + * <li>lambda, {@link JexlFeatures#supportsLambda()}</li> + * <li>method call, {@link JexlFeatures#supportsMethodCall()}</li> + * <li>structured literal, {@link JexlFeatures#supportsStructuredLiteral()}</li> + * <li>pragma, {@link JexlFeatures#supportsPragma()}</li> + * <li>annotation, {@link JexlFeatures#supportsAnnotation()}</li> + * <li>script, {@link JexlFeatures#supportsScript()}</li> + * <li>comparator names, {@link JexlFeatures#supportsComparatorNames()}</li> + * <li>namespace pragma, {@link JexlFeatures#supportsNamespacePragma()}</li> + * <li>import pragma, {@link JexlFeatures#supportsImportPragma()}</li> + * <li>pragma anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li> * </ul> * @return a new instance of a default scripting features set * @since 3.3.1 @@ -194,14 +216,35 @@ public final class JexlFeatures { return new JexlFeatures(DEFAULT_FEATURES, null, null); } + /** + * Protected future syntactic elements. + * <p><em>try, catch, throw, finally, switch, case, default, class, instanceof</em></p> + * @since 3.3.1 + */ + public static final Set<String> RESERVED_WORDS = + Collections.unmodifiableSet( + new HashSet<>((Arrays.asList( + "try", "catch", "throw", "finally", "switch", "case", "default", "class", "instanceof")))); + /** * The modern scripting features set. - * <p>All scripting features are set including lexical, lexical-shade and const-capture.</p> + * <p>This is the recommended set for new projects.</p> + * <p>All default features with the following differences:</p> + * <ul> + * <li><em>disable</em> pragma-anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li> + * <li><em>disable</em> comparator-names, {@link JexlFeatures#supportsComparatorNames()}</li> + * <li><em>enable</em> lexical, {@link JexlFeatures#isLexical()}</li> + * <li><em>enable</em> lexical-shade, {@link JexlFeatures#isLexicalShade()} </li> + * <li><em>enable</em> const-capture, {@link JexlFeatures#supportsConstCapture()}</li> + * </ul> + * <p>It also adds a set of reserved words to enable future unencumbered syntax evolution: + * <em>try, catch, throw, finally, switch, case, default, class, instanceof</em> + * </p> * @return a new instance of a modern scripting features set * @since 3.3.1 */ public static JexlFeatures createScript() { - return new JexlFeatures(SCRIPT_FEATURES, null, null); + return new JexlFeatures(SCRIPT_FEATURES, RESERVED_WORDS, null); } /** @@ -232,7 +275,6 @@ public final class JexlFeatures { // This can only be guaranteed if this ctor is private this.reservedNames = r == null? Collections.emptySet() : r; this.nameSpaces = n == null? TEST_STR_FALSE : n; - setFeature(RESERVED, !this.reservedNames.isEmpty()); } @Override @@ -284,7 +326,6 @@ public final class JexlFeatures { } else { reservedNames = Collections.unmodifiableSet(new TreeSet<>(names)); } - setFeature(RESERVED, !reservedNames.isEmpty()); return this; } diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java index bb2ae330..1ab59642 100644 --- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java +++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java @@ -21,6 +21,8 @@ import java.util.Arrays; import org.junit.Assert; import org.junit.Test; +import static org.apache.commons.jexl3.JexlFeatures.CONST_CAPTURE; + /** * Tests for blocks * @since 1.1 @@ -326,7 +328,7 @@ public class FeaturesTest extends JexlTestCase { @Test public void testIssue409() { - final JexlFeatures baseFeatures = JexlFeatures.createDefault(); + final JexlFeatures baseFeatures = JexlFeatures.createDefault(); Assert.assertFalse(baseFeatures.isLexical()); Assert.assertFalse(baseFeatures.isLexicalShade()); Assert.assertFalse(baseFeatures.supportsConstCapture()); @@ -337,14 +339,11 @@ public class FeaturesTest extends JexlTestCase { scriptFeatures.lexical(false); Assert.assertFalse(scriptFeatures.isLexical()); Assert.assertFalse(scriptFeatures.isLexicalShade()); - - scriptFeatures.constCapture(false); - Assert.assertEquals(baseFeatures, scriptFeatures); } @Test public void testCreate() { - final JexlFeatures f = JexlFeatures.create(); + final JexlFeatures f = JexlFeatures.createNone(); Assert.assertTrue(f.supportsExpression()); Assert.assertFalse(f.supportsAnnotation()); @@ -372,4 +371,46 @@ public class FeaturesTest extends JexlTestCase { Assert.assertNotNull(jnof.createExpression("3 + 4")); } + @Test + public void test410a() { + long x = JexlFeatures.ALL_FEATURES; + Assert.assertEquals(23, Long.bitCount(x)); + Assert.assertTrue((x & (1L << CONST_CAPTURE)) != 0); + + JexlFeatures all = JexlFeatures.createAll(); + final JexlEngine jexl = new JexlBuilder().features(all).create(); + JexlScript script = jexl.createScript("#0 * #1", "#0", "#1"); + Object r = script.execute(null, 6, 7); + Assert.assertEquals(42, r); + } + + @Test + public void test410b() { + JexlFeatures features = JexlFeatures.createScript(); + Assert.assertTrue(features.isLexical()); + Assert.assertTrue(features.isLexicalShade()); + Assert.assertTrue(features.supportsConstCapture()); + //features.pragmaAnywhere(false); + Assert.assertFalse(features.supportsPragmaAnywhere()); + //features.comparatorNames(false); + Assert.assertFalse(features.supportsComparatorNames()); + + final JexlEngine jexl = new JexlBuilder().features(features).create(); + for(String varName : JexlFeatures.RESERVED_WORDS) { + String src = "var " + varName; + //JexlScript script = jexl.createScript(src); + Assert.assertThrows(JexlException.Feature.class, () -> jexl.createScript(src)); + } + final String[] cmpNameScripts = { + "1 eq 1", + "2 ne 3", + "1 lt 2", + "3 le 3", + "4 gt 2", + "3 ge 2" + }; + for(String src : cmpNameScripts) { + Assert.assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src)); + } + } }