Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Fixed
-
3.1
-
None
-
jdk-11.0.5_10-hotspot
JEXL git pull on 2021-06-03
Description
In JEXL 3.2 SNAPSHOT, you can can access a null property in a map if you have no sandbox, but you cannot access a null property in a map if you have an "allow box" sandbox. This asymmetry is weird and might be an oversight.
The fix for JEXL-327 allows setting a null property in a map using the array syntax, but only for the case where there is no sandbox. The fix for JEXL-291 allows array-style access to values in a map when using a sandbox. What remains is the intersection of these two use cases: accessing a null property in a map using the array-access syntax when there's a sandbox.
Impact:
In my domain (working with data from clinical trials), a null numeric value means "missing", which is a valid value. Our JEXL programmers implement "switch" statements using a map, so you might see an expression that translates a coded variable named "DMSEX" into an English description like:
{ null : "Unknown", 1 : "MALE", 2 : "FEMALE" }[DMSEX]
Not being able to read the "Unknown" value when DMSEX=null is a problem that we've had to work around by copying the internal class SandboxUberspect and hacking it to allow this.
Technical Details:
I think the problem starts in SandboxUbserspect.getPropertyGet() because identifier is null. In this case, the most of the logic is skipped and null is returned, indicating that the property is undefined.
@Override public JexlPropertyGet getPropertyGet(final List<PropertyResolver> resolvers, final Object obj, final Object identifier) { if (obj != null && identifier != null) { final String property = identifier.toString(); final String actual = sandbox.read(obj.getClass(), property); if (actual != null) { // no transformation, strict equality: use identifier before string conversion final Object pty = actual == property? identifier : actual; return uberspect.getPropertyGet(resolvers, obj, pty); } } return null; }
In my superficial understanding, the Sandbox.read() method doesn't have a way to distinguish between "null is the property you want to read" and "access is blocked" so in my hack, I had to always allow read access of null.
Steps to Reproduce:
Below are some unit tests which show the four possibilities of with/without sandbox and get/set null. I've included the behavior in JEXL 3.1 and a recent build of github as comments.
@Test public void testGetNullKeyWithNoSandbox() throws Exception { JexlEngine jexl = new JexlBuilder().create(); JexlContext jc = new MapContext(); JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]"); // JEXL 3.1, works // JEXL 3.2, works Object o = expression.evaluate(jc); Assert.assertEquals("foo", o); } @Test public void testGetNullKeyWithSandbox() throws Exception { JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create(); JexlContext jc = new MapContext(); JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]"); // JEXL 3.1, throws JexlException$Property "unsolvable property '<?>.<null>'" // JEXL 3.2, throws JexlException$Property "undefined property '<?>.<null>'" Object o = expression.evaluate(jc); Assert.assertEquals("foo", o); } @Test public void testSetNullKeyWithNoSandbox() throws Exception { JexlEngine jexl = new JexlBuilder().create(); JexlContext jc = new MapContext(); JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'"); // JEXL 3.1, throws JexlException$Property "unsolvable property '<?>.<null>'" // JEXL 3.2, works expression.evaluate(jc); } @Test public void testSetNullKeyWithSandbox() throws Exception { JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create(); JexlContext jc = new MapContext(); JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'"); // JEXL 3.1, throws JexlException$Property "unsolvable property '<?>.<null>'" // JEXL 3.2, throws JexlException$Property "undefined property '<?>.<null>'" expression.evaluate(jc); }