You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2019/05/15 16:45:18 UTC
[commons-jexl] branch master updated: JEXL-300: limit antish
resolution to non-safe non-expression cases;
refactored IntrepreterBase/Interpreter for a better technical/functional
split
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 ee40b6e JEXL-300: limit antish resolution to non-safe non-expression cases; refactored IntrepreterBase/Interpreter for a better technical/functional split
ee40b6e is described below
commit ee40b6eeded0acd7057bf9fea75f89139ae4528a
Author: henrib <he...@apache.org>
AuthorDate: Wed May 15 18:44:31 2019 +0200
JEXL-300: limit antish resolution to non-safe non-expression cases; refactored IntrepreterBase/Interpreter for a better technical/functional split
---
RELEASE-NOTES.txt | 1 +
.../apache/commons/jexl3/internal/Interpreter.java | 312 ++++-----------------
.../commons/jexl3/internal/InterpreterBase.java | 228 +++++++++++++++
.../apache/commons/jexl3/internal/Operators.java | 4 +-
src/site/xdoc/changes.xml | 3 +
.../org/apache/commons/jexl3/AntishCallTest.java | 56 ++++
6 files changed, 341 insertions(+), 263 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index df56b3b..aa33c24 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -72,6 +72,7 @@ New Features in 3.2:
Bugs Fixed in 3.2:
==================
+* JEXL-300: Ant-ish variables should not use safe-access operator syntax
* JEXL-299: Improve message error when method could not be found
* JEXL-296: Real literal in scientific format is not parsed without suffix
* JEXL-291: Using sandbox prevents array-syntax lookup by number in Map
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 82ff17d..fbf6c5b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -26,8 +26,6 @@ import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.JxltEngine;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
-import org.apache.commons.jexl3.introspection.JexlPropertySet;
-import org.apache.commons.jexl3.introspection.JexlUberspect.PropertyResolver;
import org.apache.commons.jexl3.parser.ASTAddNode;
import org.apache.commons.jexl3.parser.ASTAndNode;
@@ -109,10 +107,7 @@ import org.apache.commons.jexl3.parser.ASTWhileStatement;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.Node;
-import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
import java.util.concurrent.Callable;
/**
@@ -121,18 +116,10 @@ import java.util.concurrent.Callable;
* @since 2.0
*/
public class Interpreter extends InterpreterBase {
- /** The operators evaluation delegate. */
- protected final Operators operators;
/** Frame height. */
protected int fp = 0;
/** Symbol values. */
protected final Scope.Frame frame;
- /** The context to store/retrieve variables. */
- protected final JexlContext.NamespaceResolver ns;
- /** The map of 'prefix:function' to object resolving as namespaces. */
- protected final Map<String, Object> functions;
- /** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
- protected Map<String, Object> functors;
/**
* The thread local interpreter.
@@ -148,15 +135,7 @@ public class Interpreter extends InterpreterBase {
*/
protected Interpreter(Engine engine, JexlContext aContext, Scope.Frame eFrame) {
super(engine, aContext);
- this.operators = new Operators(this);
this.frame = eFrame;
- if (this.context instanceof JexlContext.NamespaceResolver) {
- ns = ((JexlContext.NamespaceResolver) context);
- } else {
- ns = Engine.EMPTY_NS;
- }
- this.functions = jexl.functions;
- this.functors = null;
}
/**
@@ -166,11 +145,7 @@ public class Interpreter extends InterpreterBase {
*/
protected Interpreter(Interpreter ii, JexlArithmetic jexla) {
super(ii, jexla);
- operators = ii.operators;
frame = ii.frame;
- ns = ii.ns;
- functions = ii.functions;
- functors = ii.functors;
}
/**
@@ -256,81 +231,28 @@ public class Interpreter extends InterpreterBase {
}
return null;
}
-
+
/**
- * Resolves a namespace, eventually allocating an instance using context as constructor argument.
- * <p>
- * The lifetime of such instances span the current expression or script evaluation.</p>
- * @param prefix the prefix name (may be null for global namespace)
- * @param node the AST node
- * @return the namespace instance
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @return the attribute value
*/
- protected Object resolveNamespace(String prefix, JexlNode node) {
- Object namespace;
- // check whether this namespace is a functor
- synchronized (this) {
- if (functors != null) {
- namespace = functors.get(prefix);
- if (namespace != null) {
- return namespace;
- }
- }
- }
- // check if namespace is a resolver
- namespace = ns.resolveNamespace(prefix);
- if (namespace == null) {
- namespace = functions.get(prefix);
- if (prefix != null && namespace == null) {
- throw new JexlException(node, "no such function namespace " + prefix, null);
- }
- }
- // shortcut if ns is known to be not-a-functor
- final boolean cacheable = cache;
- Object cached = cacheable ? node.jjtGetValue() : null;
- if (cached != JexlContext.NamespaceFunctor.class) {
- // allow namespace to instantiate a functor with context if possible, not an error otherwise
- Object functor = null;
- if (namespace instanceof JexlContext.NamespaceFunctor) {
- functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
- } else if (namespace instanceof Class<?> || namespace instanceof String) {
- // attempt to reuse last ctor cached in volatile JexlNode.value
- if (cached instanceof JexlMethod) {
- Object eval = ((JexlMethod) cached).tryInvoke(null, context);
- if (JexlEngine.TRY_FAILED != eval) {
- functor = eval;
- }
- }
- if (functor == null) {
- JexlMethod ctor = uberspect.getConstructor(namespace, context);
- if (ctor != null) {
- try {
- functor = ctor.invoke(namespace, context);
- if (cacheable && ctor.isCacheable()) {
- node.jjtSetValue(ctor);
- }
- } catch (Exception xinst) {
- throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
- }
- }
- }
- }
- // got a functor, store it and return it
- if (functor != null) {
- synchronized (this) {
- if (functors == null) {
- functors = new HashMap<String, Object>();
- }
- functors.put(prefix, functor);
- }
- return functor;
- } else {
- // use the NamespaceFunctor class to tag this node as not-a-functor
- node.jjtSetValue(JexlContext.NamespaceFunctor.class);
- }
- }
- return namespace;
+ public Object getAttribute(Object object, Object attribute) {
+ return getAttribute(object, attribute, null);
}
-
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param value the value to assign to the object's attribute
+ */
+ public void setAttribute(Object object, Object attribute, Object value) {
+ setAttribute(object, attribute, value, null);
+ }
+
@Override
protected Object visit(ASTAddNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
@@ -1183,7 +1105,7 @@ public class Interpreter extends InterpreterBase {
break main;
}
}
- // catch up
+ // catch up to current node
for (; v <= c; ++v) {
JexlNode child = node.jjtGetChild(v);
if (child instanceof ASTIdentifierAccess) {
@@ -1192,13 +1114,14 @@ public class Interpreter extends InterpreterBase {
break main;
}
ant.append('.');
- ant.append(((ASTIdentifierAccess) objectNode).getName());
+ ant.append(achild.getName());
} else {
// not an identifier, not antish
ptyNode = objectNode;
break main;
}
}
+ // solve antish
object = context.get(ant.toString());
}
} else if (c != numChildren - 1) {
@@ -1344,30 +1267,43 @@ public class Interpreter extends InterpreterBase {
StringBuilder ant = null;
int v = 1;
// start at 1 if symbol
- for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
+ main: for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
objectNode = left.jjtGetChild(c);
object = objectNode.jjtAccept(this, object);
if (object != null) {
// disallow mixing antish variable & bean with same root; avoid ambiguity
antish = false;
} else if (antish) {
+ // initialize if first time
if (ant == null) {
JexlNode first = left.jjtGetChild(0);
- if (first instanceof ASTIdentifier && ((ASTIdentifier) first).getSymbol() < 0) {
- ant = new StringBuilder(((ASTIdentifier) first).getName());
+ ASTIdentifier firstId = first instanceof ASTIdentifier
+ ? (ASTIdentifier) first
+ : null;
+ if (firstId != null && firstId.getSymbol() < 0) {
+ ant = new StringBuilder(firstId.getName());
} else {
- break;
+ // ant remains null, object is null, stop solving
+ antish = false;
+ break main;
}
}
+ // catch up to current child
for (; v <= c; ++v) {
JexlNode child = left.jjtGetChild(v);
- if (child instanceof ASTIdentifierAccess) {
+ ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess
+ ? (ASTIdentifierAccess) child
+ : null;
+ // remain antish only if unsafe navigation
+ if (aid != null && !aid.isSafe() && !aid.isExpression()) {
ant.append('.');
- ant.append(((ASTIdentifierAccess) objectNode).getName());
+ ant.append(aid.getName());
} else {
- break;
+ antish = false;
+ break main;
}
}
+ // solve antish
object = context.get(ant.toString());
} else {
throw new JexlException(objectNode, "illegal assignment form");
@@ -1376,14 +1312,16 @@ public class Interpreter extends InterpreterBase {
// 2: last objectNode will perform assignement in all cases
Object property = null;
JexlNode propertyNode = left.jjtGetChild(last);
- if (propertyNode instanceof ASTIdentifierAccess) {
- property = evalIdentifier((ASTIdentifierAccess) propertyNode);
- // deal with antish variable
- if (ant != null && object == null) {
+ ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess
+ ? (ASTIdentifierAccess) propertyNode
+ : null;
+ if (propertyId != null) {
+ // deal with creating/assignining antish variable
+ if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
if (last > 0) {
ant.append('.');
}
- ant.append(String.valueOf(property));
+ ant.append(propertyId.getName());
if (assignop != null) {
Object self = context.get(ant.toString());
right = operators.tryAssignOverload(node, assignop, self, right);
@@ -1398,6 +1336,8 @@ public class Interpreter extends InterpreterBase {
}
return right; // 3
}
+ // property of an object ?
+ property = evalIdentifier(propertyId);
} else if (propertyNode instanceof ASTArrayAccess) {
// can have multiple nodes - either an expression, integer literal or reference
int numChildren = propertyNode.jjtGetNumChildren() - 1;
@@ -1747,156 +1687,6 @@ public class Interpreter extends InterpreterBase {
}
}
- /**
- * Gets an attribute of an object.
- *
- * @param object to retrieve value from
- * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
- * @return the attribute value
- */
- public Object getAttribute(Object object, Object attribute) {
- return getAttribute(object, attribute, null);
- }
-
- /**
- * Gets an attribute of an object.
- *
- * @param object to retrieve value from
- * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
- * @param node the node that evaluated as the object
- * @return the attribute value
- */
- protected Object getAttribute(Object object, Object attribute, JexlNode node) {
- if (object == null) {
- throw new JexlException(node, "object is null");
- }
- cancelCheck(node);
- final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
- ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
- Object result = operators.tryOverload(node, operator, object, attribute);
- if (result != JexlEngine.TRY_FAILED) {
- return result;
- }
- Exception xcause = null;
- try {
- // attempt to reuse last executor cached in volatile JexlNode.value
- if (node != null && cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof JexlPropertyGet) {
- JexlPropertyGet vg = (JexlPropertyGet) cached;
- Object value = vg.tryInvoke(object, attribute);
- if (!vg.tryFailed(value)) {
- return value;
- }
- }
- }
- // resolve that property
- List<PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
- JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
- if (vg != null) {
- Object value = vg.invoke(object);
- // cache executor in volatile JexlNode.value
- if (node != null && cache && vg.isCacheable()) {
- node.jjtSetValue(vg);
- }
- return value;
- }
- } catch (Exception xany) {
- xcause = xany;
- }
- // lets fail
- if (node != null) {
- boolean safe = (node instanceof ASTIdentifierAccess) && ((ASTIdentifierAccess) node).isSafe();
- if (safe) {
- return null;
- } else {
- String attrStr = attribute != null ? attribute.toString() : null;
- return unsolvableProperty(node, attrStr, true, xcause);
- }
- } else {
- // direct call
- String error = "unable to get object property"
- + ", class: " + object.getClass().getName()
- + ", property: " + attribute;
- throw new UnsupportedOperationException(error, xcause);
- }
- }
-
- /**
- * Sets an attribute of an object.
- *
- * @param object to set the value to
- * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
- * @param value the value to assign to the object's attribute
- */
- public void setAttribute(Object object, Object attribute, Object value) {
- setAttribute(object, attribute, value, null);
- }
-
- /**
- * Sets an attribute of an object.
- *
- * @param object to set the value to
- * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
- * @param value the value to assign to the object's attribute
- * @param node the node that evaluated as the object
- */
- protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
- cancelCheck(node);
- final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
- ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
- Object result = operators.tryOverload(node, operator, object, attribute, value);
- if (result != JexlEngine.TRY_FAILED) {
- return;
- }
- Exception xcause = null;
- try {
- // attempt to reuse last executor cached in volatile JexlNode.value
- if (node != null && cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof JexlPropertySet) {
- JexlPropertySet setter = (JexlPropertySet) cached;
- Object eval = setter.tryInvoke(object, attribute, value);
- if (!setter.tryFailed(eval)) {
- return;
- }
- }
- }
- List<PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
- JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
- // if we can't find an exact match, narrow the value argument and try again
- if (vs == null) {
- // replace all numbers with the smallest type that will fit
- Object[] narrow = {value};
- if (arithmetic.narrowArguments(narrow)) {
- vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
- }
- }
- if (vs != null) {
- // cache executor in volatile JexlNode.value
- vs.invoke(object, value);
- if (node != null && cache && vs.isCacheable()) {
- node.jjtSetValue(vs);
- }
- return;
- }
- } catch (Exception xany) {
- xcause = xany;
- }
- // lets fail
- if (node != null) {
- String attrStr = attribute != null ? attribute.toString() : null;
- unsolvableProperty(node, attrStr, true, xcause);
- } else {
- // direct call
- String error = "unable to set object property"
- + ", class: " + object.getClass().getName()
- + ", property: " + attribute
- + ", argument: " + value.getClass().getSimpleName();
- throw new UnsupportedOperationException(error, xcause);
- }
- }
-
@Override
protected Object visit(ASTJxltLiteral node, Object data) {
TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue();
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 3c473c2..c2669e0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -17,16 +17,22 @@
package org.apache.commons.jexl3.internal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.introspection.JexlMethod;
+import org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import org.apache.commons.jexl3.introspection.JexlPropertySet;
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.ASTIdentifierAccess;
import org.apache.commons.jexl3.parser.ASTMethodNode;
import org.apache.commons.jexl3.parser.ASTReference;
import org.apache.commons.jexl3.parser.JexlNode;
@@ -56,6 +62,14 @@ public abstract class InterpreterBase extends ParserVisitor {
protected volatile boolean cancelled = false;
/** Empty parameters for method matching. */
protected static final Object[] EMPTY_PARAMS = new Object[0];
+ /** The context to store/retrieve variables. */
+ protected final JexlContext.NamespaceResolver ns;
+ /** The map of 'prefix:function' to object resolving as namespaces. */
+ protected final Map<String, Object> functions;
+ /** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
+ protected Map<String, Object> functors;
+ /** The operators evaluation delegate. */
+ protected final Operators operators;
/**
* Creates an interpreter base.
@@ -75,6 +89,14 @@ public abstract class InterpreterBase extends ParserVisitor {
+ ", got " + arithmetic.getClass().getSimpleName()
);
}
+ if (this.context instanceof JexlContext.NamespaceResolver) {
+ ns = ((JexlContext.NamespaceResolver) context);
+ } else {
+ ns = Engine.EMPTY_NS;
+ }
+ this.functions = jexl.functions;
+ this.functors = null;
+ this.operators = new Operators(this);
}
/**
@@ -89,6 +111,10 @@ public abstract class InterpreterBase extends ParserVisitor {
context = ii.context;
arithmetic = ii.arithmetic;
cache = ii.cache;
+ ns = ii.ns;
+ functions = ii.functions;
+ functors = ii.functors;
+ operators = ii.operators;
}
@@ -125,6 +151,80 @@ public abstract class InterpreterBase extends ParserVisitor {
}
/**
+ * Resolves a namespace, eventually allocating an instance using context as constructor argument.
+ * <p>
+ * The lifetime of such instances span the current expression or script evaluation.</p>
+ * @param prefix the prefix name (may be null for global namespace)
+ * @param node the AST node
+ * @return the namespace instance
+ */
+ protected Object resolveNamespace(String prefix, JexlNode node) {
+ Object namespace;
+ // check whether this namespace is a functor
+ synchronized (this) {
+ if (functors != null) {
+ namespace = functors.get(prefix);
+ if (namespace != null) {
+ return namespace;
+ }
+ }
+ }
+ // check if namespace is a resolver
+ namespace = ns.resolveNamespace(prefix);
+ if (namespace == null) {
+ namespace = functions.get(prefix);
+ if (prefix != null && namespace == null) {
+ throw new JexlException(node, "no such function namespace " + prefix, null);
+ }
+ }
+ // shortcut if ns is known to be not-a-functor
+ final boolean cacheable = cache;
+ Object cached = cacheable ? node.jjtGetValue() : null;
+ if (cached != JexlContext.NamespaceFunctor.class) {
+ // allow namespace to instantiate a functor with context if possible, not an error otherwise
+ Object functor = null;
+ if (namespace instanceof JexlContext.NamespaceFunctor) {
+ functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
+ } else if (namespace instanceof Class<?> || namespace instanceof String) {
+ // attempt to reuse last ctor cached in volatile JexlNode.value
+ if (cached instanceof JexlMethod) {
+ Object eval = ((JexlMethod) cached).tryInvoke(null, context);
+ if (JexlEngine.TRY_FAILED != eval) {
+ functor = eval;
+ }
+ }
+ if (functor == null) {
+ JexlMethod ctor = uberspect.getConstructor(namespace, context);
+ if (ctor != null) {
+ try {
+ functor = ctor.invoke(namespace, context);
+ if (cacheable && ctor.isCacheable()) {
+ node.jjtSetValue(ctor);
+ }
+ } catch (Exception xinst) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
+ }
+ }
+ }
+ }
+ // got a functor, store it and return it
+ if (functor != null) {
+ synchronized (this) {
+ if (functors == null) {
+ functors = new HashMap<String, Object>();
+ }
+ functors.put(prefix, functor);
+ }
+ return functor;
+ } else {
+ // use the NamespaceFunctor class to tag this node as not-a-functor
+ node.jjtSetValue(JexlContext.NamespaceFunctor.class);
+ }
+ }
+ return namespace;
+ }
+
+ /**
* Gets a value of a defined local variable or from the context.
* @param frame the local frame
* @param node the variable node
@@ -693,4 +793,132 @@ public abstract class InterpreterBase extends ParserVisitor {
return unsolvableMethod(node, mname, argv);
}
}
+
+ /**
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param node the node that evaluated as the object
+ * @return the attribute value
+ */
+ protected Object getAttribute(Object object, Object attribute, JexlNode node) {
+ if (object == null) {
+ throw new JexlException(node, "object is null");
+ }
+ cancelCheck(node);
+ final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
+ ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
+ Object result = operators.tryOverload(node, operator, object, attribute);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertyGet) {
+ JexlPropertyGet vg = (JexlPropertyGet) cached;
+ Object value = vg.tryInvoke(object, attribute);
+ if (!vg.tryFailed(value)) {
+ return value;
+ }
+ }
+ }
+ // resolve that property
+ List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
+ JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
+ if (vg != null) {
+ Object value = vg.invoke(object);
+ // cache executor in volatile JexlNode.value
+ if (node != null && cache && vg.isCacheable()) {
+ node.jjtSetValue(vg);
+ }
+ return value;
+ }
+ } catch (Exception xany) {
+ xcause = xany;
+ }
+ // lets fail
+ if (node != null) {
+ boolean safe = (node instanceof ASTIdentifierAccess) && ((ASTIdentifierAccess) node).isSafe();
+ if (safe) {
+ return null;
+ } else {
+ String attrStr = attribute != null ? attribute.toString() : null;
+ return unsolvableProperty(node, attrStr, true, xcause);
+ }
+ } else {
+ // direct call
+ String error = "unable to get object property"
+ + ", class: " + object.getClass().getName()
+ + ", property: " + attribute;
+ throw new UnsupportedOperationException(error, xcause);
+ }
+ }
+
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param value the value to assign to the object's attribute
+ * @param node the node that evaluated as the object
+ */
+ protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
+ cancelCheck(node);
+ final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
+ ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
+ Object result = operators.tryOverload(node, operator, object, attribute, value);
+ if (result != JexlEngine.TRY_FAILED) {
+ return;
+ }
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertySet) {
+ JexlPropertySet setter = (JexlPropertySet) cached;
+ Object eval = setter.tryInvoke(object, attribute, value);
+ if (!setter.tryFailed(eval)) {
+ return;
+ }
+ }
+ }
+ List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
+ JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
+ // if we can't find an exact match, narrow the value argument and try again
+ if (vs == null) {
+ // replace all numbers with the smallest type that will fit
+ Object[] narrow = {value};
+ if (arithmetic.narrowArguments(narrow)) {
+ vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
+ }
+ }
+ if (vs != null) {
+ // cache executor in volatile JexlNode.value
+ vs.invoke(object, value);
+ if (node != null && cache && vs.isCacheable()) {
+ node.jjtSetValue(vs);
+ }
+ return;
+ }
+ } catch (Exception xany) {
+ xcause = xany;
+ }
+ // lets fail
+ if (node != null) {
+ String attrStr = attribute != null ? attribute.toString() : null;
+ unsolvableProperty(node, attrStr, true, xcause);
+ } else {
+ // direct call
+ String error = "unable to set object property"
+ + ", class: " + object.getClass().getName()
+ + ", property: " + attribute
+ + ", argument: " + value.getClass().getSimpleName();
+ throw new UnsupportedOperationException(error, xcause);
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index a4ab493..5efd656 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -30,7 +30,7 @@ import org.apache.commons.jexl3.parser.JexlNode;
*/
public class Operators {
/** The owner. */
- protected final Interpreter interpreter;
+ protected final InterpreterBase interpreter;
/** The overloaded arithmetic operators. */
protected final JexlArithmetic.Uberspect operators;
@@ -38,7 +38,7 @@ public class Operators {
* Constructor.
* @param owner the owning interpreter
*/
- protected Operators(Interpreter owner) {
+ protected Operators(InterpreterBase owner) {
final JexlArithmetic arithmetic = owner.arithmetic;
final JexlUberspect uberspect = owner.uberspect;
this.interpreter = owner;
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 7c79851..29416bd 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,9 @@
</properties>
<body>
<release version="3.2" date="unreleased">
+ <action dev="henrib" type="fix" issue="JEXL-296" due-to="Dmitri Blinov">
+ Ant-ish variables should not use safe-access operator syntax
+ </action>
<action dev="henrib" type="fix" issue="JEXL-299" due-to="Jarek Cecho">
Improve message error when method could not be found
</action>
diff --git a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
index 5e98149..058abb4 100644
--- a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
@@ -170,5 +170,61 @@ public class AntishCallTest extends JexlTestCase {
Object o2 = check2.execute(jc);
Assert.assertEquals("Result is not 4321", new java.math.BigInteger("4321"), o2);
}
+
+ // JEXL-300
+ @Test
+ public void testSafeAnt() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
+ JexlContext ctxt = new MapContext();
+ ctxt.set("x.y.z", 42);
+ JexlScript script;
+ Object result;
+
+ script = jexl.createScript("x.y.z");
+ result = script.execute(ctxt);
+ Assert.assertEquals(42, result);
+ Assert.assertEquals(42, ctxt.get("x.y.z"));
+
+ result = null;
+ script = jexl.createScript("x?.y?.z");
+ result = script.execute(ctxt);
+ Assert.assertNull(result); // safe navigation, null
+
+ result = null;
+ script = jexl.createScript("x?.y?.z = 3");
+ try {
+ result = script.execute(ctxt);
+ Assert.fail("not antish assign");
+ } catch(JexlException xjexl) {
+ Assert.assertNull(result);
+ }
+
+ result = null;
+ script = jexl.createScript("x.y?.z");
+ try {
+ result = script.execute(ctxt);
+ Assert.fail("x not defined");
+ } catch(JexlException xjexl) {
+ Assert.assertNull(result);
+ }
+
+ result = null;
+ script = jexl.createScript("x.y?.z = 3");
+ try {
+ result = script.execute(ctxt);
+ Assert.fail("not antish assign");
+ } catch(JexlException xjexl) {
+ Assert.assertNull(result);
+ }
+
+ result = null;
+ script = jexl.createScript("x.`'y'`.z = 3");
+ try {
+ result = script.execute(ctxt);
+ Assert.fail("not antish assign");
+ } catch(JexlException xjexl) {
+ Assert.assertNull(result);
+ }
+ }
}
\ No newline at end of file