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 23c4577  JEXL-279: refined logic to discriminate undefined vs null 
variables or properties
23c4577 is described below

commit 23c45771d63da0746d86c83d20b8b2f4019d4598
Author: henrib <[email protected]>
AuthorDate: Wed Dec 26 18:09:10 2018 +0100

    JEXL-279: refined logic to discriminate undefined vs null variables or 
properties
---
 .../apache/commons/jexl3/internal/Debugger.java    | 31 ++++++++
 .../apache/commons/jexl3/internal/Interpreter.java | 89 +++++++++++++---------
 .../commons/jexl3/internal/InterpreterBase.java    | 42 +++++-----
 .../org/apache/commons/jexl3/ExceptionTest.java    |  8 +-
 .../org/apache/commons/jexl3/Issues200Test.java    | 85 ++++++++++++++++-----
 .../java/org/apache/commons/jexl3/IssuesTest.java  |  4 +-
 6 files changed, 176 insertions(+), 83 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java 
b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
index cfc4dbc..59a9743 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -123,6 +123,8 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
     protected int indentLevel = 0;
     /** Perform indentation?. */
     protected int indent = 2;
+    /** accept() relative depth. */
+    protected int depth = Integer.MAX_VALUE;
 
     /**
      * Creates a Debugger.
@@ -140,6 +142,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         end = 0;
         indentLevel = 0;
         indent = 2;
+        depth = 0;
     }
     
     /**
@@ -249,12 +252,32 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      * @param level the number of spaces for indentation, none if less or 
equal to zero
      */
     public void setIndentation(int level) {
+        indentation(level);
+    }
+    
+    /**
+     * Sets the indentation level.
+     * @param level the number of spaces for indentation, none if less or 
equal to zero
+     * @return this debugger instance
+     */
+    public Debugger indentation(int level) {
         if (level <= 0) {
             indent = 0;
         } else {
             indent = level;
         }
         indentLevel = 0;
+        return this;
+    }
+    
+    /**
+     * Sets this debugger relative maximum depth.
+     * @param rdepth the maximum relative depth from the debugged node
+     * @return this debugger instance
+     */
+    public Debugger depth(int rdepth) {
+        this.depth = rdepth;
+        return this;
     }
 
     /**
@@ -264,10 +287,16 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
      * @return visitor pattern value
      */
     protected Object accept(JexlNode node, Object data) {
+        if (depth <= 0) {
+            builder.append("...");
+            return data;
+        }
         if (node == cause) {
             start = builder.length();
         }
+        depth -= 1;
         Object value = node.jjtAccept(this, data);
+        depth += 1;
         if (node == cause) {
             end = builder.length();
         }
@@ -289,7 +318,9 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
                 }
             }
         }
+        depth -= 1;
         Object value = accept(child, data);
