Uploaded image for project: 'Commons JEXL'
  1. Commons JEXL
  2. JEXL-350

map[null] throws "unsolvable property" when a Sandbox is used

VotersWatch issueWatchersLinkCloneUpdate Comment AuthorReplace String in CommentUpdate Comment VisibilityDelete Comments
    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 3.1
    • 3.2.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);
      }

       

      Attachments

        Activity

          This comment will be Viewable by All Users Viewable by All Users
          Cancel

          People

            henrib Henri Biestro
            david_costanzo David Costanzo
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Slack

                Issue deployment