View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.Queue;
24  import java.util.concurrent.atomic.AtomicBoolean;
25  
26  import org.apache.commons.jexl3.JexlArithmetic;
27  import org.apache.commons.jexl3.JexlContext;
28  import org.apache.commons.jexl3.JexlContext.NamespaceFunctor;
29  import org.apache.commons.jexl3.JexlEngine;
30  import org.apache.commons.jexl3.JexlException;
31  import org.apache.commons.jexl3.JexlException.VariableIssue;
32  import org.apache.commons.jexl3.JexlOperator;
33  import org.apache.commons.jexl3.JexlOptions;
34  import org.apache.commons.jexl3.introspection.JexlMethod;
35  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
36  import org.apache.commons.jexl3.introspection.JexlPropertySet;
37  import org.apache.commons.jexl3.introspection.JexlUberspect;
38  import org.apache.commons.jexl3.parser.ASTArrayAccess;
39  import org.apache.commons.jexl3.parser.ASTAssignment;
40  import org.apache.commons.jexl3.parser.ASTFunctionNode;
41  import org.apache.commons.jexl3.parser.ASTIdentifier;
42  import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
43  import org.apache.commons.jexl3.parser.ASTMethodNode;
44  import org.apache.commons.jexl3.parser.ASTNullpNode;
45  import org.apache.commons.jexl3.parser.ASTReference;
46  import org.apache.commons.jexl3.parser.ASTTernaryNode;
47  import org.apache.commons.jexl3.parser.ASTVar;
48  import org.apache.commons.jexl3.parser.JexlNode;
49  import org.apache.commons.jexl3.parser.ParserVisitor;
50  import org.apache.commons.logging.Log;
51  
52  /**
53   * The helper base of an interpreter of JEXL syntax.
54   *
55   * @since 3.0
56   */
57  public abstract class InterpreterBase extends ParserVisitor {
58  
59      /**
60       * Helping dispatch function calls.
61       */
62      protected class CallDispatcher {
63  
64          /** The syntactic node. */
65          final JexlNode node;
66  
67          /** Whether solution is cacheable. */
68          final boolean cacheable;
69  
70          /** Whether arguments have been narrowed.  */
71          boolean narrow;
72  
73          /** The method to call. */
74          JexlMethod vm;
75  
76          /** The method invocation target. */
77          Object target;
78  
79          /** The actual arguments. */
80          Object[] argv;
81  
82          /** The cacheable funcall if any. */
83          Funcall funcall;
84  
85          /**
86           * Dispatcher ctor.
87           *
88           * @param anode The syntactic node.
89           * @param acacheable whether resolution can be cached
90           */
91          CallDispatcher(final JexlNode anode, final boolean acacheable) {
92              this.node = anode;
93              this.cacheable = acacheable;
94          }
95  
96          /**
97           * Evaluates the method previously dispatched.
98           *
99           * @param methodName The method name
100          * @return The method invocation result
101          * @throws Exception when invocation fails
102          */
103         protected Object eval(final String methodName) throws Exception {
104             // we have either evaluated and returned or might have found a method
105             if (vm != null) {
106                 // vm cannot be null if xjexl is null
107                 final Object eval = vm.invoke(target, argv);
108                 // cache executor in volatile JexlNode.value
109                 if (funcall != null) {
110                     node.jjtSetValue(funcall);
111                 }
112                 return eval;
113             }
114             return unsolvableMethod(node, methodName, argv);
115         }
116 
117         /**
118          * Whether the method is an arithmetic method.
119          *
120          * @param methodName The method name
121          * @param arguments The method arguments
122          * @return true if arithmetic, false otherwise
123          */
124         protected boolean isArithmeticMethod(final String methodName, final Object[] arguments) {
125             vm = uberspect.getMethod(arithmetic, methodName, arguments);
126             if (vm != null) {
127                 argv = arguments;
128                 target = arithmetic;
129                 if (cacheable && vm.isCacheable()) {
130                     funcall = new ArithmeticFuncall(vm, narrow);
131                 }
132                 return true;
133             }
134             return false;
135         }
136 
137         /**
138          * Whether the method is a context method.
139          *
140          * @param methodName The method name
141          * @param arguments The method arguments
142          * @return true if arithmetic, false otherwise
143          */
144         protected boolean isContextMethod(final String methodName, final Object[] arguments) {
145             vm = uberspect.getMethod(context, methodName, arguments);
146             if (vm != null) {
147                 argv = arguments;
148                 target = context;
149                 if (cacheable && vm.isCacheable()) {
150                     funcall = new ContextFuncall(vm, narrow);
151                 }
152                 return true;
153             }
154             return false;
155         }
156 
157         /**
158          * Whether the method is a target method.
159          *
160          * @param ntarget The target instance
161          * @param methodName The method name
162          * @param arguments The method arguments
163          * @return true if arithmetic, false otherwise
164          */
165         protected boolean isTargetMethod(final Object ntarget, final String methodName, final Object[] arguments) {
166             // try a method
167             vm = uberspect.getMethod(ntarget, methodName, arguments);
168             if (vm != null) {
169                 argv = arguments;
170                 target = ntarget;
171                 if (cacheable && vm.isCacheable()) {
172                     funcall = new Funcall(vm, narrow);
173                 }
174                 return true;
175             }
176             return false;
177         }
178 
179         /**
180          * Attempt to reuse last funcall cached in volatile JexlNode.value (if
181          * it was cacheable).
182          *
183          * @param ntarget The target instance
184          * @param methodName The method name
185          * @param arguments The method arguments
186          * @return TRY_FAILED if invocation was not possible or failed, the
187          * result otherwise
188          */
189         protected Object tryEval(final Object ntarget, final String methodName, final Object[] arguments) {
190             // do we have  a method/function name ?
191             // attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
192             if (methodName != null && cacheable && ntarget != null) {
193                 final Object cached = node.jjtGetValue();
194                 if (cached instanceof Funcall) {
195                     return ((Funcall) cached).tryInvoke(InterpreterBase.this, methodName, ntarget, arguments);
196                 }
197             }
198             return JexlEngine.TRY_FAILED;
199         }
200     }
201 
202     /**
203      * Cached arithmetic function call.
204      */
205     protected static class ArithmeticFuncall extends Funcall {
206 
207         /**
208          * Constructs a new instance.
209          *
210          * @param jme  The method
211          * @param flag The narrow flag
212          */
213         protected ArithmeticFuncall(final JexlMethod jme, final boolean flag) {
214             super(jme, flag);
215         }
216 
217         @Override
218         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
219             return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
220         }
221     }
222 
223     /**
224      * Cached context function call.
225      */
226     protected static class ContextFuncall extends Funcall {
227 
228         /**
229          * Constructs a new instance.
230          *
231          * @param jme  The method
232          * @param flag The narrow flag
233          */
234         protected ContextFuncall(final JexlMethod jme, final boolean flag) {
235             super(jme, flag);
236         }
237 
238         @Override
239         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
240             return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
241         }
242     }
243 
244     /**
245      * A ctor that needs a context as 1st argument.
246      */
247     protected static class ContextualCtor extends Funcall {
248 
249         /**
250          * Constructs a new instance.
251          *
252          * @param jme The method
253          * @param flag The narrow flag
254          */
255         protected ContextualCtor(final JexlMethod jme, final boolean flag) {
256             super(jme, flag);
257         }
258 
259         @Override
260         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
261             return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
262         }
263     }
264 
265     /**
266      * Cached function call.
267      */
268     protected static class Funcall implements JexlNode.Funcall {
269 
270         /** Whether narrow should be applied to arguments. */
271         protected final boolean narrow;
272 
273         /** The JexlMethod to delegate the call to. */
274         protected final JexlMethod me;
275 
276         /**
277          * Constructs a new instance.
278          *
279          * @param jme  The method
280          * @param flag The narrow flag
281          */
282         protected Funcall(final JexlMethod jme, final boolean flag) {
283             this.me = jme;
284             this.narrow = flag;
285         }
286 
287         /**
288          * Try invocation.
289          *
290          * @param ii     The interpreter
291          * @param name   The method name
292          * @param target The method target
293          * @param args   The method arguments
294          * @return The method invocation result (or JexlEngine.TRY_FAILED)
295          */
296         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
297             return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
298         }
299     }
300 
301     /** Empty parameters for method matching. */
302     protected static final Object[] EMPTY_PARAMS = {};
303 
304     /**
305      * Pretty-prints a failing property value (de)reference.
306      * <p>Used by calls to unsolvableProperty(...).</p>
307      *
308      * @param node The property node
309      * @return The (pretty) string value
310      */
311     protected static String stringifyPropertyValue(final JexlNode node) {
312         return node != null ? new Debugger().depth(1).data(node) : "???";
313     }
314 
315     /** The JEXL engine. */
316     protected final Engine jexl;
317 
318     /** The logger. */
319     protected final Log logger;
320 
321     /** The uberspect. */
322     protected final JexlUberspect uberspect;
323 
324     /** The arithmetic handler. */
325     protected final JexlArithmetic arithmetic;
326 
327     /** The context to store/retrieve variables. */
328     protected final JexlContext context;
329 
330     /** The options. */
331     protected final JexlOptions options;
332 
333     /** Cache executors. */
334     protected final boolean cache;
335 
336     /** Cancellation support. */
337     protected final AtomicBoolean cancelled;
338 
339     /** The namespace resolver. */
340     protected final JexlContext.NamespaceResolver ns;
341 
342     /** The class name resolver. */
343     protected final JexlUberspect.ClassNameResolver fqcnSolver;
344 
345     /** The operators evaluation delegate. */
346     protected final JexlOperator.Uberspect operators;
347 
348     /** The map of 'prefix:function' to object resolving as namespaces. */
349     protected final Map<String, Object> functions;
350 
351     /** The map of dynamically created namespaces, NamespaceFunctor or duck-types of those. */
352     protected Map<String, Object> functors;
353 
354     /**
355      * Creates an interpreter base.
356      *
357      * @param engine   The engine creating this interpreter
358      * @param opts     The evaluation options
359      * @param aContext The evaluation context
360      */
361     protected InterpreterBase(final Engine engine, final JexlOptions opts, final JexlContext aContext) {
362         this.jexl = engine;
363         this.logger = jexl.logger;
364         this.uberspect = jexl.uberspect;
365         this.context = aContext != null ? aContext : JexlEngine.EMPTY_CONTEXT;
366         this.cache = engine.cache != null;
367         final JexlArithmetic jexla = jexl.arithmetic;
368         this.options = opts == null ? engine.evalOptions(aContext) : opts;
369         this.arithmetic = jexla.options(options);
370         if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass()) && logger.isWarnEnabled()) {
371             logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
372                     + ", got " + arithmetic.getClass().getSimpleName()
373             );
374         }
375         if (this.context instanceof JexlContext.NamespaceResolver) {
376             ns = (JexlContext.NamespaceResolver) context;
377         } else {
378             ns = JexlEngine.EMPTY_NS;
379         }
380         AtomicBoolean acancel = null;
381         if (this.context instanceof JexlContext.CancellationHandle) {
382             acancel = ((JexlContext.CancellationHandle) context).getCancellation();
383         }
384         this.cancelled = acancel != null ? acancel : new AtomicBoolean();
385         this.functions = options.getNamespaces();
386         this.functors = null;
387         JexlOperator.Uberspect ops = uberspect.getOperator(arithmetic);
388         if (ops == null) {
389             ops = new Operator(uberspect, arithmetic);
390         }
391         this.operators = ops;
392         // the import package facility
393         this.fqcnSolver = engine.createConstantResolver(options.getImports());
394     }
395 
396     /**
397      * Copy constructor.
398      *
399      * @param ii The base to copy
400      * @param jexla The arithmetic instance to use (or null)
401      */
402     protected InterpreterBase(final InterpreterBase ii, final JexlArithmetic jexla) {
403         jexl = ii.jexl;
404         logger = ii.logger;
405         uberspect = ii.uberspect;
406         arithmetic = jexla;
407         context = ii.context;
408         options = ii.options.copy();
409         cache = ii.cache;
410         ns = ii.ns;
411         operators = ii.operators;
412         cancelled = ii.cancelled;
413         functions = ii.functions;
414         functors = ii.functors;
415         fqcnSolver = ii.fqcnSolver;
416     }
417 
418     /**
419      * Triggered when an annotation processing fails.
420      *
421      * @param node     The node where the error originated from
422      * @param annotation The annotation name
423      * @param cause    The cause of error (if any)
424      * @return throws a JexlException if strict and not silent, null otherwise
425      */
426     protected Object annotationError(final JexlNode node, final String annotation, final Throwable cause) {
427         if (isStrictEngine()) {
428             throw new JexlException.Annotation(node, annotation, cause);
429         }
430         if (logger.isDebugEnabled()) {
431             logger.debug(JexlException.annotationError(node, annotation), cause);
432         }
433         return null;
434     }
435 
436     /**
437      * Concatenate arguments in call(...).
438      *
439      * @param target The pseudo-method owner, first to-be argument
440      * @param narrow whether we should attempt to narrow number arguments
441      * @param args   The other (non-null) arguments
442      * @return The arguments array
443      */
444     protected Object[] callArguments(final Object target, final boolean narrow, final Object[] args) {
445         // makes target 1st args, copy others - optionally narrow numbers
446         final Object[] nargv = new Object[args.length + 1];
447         if (narrow) {
448             nargv[0] = functionArgument(true, target);
449             for (int a = 1; a <= args.length; ++a) {
450                 nargv[a] = functionArgument(true, args[a - 1]);
451             }
452         } else {
453             nargv[0] = target;
454             System.arraycopy(args, 0, nargv, 1, args.length);
455         }
456         return nargv;
457     }
458 
459     /**
460      * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
461      *
462      * @return false if already cancelled, true otherwise
463      */
464     protected  boolean cancel() {
465         return cancelled.compareAndSet(false, true);
466     }
467 
468     /**
469      * Throws a JexlException.Cancel if script execution was cancelled.
470      *
471      * @param node The node being evaluated
472      */
473     protected void cancelCheck(final JexlNode node) {
474         if (isCancelled()) {
475             throw new JexlException.Cancel(node);
476         }
477     }
478 
479     /**
480      * Attempt to call close() if supported.
481      * <p>This is used when dealing with auto-closeable (duck-like) objects
482      *
483      * @param closeable The object we'd like to close
484      */
485     protected void closeIfSupported(final Object closeable) {
486         if (closeable != null) {
487             final JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
488             if (mclose != null) {
489                 try {
490                     mclose.invoke(closeable, EMPTY_PARAMS);
491                 } catch (final Exception xignore) {
492                     logger.warn(xignore);
493                 }
494             }
495         }
496     }
497 
498     /**
499      * Attempt to call close() if supported.
500      * <p>This is used when dealing with auto-closeable (duck-like) objects
501      *
502      * @param closeables The object queue we'd like to close
503      */
504     protected void closeIfSupported(final Queue<Object> closeables) {
505         for(final Object closeable : closeables) {
506             closeIfSupported(closeable);
507         }
508     }
509 
510     /**
511      * Triggered when a captured variable is const and assignment is attempted.
512      *
513      * @param node  The node where the error originated from
514      * @param variable   The variable name
515      * @return throws JexlException if strict and not silent, null otherwise
516      */
517     protected Object constVariable(final JexlNode node, final String variable) {
518         return variableError(node, variable, VariableIssue.CONST);
519     }
520 
521     /**
522      * Defines a variable.
523      *
524      * @param variable The variable to define
525      * @param frame The frame in which it will be defined
526      * @return true if definition succeeded, false otherwise
527      */
528     protected boolean defineVariable(final ASTVar variable, final LexicalFrame frame) {
529         final int symbol = variable.getSymbol();
530         if (symbol < 0) {
531             return false;
532         }
533         if (variable.isRedefined()) {
534             return false;
535         }
536         return frame.defineSymbol(symbol, variable.isCaptured());
537     }
538 
539     /**
540      * Finds the node causing a NPE for diadic operators.
541      *
542      * @param node  The parent node
543      * @param left  The left argument
544      * @param right The right argument
545      * @return The left, right or parent node
546      */
547     protected JexlNode findNullOperand(final JexlNode node, final Object left, final Object right) {
548         if (left == null) {
549             return node.jjtGetChild(0);
550         }
551         if (right == null) {
552             return node.jjtGetChild(1);
553         }
554         return node;
555     }
556 
557     /**
558      * @deprecated
559      */
560     @Deprecated
561     protected JexlNode findNullOperand(final RuntimeException xrt, final JexlNode node, final Object left, final Object right) {
562         return findNullOperand(node, left, right);
563     }
564 
565     /**
566      * Optionally narrows an argument for a function call.
567      *
568      * @param narrow whether narrowing should occur
569      * @param arg    The argument
570      * @return The narrowed argument
571      */
572     protected Object functionArgument(final boolean narrow, final Object arg) {
573         return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
574     }
575 
576     /**
577      * Concatenate arguments in call(...).
578      * <p>When target == context, we are dealing with a global namespace function call
579      *
580      * @param target The pseudo-method owner, first to-be argument
581      * @param narrow whether we should attempt to narrow number arguments
582      * @param args   The other (non-null) arguments
583      * @return The arguments array
584      */
585     protected Object[] functionArguments(final Object target, final boolean narrow, final Object[] args) {
586         // when target == context, we are dealing with the null namespace
587         if (target == null || target == context) {
588             if (narrow) {
589                 arithmetic.narrowArguments(args);
590             }
591             return args;
592         }
593         // makes target 1st args, copy others - optionally narrow numbers
594         final Object[] nargv = new Object[args.length + 1];
595         if (narrow) {
596             nargv[0] = functionArgument(true, target);
597             for (int a = 1; a <= args.length; ++a) {
598                 nargv[a] = functionArgument(true, args[a - 1]);
599             }
600         } else {
601             nargv[0] = target;
602             System.arraycopy(args, 0, nargv, 1, args.length);
603         }
604         return nargv;
605     }
606 
607     /**
608      * Gets an attribute of an object.
609      *
610      * @param object    to retrieve value from
611      * @param attribute The attribute of the object, e.g. an index (1, 0, 2) or key for a map
612      * @param node      The node that evaluated as the object
613      * @return The attribute value
614      */
615     protected Object getAttribute(final Object object, final Object attribute, final JexlNode node) {
616         if (object == null) {
617             throw new JexlException(node, "object is null");
618         }
619         cancelCheck(node);
620         Exception xcause = null;
621         try {
622             final JexlOperator operator;
623             if (node != null) {
624                 // attempt to reuse last executor cached in volatile JexlNode.value
625                 if (cache) {
626                     final Object cached = node.jjtGetValue();
627                     if (cached instanceof JexlPropertyGet) {
628                         final JexlPropertyGet vg = (JexlPropertyGet) cached;
629                         final Object value = vg.tryInvoke(object, attribute);
630                         if (!vg.tryFailed(value)) {
631                             return value;
632                         }
633                     }
634                 }
635                 // try operator overload
636                 operator = node.jjtGetParent() instanceof ASTArrayAccess ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
637                 final Object result = operators.tryOverload(node, operator, object, attribute);
638                 if (result != JexlEngine.TRY_FAILED) {
639                     return result;
640                 }
641             } else {
642                 operator = JexlOperator.PROPERTY_GET;
643             }
644             // resolve that property
645             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
646             final JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
647             if (vg != null) {
648                 final Object value = vg.invoke(object);
649                 // cache executor in volatile JexlNode.value
650                 if (node != null && cache && vg.isCacheable()) {
651                     node.jjtSetValue(vg);
652                 }
653                 return value;
654             }
655         } catch (final Exception xany) {
656             xcause = xany;
657         }
658         // lets fail
659         if (node == null) {
660             // direct call
661             final String error = "unable to get object property"
662                     + ", class: " + object.getClass().getName()
663                     + ", property: " + attribute;
664             throw new UnsupportedOperationException(error, xcause);
665         }
666         final boolean safe = node instanceof ASTIdentifierAccess && ((ASTIdentifierAccess) node).isSafe();
667         if (safe) {
668             return null;
669         }
670         final String attrStr = Objects.toString(attribute, null);
671         return unsolvableProperty(node, attrStr, true, xcause);
672     }
673 
674     /**
675      * Gets a value of a defined local variable or from the context.
676      *
677      * @param frame The local frame
678      * @param block The lexical block if any
679      * @param identifier The variable node
680      * @return The value
681      */
682     protected Object getVariable(final Frame frame, final LexicalScope block, final ASTIdentifier identifier) {
683         final int symbol = identifier.getSymbol();
684         final String name = identifier.getName();
685         // if we have a symbol, we have a scope thus a frame
686         if ((options.isLexicalShade() || identifier.isLexical()) && identifier.isShaded()) {
687             return undefinedVariable(identifier, name);
688         }
689         // a local var ?
690         if (symbol >= 0 && frame.has(symbol)) {
691             final Object value = frame.get(symbol);
692             // not out of scope with no lexical shade ?
693             if (value != Scope.UNDEFINED) {
694                 // null operand of an arithmetic operator ?
695                 if (value == null && isStrictOperand(identifier)) {
696                     return unsolvableVariable(identifier, name, false); // defined but null
697                 }
698                 return value;
699             }
700         }
701         // consider global
702         final Object value = context.get(name);
703         // is it null ?
704         if (value == null) {
705             // is it defined ?
706             if (!context.has(name)) {
707                 // not defined, ignore in some cases...
708                 final boolean ignore = identifier.jjtGetParent() instanceof ASTReference
709                         || isSafe() && (symbol >= 0 || identifier.jjtGetParent() instanceof ASTAssignment);
710                 if (!ignore) {
711                     return undefinedVariable(identifier, name); // undefined
712                 }
713             } else if (isStrictOperand(identifier)) {
714                 return unsolvableVariable(identifier, name, false); // defined but null
715             }
716         }
717         return value;
718     }
719 
720     /**
721      * Triggered when method, function or constructor invocation fails with an exception.
722      *
723      * @param node       The node triggering the exception
724      * @param methodName The method/function name
725      * @param xany       The cause
726      * @return A JexlException that will be thrown
727      */
728     protected JexlException invocationException(final JexlNode node, final String methodName, final Throwable xany) {
729         final Throwable cause = xany.getCause();
730         if (cause instanceof JexlException) {
731             return (JexlException) cause;
732         }
733         if (cause instanceof InterruptedException) {
734             return new JexlException.Cancel(node);
735         }
736         return new JexlException(node, methodName, xany);
737     }
738 
739     /**
740      * @return true if interrupt throws a JexlException.Cancel.
741      */
742     protected boolean isCancellable() {
743         return options.isCancellable();
744     }
745 
746     /**
747      * Checks whether this interpreter execution was cancelled due to thread interruption.
748      *
749      * @return true if cancelled, false otherwise
750      */
751     protected boolean isCancelled() {
752         return cancelled.get() || Thread.currentThread().isInterrupted();
753     }
754 
755     /**
756      * Whether this interpreter ignores null in navigation expression as errors.
757      *
758      * @return true if safe, false otherwise
759      */
760     protected boolean isSafe() {
761         return options.isSafe();
762     }
763 
764     /**
765      * Whether this interpreter is currently evaluating with a silent mode.
766      *
767      * @return true if silent, false otherwise
768      */
769     protected boolean isSilent() {
770         return options.isSilent();
771     }
772 
773     /**
774      * Whether this interpreter is currently evaluating with a strict engine flag.
775      *
776      * @return true if strict engine, false otherwise
777      */
778     protected boolean isStrictEngine() {
779         return options.isStrict();
780     }
781 
782     /**
783      * @param node The operand node
784      * @return true if this node is an operand of a strict operator, false otherwise
785      */
786     protected boolean isStrictOperand(final JexlNode node) {
787        return node.jjtGetParent().isStrictOperator(arithmetic);
788     }
789 
790     /**
791      * Check if a null evaluated expression is protected by a ternary expression.
792      * <p>
793      * The rationale is that the ternary / elvis expressions are meant for the user to explicitly take control
794      * over the error generation; ie, ternaries can return null even if the engine in strict mode
795      * would normally throw an exception.
796      * </p>
797      *
798      * @return true if nullable variable, false otherwise
799      */
800     protected boolean isTernaryProtected(final JexlNode startNode) {
801         JexlNode node = startNode;
802         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
803             // protect only the condition part of the ternary
804             if (walk instanceof ASTTernaryNode
805                     || walk instanceof ASTNullpNode) {
806                 return node == walk.jjtGetChild(0);
807             }
808             if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
809                 break;
810             }
811             node = walk;
812         }
813         return false;
814     }
815 
816     /**
817      * Checks whether a variable is defined.
818      * <p>The var may be either a local variable declared in the frame and
819      * visible from the block or defined in the context.
820      *
821      * @param frame The frame
822      * @param block The block
823      * @param name The variable name
824      * @return true if variable is defined, false otherwise
825      */
826     protected boolean isVariableDefined(final Frame frame, final LexicalScope block, final String name) {
827         if (frame != null && block != null) {
828             final Integer ref = frame.getScope().getSymbol(name);
829             final int symbol = ref != null ? ref : -1;
830             if (symbol >= 0  && block.hasSymbol(symbol)) {
831                 final Object value = frame.get(symbol);
832                 return value != Scope.UNDEFINED && value != Scope.UNDECLARED;
833             }
834         }
835         return context.has(name);
836     }
837 
838     /**
839      * Triggered when an operator fails.
840      *
841      * @param node     The node where the error originated from
842      * @param operator The operator symbol
843      * @param cause    The cause of error (if any)
844      * @return throws JexlException if strict and not silent, null otherwise
845      */
846     protected Object operatorError(final JexlNode node, final JexlOperator operator, final Throwable cause) {
847         if (isStrictEngine()) {
848             throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
849         }
850         if (logger.isDebugEnabled()) {
851             logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
852         }
853         return null;
854     }
855 
856     /**
857      * Triggered when a variable is lexically known as being redefined.
858      *
859      * @param node  The node where the error originated from
860      * @param variable   The variable name
861      * @return throws JexlException if strict and not silent, null otherwise
862      */
863     protected Object redefinedVariable(final JexlNode node, final String variable) {
864         return variableError(node, variable, VariableIssue.REDEFINED);
865     }
866 
867     /**
868      * Resolves a namespace, eventually allocating an instance using context as constructor argument.
869      * <p>
870      * The lifetime of such instances span the current expression or script evaluation.</p>
871      *
872      * @param prefix The prefix name (can be null for global namespace)
873      * @param node   The AST node
874      * @return The namespace instance
875      */
876     protected Object resolveNamespace(final String prefix, final JexlNode node) {
877         Object namespace;
878         // check whether this namespace is a functor
879         synchronized (this) {
880             if (functors != null) {
881                 namespace = functors.get(prefix);
882                 if (namespace != null) {
883                     return namespace;
884                 }
885             }
886         }
887         // check if namespace is a resolver
888         namespace = ns.resolveNamespace(prefix);
889         if (namespace == null) {
890             namespace = functions.get(prefix);
891             if (namespace == null) {
892                 namespace = jexl.getNamespace(prefix);
893             }
894             if (prefix != null && namespace == null) {
895                 throw new JexlException(node, "no such function namespace " + prefix, null);
896             }
897         }
898         Object functor = null;
899         // class or string (*1)
900         if (namespace instanceof Class<?> || namespace instanceof String) {
901             // the namespace(d) identifier
902             final ASTIdentifier nsNode = (ASTIdentifier) node.jjtGetChild(0);
903             final boolean cacheable = cache && prefix != null;
904             final Object cached = cacheable ? nsNode.jjtGetValue() : null;
905             // we know the class is used as namespace of static methods, no functor
906             if (cached instanceof Class<?>) {
907                 return cached;
908             }
909             // whether reflective auto-instantiation of a functor is allowed for this namespace
910             final boolean instantiate = options.isNamespaceInstantiation();
911             // attempt to reuse last cached constructor
912             if (instantiate && cached instanceof JexlContext.NamespaceFunctor) {
913                 final Object eval = ((JexlContext.NamespaceFunctor) cached).createFunctor(context);
914                 if (JexlEngine.TRY_FAILED != eval) {
915                     functor = eval;
916                     namespace = cached;
917                 }
918             }
919             if (functor == null) {
920                 // find a constructor with that context as argument or without
921                 for (int tried = 0; instantiate && tried < 2; ++tried) {
922                     final boolean withContext = tried == 0;
923                     final JexlMethod ctor = withContext
924                             ? uberspect.getConstructor(namespace, context)
925                             : uberspect.getConstructor(namespace);
926                     if (ctor != null) {
927                         try {
928                             functor = withContext
929                                     ? ctor.invoke(namespace, context)
930                                     : ctor.invoke(namespace);
931                             // defensive
932                             if (functor != null) {
933                                 // wrap the namespace in a NamespaceFunctor to shield us from the actual
934                                 // number of arguments to call it with.
935                                 final Object nsFinal = namespace;
936                                 // make it a class (not a lambda!) so instanceof (see *2) will catch it
937                                 namespace = (NamespaceFunctor) context -> withContext
938                                         ? ctor.tryInvoke(null, nsFinal, context)
939                                         : ctor.tryInvoke(null, nsFinal);
940                                 if (cacheable && ctor.isCacheable()) {
941                                     nsNode.jjtSetValue(namespace);
942                                 }
943                                 break; // we found a constructor that did create a functor
944                             }
945                         } catch (final Exception xinst) {
946                             throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
947                         }
948                     }
949                 }
950                 // did not, will not create a functor instance; use a class, namespace of static methods
951                 if (functor == null) {
952                     // try to find a class with that name; getClassByName is permission-aware and returns
953                     // null when the class is denied by permissions or cannot be located (f047).
954                     if (namespace instanceof String) {
955                         final Class<?> clazz = uberspect.getClassByName((String) namespace);
956                         if (clazz == null) {
957                             throw new JexlException(node, "no such class namespace " + prefix, null);
958                         }
959                         namespace = clazz;
960                     }
961                     // only cache the Class<?> when instantiation was attempted (and no ctor was found);
962                     // when instantiation is disabled we skip the ctor search, so the cache entry would be
963                     // ambiguous: a later execution with instantiation enabled would hit the Class<?> fast-path
964                     // and never try the constructor.
965                     if (cacheable && instantiate) {
966                         nsNode.jjtSetValue(namespace);
967                     }
968                 }
969             }
970         }
971         // if a namespace functor, instantiate the functor (if not done already) and store it (*2)
972         if (functor == null && namespace instanceof JexlContext.NamespaceFunctor) {
973             functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
974         }
975         // got a functor, store it and return it
976         if (functor != null) {
977             synchronized (this) {
978                 if (functors == null) {
979                     functors = new HashMap<>();
980                 }
981                 functors.put(prefix, functor);
982             }
983             return functor;
984         }
985         return namespace;
986     }
987 
988     /**
989      * Sets an attribute of an object.
990      *
991      * @param object    to set the value to
992      * @param attribute The attribute of the object, e.g. an index (1, 0, 2) or key for a map
993      * @param value     The value to assign to the object's attribute
994      * @param node      The node that evaluated as the object
995      */
996     protected void setAttribute(final Object object, final Object attribute, final Object value, final JexlNode node) {
997         cancelCheck(node);
998         Exception xcause = null;
999         try {
1000             final JexlOperator operator;
1001             if (node != null) {
1002                 // attempt to reuse last executor cached in volatile JexlNode.value
1003                 if (cache) {
1004                     final Object cached = node.jjtGetValue();
1005                     if (cached instanceof JexlPropertySet) {
1006                         final JexlPropertySet setter = (JexlPropertySet) cached;
1007                         final Object eval = setter.tryInvoke(object, attribute, value);
1008                         if (!setter.tryFailed(eval)) {
1009                             return;
1010                         }
1011                     }
1012                 }
1013                 // try operator overload
1014                 operator = node.jjtGetParent() instanceof ASTArrayAccess ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
1015                 final Object result = operators.tryOverload(node, operator, object, attribute, value);
1016                 if (result != JexlEngine.TRY_FAILED) {
1017                     return;
1018                 }
1019             } else {
1020                 operator = JexlOperator.PROPERTY_SET;
1021             }
1022             // resolve that property
1023             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
1024             JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
1025             // if we can't find an exact match, narrow the value argument and try again
1026             if (vs == null) {
1027                 // replace all numbers with the smallest type that will fit
1028                 final Object[] narrow = {value};
1029                 if (arithmetic.narrowArguments(narrow)) {
1030                     vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
1031                 }
1032             }
1033             if (vs != null) {
1034                 // cache executor in volatile JexlNode.value
1035                 vs.invoke(object, value);
1036                 if (node != null && cache && vs.isCacheable()) {
1037                     node.jjtSetValue(vs);
1038                 }
1039                 return;
1040             }
1041         } catch (final Exception xany) {
1042             xcause = xany;
1043         }
1044         // lets fail
1045         if (node == null) {
1046             // direct call
1047             final String error = "unable to set object property"
1048                     + ", class: " + object.getClass().getName()
1049                     + ", property: " + attribute
1050                     + ", argument: " + value.getClass().getSimpleName();
1051             throw new UnsupportedOperationException(error, xcause);
1052         }
1053         final String attrStr = Objects.toString(attribute, null);
1054         unsolvableProperty(node, attrStr, true, xcause);
1055     }
1056 
1057     /**
1058      * Sets a variable in the global context.
1059      * <p>If interpretation applies lexical shade, the variable must exist (ie
1060      * the context has(...) method returns true) otherwise an error occurs.
1061      *
1062      * @param node The node
1063      * @param name The variable name
1064      * @param value The variable value
1065      */
1066     protected void setContextVariable(final JexlNode node, final String name, final Object value) {
1067         boolean lexical = options.isLexicalShade();
1068         if (!lexical && node instanceof ASTIdentifier) {
1069             lexical = ((ASTIdentifier) node).isLexical();
1070         }
1071         if (lexical && !context.has(name)) {
1072             throw new JexlException.Variable(node, name, true);
1073         }
1074         try {
1075             context.set(name, value);
1076         } catch (final UnsupportedOperationException xsupport) {
1077             throw new JexlException(node, "context is readonly", xsupport);
1078         }
1079     }
1080 
1081     /**
1082      * Pretty-prints a failing property (de)reference.
1083      * <p>Used by calls to unsolvableProperty(...).</p>
1084      *
1085      * @param node The property node
1086      * @return The (pretty) string
1087      */
1088     protected String stringifyProperty(final JexlNode node) {
1089         if (node instanceof ASTArrayAccess) {
1090             return "[" + stringifyPropertyValue(node.jjtGetChild(0)) + "]";
1091         }
1092         if (node instanceof ASTMethodNode) {
1093             return stringifyPropertyValue(node.jjtGetChild(0));
1094         }
1095         if (node instanceof ASTFunctionNode) {
1096             return stringifyPropertyValue(node.jjtGetChild(0));
1097         }
1098         if (node instanceof ASTIdentifier) {
1099             return ((ASTIdentifier) node).getName();
1100         }
1101         if (node instanceof ASTReference) {
1102             return stringifyProperty(node.jjtGetChild(0));
1103         }
1104         return stringifyPropertyValue(node);
1105     }
1106 
1107     /**
1108      * Triggered when a variable is lexically known as undefined.
1109      *
1110      * @param node  The node where the error originated from
1111      * @param variable   The variable name
1112      * @return throws JexlException if strict and not silent, null otherwise
1113      */
1114     protected Object undefinedVariable(final JexlNode node, final String variable) {
1115         return variableError(node, variable, VariableIssue.UNDEFINED);
1116     }
1117 
1118     /**
1119      * Triggered when a method cannot be resolved.
1120      *
1121      * @param node   The node where the error originated from
1122      * @param method The method name
1123      * @return throws JexlException if strict and not silent, null otherwise
1124      */
1125     protected Object unsolvableMethod(final JexlNode node, final String method) {
1126         return unsolvableMethod(node, method, null);
1127     }
1128 
1129     /**
1130      * Triggered when a method cannot be resolved.
1131      *
1132      * @param node   The node where the error originated from
1133      * @param method The method name
1134      * @param args The method arguments
1135      * @return throws JexlException if strict and not silent, null otherwise
1136      */
1137     protected Object unsolvableMethod(final JexlNode node, final String method, final Object[] args) {
1138         if (isStrictEngine()) {
1139             throw new JexlException.Method(node, method, args);
1140         }
1141         if (logger.isDebugEnabled()) {
1142             logger.debug(JexlException.methodError(node, method, args));
1143         }
1144         return null;
1145     }
1146 
1147     /**
1148      * Triggered when a property cannot be resolved.
1149      *
1150      * @param node  The node where the error originated from
1151      * @param property   The property node
1152      * @param cause The cause if any
1153      * @param undef whether the property is undefined or null
1154      * @return throws JexlException if strict and not silent, null otherwise
1155      */
1156     protected Object unsolvableProperty(final JexlNode node, final String property, final boolean undef, final Throwable cause) {
1157         if (isStrictEngine() && !isTernaryProtected(node)) {
1158             throw new JexlException.Property(node, property, undef, cause);
1159         }
1160         if (logger.isDebugEnabled()) {
1161             logger.debug(JexlException.propertyError(node, property, undef));
1162         }
1163         return null;
1164     }
1165 
1166     /**
1167      * Triggered when a variable cannot be resolved.
1168      *
1169      * @param node  The node where the error originated from
1170      * @param variable   The variable name
1171      * @param undef whether the variable is undefined or null
1172      * @return throws JexlException if strict and not silent, null otherwise
1173      */
1174     protected Object unsolvableVariable(final JexlNode node, final String variable, final boolean undef) {
1175         return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
1176     }
1177 
1178     /**
1179      * Triggered when a variable generates an issue.
1180      *
1181      * @param node  The node where the error originated from
1182      * @param variable   The variable name
1183      * @param issue The issue type
1184      * @return throws JexlException if strict and not silent, null otherwise
1185      */
1186     protected Object variableError(final JexlNode node, final String variable, final VariableIssue issue) {
1187         if (isStrictEngine() && !isTernaryProtected(node)) {
1188             throw new JexlException.Variable(node, variable, issue);
1189         }
1190         if (logger.isDebugEnabled()) {
1191             logger.debug(JexlException.variableError(node, variable, issue));
1192         }
1193         return null;
1194     }
1195 }