+        depth += 1;
         // blocks, if, for & while dont need a ';' at end
         if (!(child instanceof ASTJexlScript
             || child instanceof ASTBlock
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 76d3b68..ac48721 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -1019,7 +1019,7 @@ public class Interpreter extends InterpreterBase {
         for (int i = 0; i < numChildren; i++) {
             JexlNode nindex = node.jjtGetChild(i);
             if (object == null) {
-                return null;
+                return unsolvableProperty(nindex, stringifyProperty(nindex), 
false, null);
             }
             Object index = nindex.jjtAccept(this, null);
             cancelCheck(node);
@@ -1039,7 +1039,7 @@ public class Interpreter extends InterpreterBase {
                 && node.jjtGetChild(which) instanceof ASTIdentifier
                 && ((ASTIdentifier) node.jjtGetChild(which)).getSymbol() >= 0);
     }
-
+    
     /**
      * Evaluates an access identifier based on the 2 main implementations;
      * static (name or numbered identifier) or dynamic (jxlt).
@@ -1082,7 +1082,7 @@ public class Interpreter extends InterpreterBase {
         Object id = evalIdentifier(node);
         return getAttribute(data, id, node);
     }
-
+    
     @Override
     protected Object visit(ASTReference node, Object data) {
         cancelCheck(node);
@@ -1090,7 +1090,7 @@ public class Interpreter extends InterpreterBase {
         final JexlNode parent = node.jjtGetParent();
         // pass first piece of data in and loop through children
         Object object = null;
-        JexlNode objectNode = null;
+        JexlNode objectNode;
         JexlNode ptyNode = null;
         StringBuilder ant = null;
         boolean antish = !(parent instanceof ASTReference);
@@ -1104,13 +1104,17 @@ public class Interpreter extends InterpreterBase {
                     if (ant != null) {
                         JexlNode child = objectNode.jjtGetChild(0);
                         if (child instanceof ASTIdentifierAccess) {
+                            int alen = ant.length();
                             ant.append('.');
                             ant.append(((ASTIdentifierAccess) 
child).getName());
                             object = context.get(ant.toString());
                             if (object != null) {
                                 object = visit((ASTMethodNode) objectNode, 
object, context);
+                                continue;
+                            } else {
+                                // remove method name from antish
+                                ant.delete(alen, ant.length());
                             }
-                            continue;
                         }
                     }
                     break;
@@ -1119,7 +1123,6 @@ public class Interpreter extends InterpreterBase {
                 }
             } else if (objectNode instanceof ASTArrayAccess) {
                 if (object == null) {
-                    ptyNode = objectNode;
                     break;
                 } else {
                     antish = false;
@@ -1131,55 +1134,65 @@ public class Interpreter extends InterpreterBase {
             if (object != null) {
                 // disallow mixing antish variable & bean with same root; 
avoid ambiguity
                 antish = false;
-            } else if (antish) {  // if we still have a null object, check for 
an antish variable
-                if (ant == null) {
-                    JexlNode first = node.jjtGetChild(0);
-                    if (first instanceof ASTIdentifier) {
-                        ASTIdentifier afirst = (ASTIdentifier) first;
-                        ant = new StringBuilder(afirst.getName());
-                    } else {
-                        ptyNode = objectNode;
-                        break;
+            } else if (antish) {
+                // skip the first node case since it was trialed in jjtAccept 
above and returned null
+                if (c > 0) {
+                    // create first from first node
+                    if (ant == null) {
+                        // if we still have a null object, check for an antish 
variable
+                        JexlNode first = node.jjtGetChild(0);
+                        if (first instanceof ASTIdentifier) {
+                            ASTIdentifier afirst = (ASTIdentifier) first;
+                            ant = new StringBuilder(afirst.getName());
+                        } else {
+                            // not an identifier, not antish
+                            ptyNode = objectNode;
+                            break main;
+                        }
                     }
-                }
-                for (; v <= c; ++v) {
-                    JexlNode child = node.jjtGetChild(v);
-                    if (child instanceof ASTIdentifierAccess) {
-                        ASTIdentifierAccess achild = (ASTIdentifierAccess) 
child;
-                        if (achild.isSafe() || achild.isExpression()) {
-                           break main;
+                    // catch up
+                    for (; v <= c; ++v) {
+                        JexlNode child = node.jjtGetChild(v);
+                        if (child instanceof ASTIdentifierAccess) {
+                            ASTIdentifierAccess achild = (ASTIdentifierAccess) 
child;
+                            if (achild.isSafe() || achild.isExpression()) {
+                                break main;
+                            }
+                            ant.append('.');
+                            ant.append(((ASTIdentifierAccess) 
objectNode).getName());
+                        } else {
+                            // not an identifier, not antish
+                            ptyNode = objectNode;
+                            break main;
                         }
-                        ant.append('.');
-                        ant.append(((ASTIdentifierAccess) 
objectNode).getName());
-                    } else {
-                        break main;
                     }
+                    object = context.get(ant.toString());
                 }
-                object = context.get(ant.toString());
-            } else {
-                // the last one may be null
-                ptyNode = c != numChildren - 1? objectNode : null;
+            } else if (c != numChildren - 1) {
+                // only the last one may be null
+                ptyNode = objectNode;
                 break; //
             }
         }
         if (object == null && !node.isTernaryProtected()) {
-            if (antish && ant != null) {
-                boolean undefined = !(context.has(ant.toString()) || 
isLocalVariable(node, 0));
-                // variable unknown in context and not a local
-                return node.isSafeLhs(jexl.safe)
-                        ? null
-                        : unsolvableVariable(node, ant.toString(), undefined);
-            }
             if (ptyNode != null) {
                 // am I the left-hand side of a safe op ?
                 return ptyNode.isSafeLhs(jexl.safe)
                        ? null
                        : unsolvableProperty(node, stringifyProperty(ptyNode), 
false, null);
             }
+            if (antish) {
+                String aname = ant != null? ant.toString() : 
stringifyProperty(node);
+                boolean undefined = !(context.has(aname) || 
isLocalVariable(node, 0));
+                // variable unknown in context and not a local
+                return node.isSafeLhs(jexl.safe)
+                        ? null
+                        : unsolvableVariable(node, undefined? 
stringifyProperty(node) : aname, undefined);
+            }
         }
         return object;
     }
-
+    
     @Override
     protected Object visit(ASTAssignment node, Object data) {
         return executeAssign(node, null, data);
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java 
b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index e8a0287..b5d44fc 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -26,7 +26,9 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.parser.ASTArrayAccess;
 import org.apache.commons.jexl3.parser.ASTFunctionNode;
+import org.apache.commons.jexl3.parser.ASTIdentifier;
 import org.apache.commons.jexl3.parser.ASTMethodNode;
+import org.apache.commons.jexl3.parser.ASTReference;
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.ParserVisitor;
 
@@ -236,29 +238,33 @@ public abstract class InterpreterBase extends 
ParserVisitor {
      */
     protected String stringifyProperty(JexlNode node) {
         if (node instanceof ASTArrayAccess) {
-            if (node.jjtGetChild(0) != null) {
-                return "["
-                       + node.jjtGetChild(0).toString()
-                       + "]";
-            }
-            return "[???]";
+            return "["
+                    + stringifyPropertyValue(node.jjtGetChild(0))
+                    + "]";
         }
         if (node instanceof ASTMethodNode) {
-            if (node.jjtGetChild(0) != null) {
-                return "."
-                       + node.jjtGetChild(0).toString()
-                       + "(...)";
-            }
-            return ".???(...)";
+            return stringifyPropertyValue(node.jjtGetChild(0));
         }
         if (node instanceof ASTFunctionNode) {
-            if (node.jjtGetChild(0) != null) {
-                return node.jjtGetChild(0).toString()
-                       + "(...)";
-            }
-            return "???(...)";
+            return stringifyPropertyValue(node.jjtGetChild(0));
+        }
+        if (node instanceof ASTIdentifier) {
+            return ((ASTIdentifier) node).getName();
+        }
+        if (node instanceof ASTReference) {
+            return stringifyPropertyValue(node.jjtGetChild(0));
         }
-        return node.toString();
+        return stringifyPropertyValue(node);
+    }
+        
+    /**
+     * Pretty-prints a failing property value (de)reference.
+     * <p>Used by calls to unsolvableProperty(...).</p>
+     * @param node the property node
+     * @return the (pretty) string value
+     */
+    protected static String stringifyPropertyValue(JexlNode node) {
+        return node != null? new Debugger().depth(1).data(node) : "???";
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java 
b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index b565eff..fc1b555 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -132,10 +132,10 @@ public class ExceptionTest extends JexlTestCase {
         // empty cotext
         try {
             /* Object o = */ e.evaluate(ctxt);
-            Assert.fail("c.e not defined as variable should throw");
+            Assert.fail("c not defined as variable should throw");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("c.e") > 0);
+            Assert.assertTrue(msg.indexOf("c") > 0);
         }
 
         // disallow null operands
@@ -212,10 +212,10 @@ public class ExceptionTest extends JexlTestCase {
         // empty cotext
         try {
             /* Object o = */ e.evaluate(ctxt);
-            Assert.fail("c.e not declared as variable should throw");
+            Assert.fail("c not declared as variable should throw");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("c.e") > 0);
+            Assert.assertTrue(msg.indexOf("c") > 0);
         }
 
         // disallow null operands
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index 8d520aa..27a9ff3 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -32,6 +32,8 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -631,39 +633,80 @@ public class Issues200Test extends JexlTestCase {
             Assert.assertEquals(src, ctls[i], value);
         }
     }
+    
+    public static class Context279 extends MapContext {
+        public String identity(String x) {
+            return x;
+        }
+        public Number identity(Number x) {
+            return x;
+        }
+    }
 
     @Test
     public void test279() throws Exception {
+        final Log logger = null;//LogFactory.getLog(Issues200Test.class);
         Object result;
         JexlScript script;
-        JexlContext ctxt = new MapContext();
-        ctxt.set("y.z", null);
-        ctxt.set("z", null);
+        JexlContext ctxt = new Context279();
         String[] srcs = new String[]{
-             "var x = null; x[0];",
-             "var x = null; x.0;",
+            "var z = null; identity(z[0]);",
+             "var z = null; z.0;",
+             "var z = null; z.foo();",
+            "z['y']['z']",
+            "z.y.any()",
+             "identity(z.any())",
              "z[0]",
              "z.0",
-             "y.z[0]",
-             "y.z.0",
+             "z.foo()",
+             "z.y[0]",
+             "z.y[0].foo()",
+             "z.y.0",
+             "z.y.foo()",
+             "var z = { 'y' : [42] }; z.y[1]",
+             "var z = { 'y' : [42] }; z.y.1",
+             "var z = { 'y' : [-42] }; z.y[1].foo()",
+             "var z = { 'y' : [42] }; z.y.1.foo()",
+             "var z = { 'y' : [null, null] }; z.y[1].foo()",
+             "var z = { 'y' : [null, null] }; z.y.1.foo()"
         };
-        for(boolean strict : new boolean[]{ true, false} ) {
-            JexlEngine jexl = new 
JexlBuilder().safe(false).strict(strict).create();
-            for (String src : srcs) {
-                script = jexl.createScript(src);
-                try {
-                    result = script.execute(ctxt);
-                    if (strict) {
-                        Assert.fail("should have failed: " + src);
+        for (int i = 0; i < 2; ++i) {
+            for (boolean strict : new boolean[]{true, false}) {
+                JexlEngine jexl = new 
JexlBuilder().safe(false).strict(strict).create();
+                for (String src : srcs) {
+                    script = jexl.createScript(src);
+                    try {
+                        result = script.execute(ctxt);
+                        if (strict) {
+                            if (logger != null) {
+                                logger.warn(ctxt.has("z") + ": " + src + ": no 
fail, " + result);
+                            }
+                            Assert.fail("should have failed: " + src);
+                        }
+                        // not reachable
+                        Assert.assertNull("non-null result ?!", result);
+                    } catch (JexlException.Variable xvar) {
+                        if (logger != null) {
+                            logger.warn(ctxt.has("z") + ": " + src + ": fail, 
" + xvar);
+                        }
+                        if (!strict) {
+                            Assert.fail(src + ", should not have thrown " + 
xvar);
+                        } else {
+                            Assert.assertTrue(src + ": " + xvar.toString(), 
xvar.toString().contains("z"));
+                        }
+                    } catch (JexlException.Property xprop) {
+                        if (logger != null) {
+                            logger.warn(ctxt.has("z") + ": " + src + ": fail, 
" + xprop);
+                        }
+                        if (!strict) {
+                            Assert.fail(src + ", should not have thrown " + 
xprop);
+                        } else {
+                            Assert.assertTrue(src + ": " + xprop.toString(), 
xprop.toString().contains("1"));
+                        }
                     }
-                    // not reachable
-                    Assert.assertNull("non-null result ?!", result);
-                } catch (JexlException xany) {
-                   if (!strict) {
-                       Assert.fail(src + ", should not have thrown " + xany);
-                   }
                 }
             }
+            ctxt.set("z.y", null);
         }
     }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest.java 
b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
index 477afa5..a941fa2 100644
--- a/src/test/java/org/apache/commons/jexl3/IssuesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
@@ -266,10 +266,10 @@ public class IssuesTest extends JexlTestCase {
         e = jexl.createExpression("c.e");
         try {
             /* Object o = */ e.evaluate(ctxt);
-            Assert.fail("c.e not declared as variable");
+            Assert.fail("c not declared as variable");
         } catch (JexlException.Variable xjexl) {
             String msg = xjexl.getMessage();
-            Assert.assertTrue(msg.indexOf("c.e") > 0);
+            Assert.assertTrue(msg.indexOf("c") > 0);
         }
 
         ctxt.set("c", "{ 'a' : 3, 'b' : 5}");

Reply via email to