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