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  //CSOFF: FileLength
18  package org.apache.commons.jexl3.internal;
19  
20  import java.util.ArrayDeque;
21  import java.util.Iterator;
22  import java.util.Objects;
23  import java.util.Queue;
24  import java.util.concurrent.Callable;
25  import java.util.function.Consumer;
26  import java.util.function.Supplier;
27  import java.util.regex.Pattern;
28  
29  import org.apache.commons.jexl3.JexlArithmetic;
30  import org.apache.commons.jexl3.JexlContext;
31  import org.apache.commons.jexl3.JexlEngine;
32  import org.apache.commons.jexl3.JexlException;
33  import org.apache.commons.jexl3.JexlOperator;
34  import org.apache.commons.jexl3.JexlOptions;
35  import org.apache.commons.jexl3.JexlScript;
36  import org.apache.commons.jexl3.JxltEngine;
37  import org.apache.commons.jexl3.introspection.JexlMethod;
38  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
39  import org.apache.commons.jexl3.parser.*;
40  
41  /**
42   * An interpreter of JEXL syntax.
43   *
44   * @since 2.0
45   */
46  public class Interpreter extends InterpreterBase {
47  
48      /**
49       * An annotated call.
50       */
51      public class AnnotatedCall implements Callable<Object> {
52  
53          /** The statement. */
54          private final ASTAnnotatedStatement stmt;
55  
56          /** The child index. */
57          private final int index;
58  
59          /** The data. */
60          private final Object data;
61  
62          /** Tracking whether we processed the annotation. */
63          private boolean processed;
64  
65          /**
66           * Simple ctor.
67           *
68           * @param astmt The statement
69           * @param aindex The index
70           * @param adata The data
71           */
72          AnnotatedCall(final ASTAnnotatedStatement astmt, final int aindex, final Object adata) {
73              stmt = astmt;
74              index = aindex;
75              data = adata;
76          }
77  
78          @Override
79          public Object call() throws Exception {
80              processed = true;
81              try {
82                  return processAnnotation(stmt, index, data);
83              } catch (JexlException.Return | JexlException.Break | JexlException.Continue xreturn) {
84                  return xreturn;
85              }
86          }
87  
88          /**
89           * @return The actual statement.
90           */
91          public Object getStatement() {
92              return stmt;
93          }
94  
95          /**
96           * @return whether the statement has been processed
97           */
98          public boolean isProcessed() {
99              return processed;
100         }
101     }
102 
103     /**
104      * The thread local interpreter.
105      */
106     protected static final ThreadLocal<Interpreter> INTER =
107                        new ThreadLocal<>();
108 
109     /** Frame height. */
110     protected int fp;
111 
112     /** Symbol values. */
113     protected final Frame frame;
114 
115     /** Block micro-frames. */
116     protected LexicalFrame block;
117 
118     /**
119      * Creates an interpreter.
120      *
121      * @param engine   The engine creating this interpreter
122      * @param aContext The evaluation context, global variables, methods and functions
123      * @param opts     The evaluation options, flags modifying evaluation behavior
124      * @param eFrame   The evaluation frame, arguments and local variables
125      */
126     protected Interpreter(final Engine engine, final JexlOptions opts, final JexlContext aContext, final Frame eFrame) {
127         super(engine, opts, aContext);
128         this.frame = eFrame;
129     }
130 
131     /**
132      * Copy constructor.
133      *
134      * @param ii  The interpreter to copy
135      * @param jexla The arithmetic instance to use (or null)
136      */
137     protected Interpreter(final Interpreter ii, final JexlArithmetic jexla) {
138         super(ii, jexla);
139         frame = ii.frame;
140         block = ii.block != null ? new LexicalFrame(ii.block) : null;
141     }
142 
143     /**
144      * Calls a method (or function).
145      * <p>
146      * Method resolution is a follows:
147      * 1 - attempt to find a method in the target passed as parameter;
148      * 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target;
149      * 3 - if this fails, narrow the arguments and try again 1
150      * 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument;
151      * </p>
152      * *duck-callable: an object where a "call" function exists
153      *
154      * @param node    The method node
155      * @param target  The target of the method, what it should be invoked upon
156      * @param funcNode The object carrying the method or function or the method identifier
157      * @param argNode The node carrying the arguments
158      * @return The result of the method invocation
159      */
160     protected Object call(final JexlNode node, final Object target, final Object funcNode, final ASTArguments argNode) {
161         cancelCheck(node);
162         // evaluate the arguments
163         final Object[] argv = visit(argNode, null);
164         final String methodName;
165         boolean cacheable = cache;
166         boolean isavar = false;
167         Object functor = funcNode;
168         // get the method name if identifier
169         if (functor instanceof ASTIdentifier) {
170             // function call, target is context or namespace (if there was one)
171             final ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
172             final int symbol = methodIdentifier.getSymbol();
173             methodName = methodIdentifier.getName();
174             functor = null;
175             // is it a global or local variable?
176             if (target == context) {
177                 if (frame != null && frame.has(symbol)) {
178                     functor = frame.get(symbol);
179                     isavar = functor != null;
180                 } else if (context.has(methodName)) {
181                     functor = context.get(methodName);
182                     isavar = functor != null;
183                 }
184                 // name is a variable, can't be cached
185                 cacheable &= !isavar;
186             }
187         } else if (functor instanceof ASTIdentifierAccess) {
188             // a method call on target
189             methodName = ((ASTIdentifierAccess) functor).getName();
190             functor = null;
191             cacheable = true;
192         } else if (functor != null) {
193             // ...(x)(y)
194             methodName = null;
195             cacheable = false;
196         } else if (!node.isSafeLhs(isSafe())) {
197             return unsolvableMethod(node, "?(...)");
198         } else {
199             // safe lhs
200             return null;
201         }
202 
203         // solving the call site
204         final CallDispatcher call = new CallDispatcher(node, cacheable);
205         try {
206             // do we have a cached version method/function name?
207             final Object eval = call.tryEval(target, methodName, argv);
208             if (JexlEngine.TRY_FAILED != eval) {
209                 return eval;
210             }
211             boolean functorp = false;
212             boolean narrow = false;
213             // pseudo loop to try acquiring methods without and with argument narrowing
214             while (true) {
215                 call.narrow = narrow;
216                 // direct function or method call
217                 if (functor == null || functorp) {
218                     // try a method or function from context
219                     if (call.isTargetMethod(target, methodName, argv)) {
220                         return call.eval(methodName);
221                     }
222                     if (target == context) {
223                         // solve 'null' namespace
224                         final Object namespace = resolveNamespace(null, node);
225                         if (namespace != null
226                             && namespace != context
227                             && call.isTargetMethod(namespace, methodName, argv)) {
228                             return call.eval(methodName);
229                         }
230                         // do not try context function since this was attempted
231                         // 10 lines above...; solve as an arithmetic function
232                         if (call.isArithmeticMethod(methodName, argv)) {
233                             return call.eval(methodName);
234                         }
235                         // could not find a method, try as a property of a non-context target (performed once)
236                     } else {
237                         // try prepending target to arguments and look for
238                         // applicable method in context...
239                         final Object[] pargv = functionArguments(target, narrow, argv);
240                         if (call.isContextMethod(methodName, pargv)) {
241                             return call.eval(methodName);
242                         }
243                         // ...or arithmetic
244                         if (call.isArithmeticMethod(methodName, pargv)) {
245                             return call.eval(methodName);
246                         }
247                         // the method may also be a functor stored in a property of the target
248                         if (!narrow) {
249                             final JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
250                             if (get != null) {
251                                 functor = get.tryInvoke(target, methodName);
252                                 functorp = functor != null;
253                             }
254                         }
255                     }
256                 }
257                 // this may happen without the above when we are chaining call like x(a)(b)
258                 // or when a var/symbol or antish var is used as a "function" name
259                 if (functor != null) {
260                     // lambda, script or jexl method will do
261                     if (functor instanceof JexlScript) {
262                         return ((JexlScript) functor).execute(context, argv);
263                     }
264                     if (functor instanceof JexlMethod) {
265                         return ((JexlMethod) functor).invoke(target, argv);
266                     }
267                     final String mCALL = "call";
268                     // maybe a generic callable, try a 'call' method
269                     if (call.isTargetMethod(functor, mCALL, argv)) {
270                         return call.eval(mCALL);
271                     }
272                     // functor is a var, may be method is a global one?
273                     if (isavar) {
274                         if (call.isContextMethod(methodName, argv)) {
275                             return call.eval(methodName);
276                         }
277                         if (call.isArithmeticMethod(methodName, argv)) {
278                             return call.eval(methodName);
279                         }
280                     }
281                     // try prepending functor to arguments and look for
282                     // context or arithmetic function called 'call'
283                     final Object[] pargv = functionArguments(functor, narrow, argv);
284                     if (call.isContextMethod(mCALL, pargv)) {
285                         return call.eval(mCALL);
286                     }
287                     if (call.isArithmeticMethod(mCALL, pargv)) {
288                         return call.eval(mCALL);
289                     }
290                 }
291                 // if we did not find an exact method by name and we haven't tried yet,
292                 // attempt to narrow the parameters and if this succeeds, try again in next loop
293                 if (narrow || !arithmetic.narrowArguments(argv)) {
294                     break;
295                 }
296                 narrow = true;
297                 // continue;
298             }
299         }
300         catch (final JexlException.TryFailed xany) {
301             throw invocationException(node, methodName, xany);
302         }
303         catch (final JexlException xthru) {
304             if (xthru.getInfo() != null) {
305                 throw xthru;
306             }
307         }
308         catch (final Exception xany) {
309             throw invocationException(node, methodName, xany);
310         }
311         // we have either evaluated and returned or no method was found
312         return node.isSafeLhs(isSafe())
313                 ? null
314                 : unsolvableMethod(node, methodName, argv);
315     }
316 
317     /**
318      * Evaluate the catch in a try/catch/finally.
319      *
320      * @param catchVar The variable containing the exception
321      * @param catchBody The body
322      * @param caught The caught exception
323      * @param data The data
324      * @return The result of body evaluation
325      */
326     private Object evalCatch(final ASTReference catchVar, final JexlNode catchBody,
327                              final JexlException caught, final Object data) {
328         // declare catch variable and assign with caught exception
329         final ASTIdentifier catchVariable = (ASTIdentifier) catchVar.jjtGetChild(0);
330         final int symbol = catchVariable.getSymbol();
331         final boolean lexical = catchVariable.isLexical() || options.isLexical();
332         if (lexical) {
333             // create lexical frame
334             final LexicalFrame locals = new LexicalFrame(frame, block);
335             // it may be a local previously declared
336             final boolean trySymbol = symbol >= 0 && catchVariable instanceof ASTVar;
337             if (trySymbol && !defineVariable((ASTVar) catchVariable, locals)) {
338                 return redefinedVariable(catchVar.jjtGetParent(), catchVariable.getName());
339             }
340             block = locals;
341         }
342         if (symbol < 0) {
343             setContextVariable(catchVar.jjtGetParent(), catchVariable.getName(), caught);
344         } else {
345             final Throwable cause  = caught.getCause();
346             frame.set(symbol, cause == null? caught : cause);
347         }
348         try {
349             // evaluate body
350             return catchBody.jjtAccept(this, data);
351         } finally {
352             // restore lexical frame
353             if (lexical) {
354                 block = block.pop();
355             }
356         }
357     }
358 
359     @Override
360     protected Object visit(final ASTJxltLiteral node, final Object data) {
361         return evalJxltHandle(node);
362     }
363 
364     /**
365      * Evaluates an access identifier based on the 2 main implementations;
366      * static (name or numbered identifier) or dynamic (jxlt).
367      *
368      * @param node The identifier access node
369      * @return The evaluated identifier
370      */
371     private Object evalIdentifier(final ASTIdentifierAccess node) {
372         if (!(node instanceof ASTIdentifierAccessJxlt)) {
373             return node.getIdentifier();
374         }
375         final ASTIdentifierAccessJxlt jxltNode = (ASTIdentifierAccessJxlt) node;
376         Throwable cause = null;
377         try {
378             final Object name = evalJxltHandle(jxltNode);
379             if (name != null) {
380                 return name;
381             }
382         } catch (final JxltEngine.Exception xjxlt) {
383             cause = xjxlt;
384         }
385         return node.isSafe() ? null : unsolvableProperty(jxltNode, jxltNode.getExpressionSource(), true, cause);
386     }
387 
388     /**
389      * Evaluates a JxltHandle node.
390      * <p>This parses and stores the JXLT template if necessary (upon first execution)</p>
391      *
392      * @param node The node
393      * @return The JXLT template evaluation.
394      * @param <NODE> the node type
395      */
396     private <NODE extends JexlNode & JexlNode.JxltHandle> Object evalJxltHandle(final NODE node) {
397         final JxltEngine.Expression expr = node.getExpression();
398         // internal classes to evaluate in context
399         if (expr instanceof TemplateEngine.TemplateExpression) {
400             final Object eval = ((TemplateEngine.TemplateExpression) expr).evaluate(context, frame, options);
401             if (eval != null) {
402                 final String inter = eval.toString();
403                 if (options.isStrictInterpolation()) {
404                     return inter;
405                 }
406                 final Integer id = JexlArithmetic.parseIdentifier(inter);
407                 return id != null ? id : eval;
408             }
409         }
410         return null;
411     }
412 
413     /**
414      * Executes an assignment with an optional side effect operator.
415      *
416      * @param node     The node
417      * @param assignop The assignment operator or null if simply assignment
418      * @param data     The data
419      * @return The left hand side
420      */
421     protected Object executeAssign(final JexlNode node, final JexlOperator assignop, final Object data) { // CSOFF: MethodLength
422         cancelCheck(node);
423         // left contains the reference to assign to
424         final JexlNode left = node.jjtGetChild(0);
425         final ASTIdentifier variable;
426         Object object = null;
427         final int symbol;
428         // check var decl with assign is ok
429         if (left instanceof ASTIdentifier) {
430             variable = (ASTIdentifier) left;
431             symbol = variable.getSymbol();
432             if (symbol >= 0) {
433                 if  (variable.isLexical() || options.isLexical()) {
434                     if (variable instanceof ASTVar) {
435                         if (!defineVariable((ASTVar) variable, block)) {
436                             return redefinedVariable(variable, variable.getName());
437                         }
438                     } else if (variable.isShaded() && (variable.isLexical() || options.isLexicalShade())) {
439                         return undefinedVariable(variable, variable.getName());
440                     }
441                 }
442                 if (variable.isCaptured() && options.isConstCapture()) {
443                     return constVariable(variable, variable.getName());
444                 }
445             }
446         } else {
447             variable = null;
448             symbol = -1;
449         }
450         boolean antish = options.isAntish();
451         // 0: determine initial object & property:
452         final int last = left.jjtGetNumChildren() - 1;
453         // right is the value expression to assign
454        final  Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
455         // actual value to return, right in most cases
456         Object actual = right;
457         // a (var?) v = ... expression
458         if (variable != null) {
459             if (symbol >= 0) {
460                 // check we are not assigning a symbol itself
461                 if (last < 0) {
462                     if (assignop == null) {
463                         // make the closure accessible to itself, ie capture the currently set variable after frame creation
464                         if (right instanceof Closure) {
465                             final Closure closure = (Closure) right;
466                             // the variable scope must be the parent of the lambdas
467                             closure.captureSelfIfRecursive(frame, symbol);
468                         }
469                         frame.set(symbol, right);
470                     } else {
471                         // go through potential overload
472                         final Object self = getVariable(frame, block, variable);
473                         final Consumer<Object> f = r -> frame.set(symbol, r);
474                         actual = operators.tryAssignOverload(node, assignop, f, self, right);
475                     }
476                     return actual; // 1
477                 }
478                 object = getVariable(frame, block, variable);
479                 // top level is a symbol, cannot be an antish var
480                 antish = false;
481             } else {
482                 // check we are not assigning direct global
483                 final String name = variable.getName();
484                 if (last < 0) {
485                     if (assignop == null) {
486                         setContextVariable(node, name, right);
487                     } else {
488                         // go through potential overload
489                         final Object self = context.get(name);
490                         final Consumer<Object> f = r ->  setContextVariable(node, name, r);
491                         actual = operators.tryAssignOverload(node, assignop, f, self, right);
492                     }
493                     return actual; // 2
494                 }
495                 object = context.get(name);
496                 // top level accesses object, cannot be an antish var
497                 if (object != null) {
498                     antish = false;
499                 }
500             }
501         } else if (!(left instanceof ASTReference)) {
502             throw new JexlException(left, "illegal assignment form 0");
503         }
504         // 1: follow children till penultimate, resolve dot/array
505         JexlNode objectNode = null;
506         StringBuilder ant = null;
507         int v = 1;
508         // start at 1 if symbol
509         main: for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
510             objectNode = left.jjtGetChild(c);
511             object = objectNode.jjtAccept(this, object);
512             if (object != null) {
513                 // disallow mixing antish variable & bean with same root; avoid ambiguity
514                 antish = false;
515             } else if (antish) {
516                 // initialize if first time
517                 if (ant == null) {
518                     final JexlNode first = left.jjtGetChild(0);
519                     final ASTIdentifier firstId = first instanceof ASTIdentifier
520                             ? (ASTIdentifier) first
521                             : null;
522                     if (firstId == null || firstId.getSymbol() >= 0) {
523                         // ant remains null, object is null, stop solving
524                         antish = false;
525                         break main;
526                     }
527                     ant = new StringBuilder(firstId.getName());
528                 }
529                 // catch up to current child
530                 for (; v <= c; ++v) {
531                     final JexlNode child = left.jjtGetChild(v);
532                     final ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess
533                             ? (ASTIdentifierAccess) child
534                             : null;
535                     // remain antish only if unsafe navigation
536                     if (aid == null || aid.isSafe() || aid.isExpression()) {
537                         antish = false;
538                         break main;
539                     }
540                     ant.append('.');
541                     ant.append(aid.getName());
542                 }
543                 // solve antish
544                 object = context.get(ant.toString());
545             } else {
546                 throw new JexlException(objectNode, "illegal assignment form");
547             }
548         }
549         // 2: last objectNode will perform assignment in all cases
550         JexlNode propertyNode = left.jjtGetChild(last);
551         final ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess
552                 ? (ASTIdentifierAccess) propertyNode
553                 : null;
554         final Object property;
555         if (propertyId != null) {
556             // deal with creating/assigning antish variable
557             if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
558                 ant.append('.');
559                 ant.append(propertyId.getName());
560                 final String name = ant.toString();
561                 if (assignop == null) {
562                     setContextVariable(propertyNode, name, right);
563                 } else {
564                     final Object self = context.get(ant.toString());
565                     final JexlNode pnode = propertyNode;
566                     final Consumer<Object> assign = r -> setContextVariable(pnode, name, r);
567                     actual = operators.tryAssignOverload(node, assignop, assign, self, right);
568                 }
569                 return actual; // 3
570             }
571             // property of an object ?
572             property = evalIdentifier(propertyId);
573         } else if (propertyNode instanceof ASTArrayAccess) {
574             // can have multiple nodes - either an expression, integer literal or reference
575             final int numChildren = propertyNode.jjtGetNumChildren() - 1;
576             for (int i = 0; i < numChildren; i++) {
577                 final JexlNode nindex = propertyNode.jjtGetChild(i);
578                 final Object index = nindex.jjtAccept(this, null);
579                 object = getAttribute(object, index, nindex);
580             }
581             propertyNode = propertyNode.jjtGetChild(numChildren);
582             property = propertyNode.jjtAccept(this, null);
583         } else {
584             throw new JexlException(objectNode, "illegal assignment form");
585         }
586         // we may have a null property as in map[null], no check needed.
587         // we cannot *have* a null object though.
588         if (object == null) {
589             // no object, we fail
590             return unsolvableProperty(objectNode, "<null>.<?>", true, null);
591         }
592         // 3: one before last, assign
593         if (assignop == null) {
594             setAttribute(object, property, right, propertyNode);
595         } else {
596             final Object self = getAttribute(object, property, propertyNode);
597             final Object o = object;
598             final JexlNode n = propertyNode;
599             final Consumer<Object> assign = r ->  setAttribute(o, property, r, n);
600             actual = operators.tryAssignOverload(node, assignop, assign, self, right);
601         }
602         return actual;
603     }
604 
605     private Object forIterator(final ASTForeachStatement node, final Object data) {
606         Object result = null;
607         /* first objectNode is the loop variable */
608         final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
609         final ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
610         final int symbol = loopVariable.getSymbol();
611         final boolean lexical = loopVariable.isLexical() || options.isLexical();
612         final LexicalFrame locals = lexical? new LexicalFrame(frame, block) : null;
613         final boolean loopSymbol = symbol >= 0 && loopVariable instanceof ASTVar;
614         if (lexical) {
615             // create lexical frame
616             // it may be a local previously declared
617             if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
618                 return redefinedVariable(node, loopVariable.getName());
619             }
620             block = locals;
621         }
622         Object forEach = null;
623         try {
624             /* second objectNode is the variable to iterate */
625             final Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
626             // make sure there is a value to iterate upon
627             if (iterableValue == null) {
628                 return null;
629             }
630             /* last child node is the statement to execute */
631             final int numChildren = node.jjtGetNumChildren();
632             final JexlNode statement = numChildren >= 3 ? node.jjtGetChild(numChildren - 1) : null;
633             // get an iterator for the collection/array/etc. via the introspector.
634             forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
635             final Iterator<?> itemsIterator = forEach instanceof Iterator
636                     ? (Iterator<?>) forEach
637                     : uberspect.getIterator(iterableValue);
638             if (itemsIterator == null) {
639                 return null;
640             }
641             int cnt = 0;
642             while (itemsIterator.hasNext()) {
643                 cancelCheck(node);
644                 // reset loop variable
645                 if (lexical && cnt++ > 0) {
646                     // clean up but remain current
647                     block.pop();
648                     // unlikely to fail
649                     if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
650                         return redefinedVariable(node, loopVariable.getName());
651                     }
652                 }
653                 // set loopVariable to value of iterator
654                 final Object value = itemsIterator.next();
655                 if (symbol < 0) {
656                     setContextVariable(node, loopVariable.getName(), value);
657                 } else {
658                     frame.set(symbol, value);
659                 }
660                 if (statement != null) {
661                     try {
662                         // execute statement
663                         result = statement.jjtAccept(this, data);
664                     } catch (final JexlException.Break stmtBreak) {
665                         break;
666                     } catch (final JexlException.Continue stmtContinue) {
667                         //continue;
668                     }
669                 }
670             }
671         } finally {
672             //  closeable iterator handling
673             closeIfSupported(forEach);
674             // restore lexical frame
675             if (lexical) {
676                 block = block.pop();
677             }
678         }
679         return result;
680     }
681 
682     private Object forLoop(final ASTForeachStatement node, final Object data) {
683         Object result = null;
684         int nc;
685         final int form = node.getLoopForm();
686         final LexicalFrame locals;
687         /* first child node might be the loop variable */
688         if ((form & 1) != 0) {
689             nc = 1;
690             final JexlNode init = node.jjtGetChild(0);
691             ASTVar loopVariable = null;
692             if (init instanceof ASTAssignment) {
693                 final JexlNode child = init.jjtGetChild(0);
694                 if (child instanceof ASTVar) {
695                     loopVariable = (ASTVar) child;
696                 }
697             } else if (init instanceof  ASTVar){
698                 loopVariable = (ASTVar) init;
699             }
700             if (loopVariable != null) {
701                 final boolean lexical = loopVariable.isLexical() || options.isLexical();
702                 locals = lexical ? new LexicalFrame(frame, block) : null;
703                 if (locals != null) {
704                     block = locals;
705                 }
706             } else {
707                 locals = null;
708             }
709             // initialize after eventual creation of local lexical frame
710             init.jjtAccept(this, data);
711             // other inits
712             for (JexlNode moreAssignment = node.jjtGetChild(nc);
713                  moreAssignment instanceof ASTAssignment;
714                  moreAssignment = node.jjtGetChild(++nc)) {
715                 moreAssignment.jjtAccept(this, data);
716             }
717         } else {
718             locals = null;
719             nc = 0;
720         }
721         try {
722             // the loop condition
723             final JexlNode predicate = (form & 2) != 0? node.jjtGetChild(nc++) : null;
724             // the loop step
725             final JexlNode step = (form & 4) != 0? node.jjtGetChild(nc++) : null;
726             // last child is body
727             final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
728             // while(predicate())...
729             while (predicate == null || testPredicate(predicate, predicate.jjtAccept(this, data))) {
730                 cancelCheck(node);
731                 // the body
732                 if (statement != null) {
733                     try {
734                         // execute statement
735                         result = statement.jjtAccept(this, data);
736                     } catch (final JexlException.Break stmtBreak) {
737                         break;
738                     } catch (final JexlException.Continue stmtContinue) {
739                         //continue;
740                     }
741                 }
742                 // the step
743                 if (step != null) {
744                     step.jjtAccept(this, data);
745                 }
746             }
747         } finally {
748             // restore lexical frame
749             if (locals != null) {
750                 block = block.pop();
751             }
752         }
753         return result;
754     }
755 
756     /**
757      * Interpret the given script/expression.
758      * <p>
759      * If the underlying JEXL engine is silent, errors will be logged through
760      * its logger as warning.
761      *
762      * @param node The script or expression to interpret.
763      * @return The result of the interpretation.
764      * @throws JexlException if any error occurs during interpretation.
765      */
766     public Object interpret(final JexlNode node) {
767         JexlContext.ThreadLocal tcontext = null;
768         JexlEngine tjexl = null;
769         Interpreter tinter = null;
770         try {
771             tinter = putThreadInterpreter(this);
772             if (tinter != null) {
773                 fp = tinter.fp + 1;
774             }
775             if (context instanceof JexlContext.ThreadLocal) {
776                 tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
777             }
778             tjexl = jexl.putThreadEngine(jexl);
779             if (fp > jexl.stackOverflow) {
780                 throw new JexlException.StackOverflow(node.jexlInfo(), "jexl (" + jexl.stackOverflow + ")", null);
781             }
782             cancelCheck(node);
783             return arithmetic.controlReturn(node.jjtAccept(this, null));
784         } catch (final StackOverflowError xstack) {
785             final JexlException xjexl = new JexlException.StackOverflow(node.jexlInfo(), "jvm", xstack);
786             if (!isSilent()) {
787                 throw xjexl.clean();
788             }
789             if (logger.isWarnEnabled()) {
790                 logger.warn(xjexl.getMessage(), xjexl.getCause());
791             }
792         } catch (final JexlException.Return xreturn) {
793             return xreturn.getValue();
794         } catch (final JexlException.Cancel xcancel) {
795             // cancelled |= Thread.interrupted();
796             cancelled.weakCompareAndSet(false, Thread.interrupted());
797             if (isCancellable()) {
798                 throw xcancel.clean();
799             }
800         } catch (final JexlException xjexl) {
801             if (!isSilent()) {
802                 throw xjexl.clean();
803             }
804             if (logger.isWarnEnabled()) {
805                 logger.warn(xjexl.getMessage(), xjexl.getCause());
806             }
807         } finally {
808             // clean functors at top level
809             if (fp == 0) {
810                 synchronized (this) {
811                     if (functors != null) {
812                         for (final Object functor : functors.values()) {
813                             closeIfSupported(functor);
814                         }
815                         functors.clear();
816                         functors = null;
817                     }
818                 }
819             }
820             jexl.putThreadEngine(tjexl);
821             if (context instanceof JexlContext.ThreadLocal) {
822                 jexl.putThreadLocal(tcontext);
823             }
824             if (tinter != null) {
825                 fp = tinter.fp - 1;
826             }
827             putThreadInterpreter(tinter);
828         }
829         return null;
830     }
831 
832     /**
833      * Determines if the specified Object is assignment-compatible with the object represented by the Class.
834      *
835      * @param object The Object
836      * @param clazz The Class
837      * @return The result of isInstance call
838      */
839     private boolean isInstance(final Object object, final Object clazz) {
840         if (object == null || clazz == null) {
841             return false;
842         }
843         final Class<?> c = clazz instanceof Class<?>
844             ? (Class<?>) clazz
845             : uberspect.getClassByName(resolveClassName(clazz.toString()));
846         return c != null && c.isInstance(object);
847     }
848 
849     /**
850      * Processes an annotated statement.
851      *
852      * @param stmt The statement
853      * @param index The index of the current annotation being processed
854      * @param data The contextual data
855      * @return  the result of the statement block evaluation
856      */
857     protected Object processAnnotation(final ASTAnnotatedStatement stmt, final int index, final Object data) {
858         // are we evaluating the block ?
859         final int last = stmt.jjtGetNumChildren() - 1;
860         if (index == last) {
861             final JexlNode cblock = stmt.jjtGetChild(last);
862             // if the context has changed, might need a new interpreter
863             final JexlArithmetic jexla = arithmetic.options(context);
864             if (jexla == arithmetic) {
865                 return cblock.jjtAccept(Interpreter.this, data);
866             }
867             if (!arithmetic.getClass().equals(jexla.getClass()) && logger.isWarnEnabled()) {
868                 logger.warn("expected arithmetic to be " + arithmetic.getClass().getSimpleName()
869                         + ", got " + jexla.getClass().getSimpleName()
870                 );
871             }
872             final Interpreter ii = new Interpreter(Interpreter.this, jexla);
873             final Object r = cblock.jjtAccept(ii, data);
874             if (ii.isCancelled()) {
875                 Interpreter.this.cancel();
876             }
877             return r;
878         }
879         // tracking whether we processed the annotation
880         final AnnotatedCall jstmt = new AnnotatedCall(stmt, index + 1, data);
881         // the annotation node and name
882         final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index);
883         final String aname = anode.getName();
884         // evaluate the arguments
885         final Object[] argv = anode.jjtGetNumChildren() > 0
886                         ? visit((ASTArguments) anode.jjtGetChild(0), null) : null;
887         // wrap the future, will recurse through annotation processor
888         Object result;
889         try {
890             result = processAnnotation(aname, argv, jstmt);
891             // not processing an annotation is an error
892             if (!jstmt.isProcessed()) {
893                 return annotationError(anode, aname, null);
894             }
895         } catch (final JexlException xany) {
896             throw xany;
897         } catch (final Exception xany) {
898             return annotationError(anode, aname, xany);
899         }
900         // the caller may return a return, break or continue
901         if (result instanceof JexlException) {
902             throw (JexlException) result;
903         }
904         return result;
905     }
906 
907     /**
908      * Delegates the annotation processing to the JexlContext if it is an AnnotationProcessor.
909      *
910      * @param annotation    The annotation name
911      * @param args          The annotation arguments
912      * @param stmt          The statement / block that was annotated
913      * @return The result of statement.call()
914      * @throws Exception Thrown if anything goes wrong
915      */
916     protected Object processAnnotation(final String annotation, final Object[] args, final Callable<Object> stmt) throws Exception {
917                 return context instanceof JexlContext.AnnotationProcessor
918                 ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
919                 : stmt.call();
920     }
921 
922     /**
923      * Swaps the current thread local interpreter.
924      *
925      * @param inter The interpreter or null
926      * @return The previous thread local interpreter
927      */
928     protected Interpreter putThreadInterpreter(final Interpreter inter) {
929         final Interpreter pinter = INTER.get();
930         INTER.set(inter);
931         return pinter;
932     }
933 
934     /**
935      * Resolves a class name.
936      *
937      * @param name The simple class name
938      * @return The fully qualified class name or the name
939      */
940     private String resolveClassName(final String name) {
941         // try with local solver
942         String fqcn = fqcnSolver.resolveClassName(name);
943         if (fqcn != null) {
944             return fqcn;
945         }
946         // context may be solving class name?
947         if (context instanceof JexlContext.ClassNameResolver) {
948             final JexlContext.ClassNameResolver resolver = (JexlContext.ClassNameResolver) context;
949             fqcn = resolver.resolveClassName(name);
950             if (fqcn != null) {
951                 return fqcn;
952             }
953         }
954         return name;
955     }
956 
957     /**
958      * Runs a closure.
959      *
960      * @param closure The closure
961      * @return The closure return value
962      */
963     protected Object runClosure(final Closure closure) {
964         final ASTJexlScript script = closure.getScript();
965         // if empty script, nothing to evaluate
966         final int numChildren = script.jjtGetNumChildren();
967         if (numChildren == 0) {
968             return null;
969         }
970         block = new LexicalFrame(frame, block).defineArgs();
971         try {
972             final JexlNode body = script instanceof ASTJexlLambda
973                     ? script.jjtGetChild(numChildren - 1)
974                     : script;
975             return interpret(body);
976         } finally {
977             block = block.pop();
978         }
979     }
980 
981     private boolean testPredicate(final JexlNode node, final Object condition) {
982         final Object predicate = operators.tryOverload(node, JexlOperator.CONDITION, condition);
983         return  arithmetic.testPredicate(predicate != JexlEngine.TRY_FAILED? predicate : condition);
984     }
985 
986     @Override
987     protected Object visit(final ASTAddNode node, final Object data) {
988         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
989         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
990         try {
991             final Object result = operators.tryOverload(node, JexlOperator.ADD, left, right);
992             return result != JexlEngine.TRY_FAILED ? result : arithmetic.add(left, right);
993         } catch (final ArithmeticException xrt) {
994             throw new JexlException(findNullOperand(node, left, right), "+ error", xrt);
995         }
996     }
997 
998     /**
999      * Short-circuit evaluation of logical expression.
1000      *
1001      * @param check The fuse value that will stop evaluation, true for OR, false for AND
1002      * @param node A ASTAndNode or a ASTOrNode
1003      * @param data The data, usually null and unused
1004      * @return true or false if boolean logical option is true, the last evaluated argument otherwise
1005      */
1006     private Object shortCircuit(final boolean check, final JexlNode node, final Object data) {
1007         /*
1008          * The pattern for exception mgmt is to let the child*.jjtAccept out of the try/catch loop so that if one fails,
1009          * the ex will traverse up to the interpreter. In cases where this is not convenient/possible, JexlException
1010          * must be caught explicitly and rethrown.
1011          */
1012         final int last = node.jjtGetNumChildren();
1013         Object argument = null;
1014         boolean result = false;
1015         for (int c = 0; c < last; ++c) {
1016             argument = node.jjtGetChild(c).jjtAccept(this, data);
1017             try {
1018                 // short-circuit
1019                 result = arithmetic.toBoolean(argument);
1020                 if (result == check) {
1021                     break;
1022                 }
1023             } catch (final ArithmeticException xrt) {
1024                 throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
1025             }
1026         }
1027         return options.isBooleanLogical()? result : argument;
1028     }
1029 
1030     @Override
1031     protected Object visit(final ASTAndNode node, final Object data) {
1032         return shortCircuit(false, node, data);
1033     }
1034 
1035     @Override
1036     protected Object visit(final ASTOrNode node, final Object data) {
1037         return shortCircuit(true, node, data);
1038     }
1039 
1040     @Override
1041     protected Object visit(final ASTAnnotatedStatement node, final Object data) {
1042         return processAnnotation(node, 0, data);
1043     }
1044 
1045     @Override
1046     protected Object visit(final ASTAnnotation node, final Object data) {
1047         throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported.");
1048     }
1049 
1050     @Override
1051     protected Object[] visit(final ASTArguments node, final Object data) {
1052         final int argc = node.jjtGetNumChildren();
1053         final Object[] argv = new Object[argc];
1054         for (int i = 0; i < argc; i++) {
1055             argv[i] = node.jjtGetChild(i).jjtAccept(this, data);
1056         }
1057         return argv;
1058     }
1059 
1060     @Override
1061     protected Object visit(final ASTArrayAccess node, final Object data) {
1062         // first objectNode is the identifier
1063         Object object = data;
1064         // can have multiple nodes - either an expression, integer literal or reference
1065         final int numChildren = node.jjtGetNumChildren();
1066         for (int i = 0; i < numChildren; i++) {
1067             final JexlNode nindex = node.jjtGetChild(i);
1068             if (object == null) {
1069                 // safe navigation access
1070                 return node.isSafeChild(i)
1071                     ? null
1072                     :unsolvableProperty(nindex, stringifyProperty(nindex), false, null);
1073             }
1074             final Object index = nindex.jjtAccept(this, null);
1075             cancelCheck(node);
1076             object = getAttribute(object, index, nindex);
1077         }
1078         return object;
1079     }
1080 
1081     @Override
1082     protected Object visit(final ASTArrayLiteral node, final Object data) {
1083         final int childCount = node.jjtGetNumChildren();
1084         final JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount, node.isExtended());
1085         boolean extended = false;
1086         for (int i = 0; i < childCount; i++) {
1087             cancelCheck(node);
1088             final JexlNode child = node.jjtGetChild(i);
1089             if (child instanceof ASTExtendedLiteral) {
1090                 extended = true;
1091             } else {
1092                 final Object entry = node.jjtGetChild(i).jjtAccept(this, data);
1093                 ab.add(entry);
1094             }
1095         }
1096         return ab.create(extended);
1097     }
1098 
1099     @Override
1100     protected Object visit(final ASTAssignment node, final Object data) {
1101         return executeAssign(node, null, data);
1102     }
1103 
1104     @Override
1105     protected Object visit(final ASTBitwiseAndNode node, final Object data) {
1106         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1107         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1108         try {
1109             final Object result = operators.tryOverload(node, JexlOperator.AND, left, right);
1110             return result != JexlEngine.TRY_FAILED ? result : arithmetic.and(left, right);
1111         } catch (final ArithmeticException xrt) {
1112             throw new JexlException(findNullOperand(node, left, right), "& error", xrt);
1113         }
1114     }
1115 
1116     @Override
1117     protected Object visit(final ASTBitwiseComplNode node, final Object data) {
1118         final Object arg = node.jjtGetChild(0).jjtAccept(this, data);
1119         try {
1120             final Object result = operators.tryOverload(node, JexlOperator.COMPLEMENT, arg);
1121             return result != JexlEngine.TRY_FAILED ? result : arithmetic.complement(arg);
1122         } catch (final ArithmeticException xrt) {
1123             throw new JexlException(node, "~ error", xrt);
1124         }
1125     }
1126 
1127     @Override
1128     protected Object visit(final ASTBitwiseOrNode node, final Object data) {
1129         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1130         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1131         try {
1132             final Object result = operators.tryOverload(node, JexlOperator.OR, left, right);
1133             return result != JexlEngine.TRY_FAILED ? result : arithmetic.or(left, right);
1134         } catch (final ArithmeticException xrt) {
1135             throw new JexlException(findNullOperand(node, left, right), "| error", xrt);
1136         }
1137     }
1138 
1139     @Override
1140     protected Object visit(final ASTBitwiseXorNode node, final Object data) {
1141         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1142         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1143         try {
1144             final Object result = operators.tryOverload(node, JexlOperator.XOR, left, right);
1145             return result != JexlEngine.TRY_FAILED ? result : arithmetic.xor(left, right);
1146         } catch (final ArithmeticException xrt) {
1147             throw new JexlException(findNullOperand(node, left, right), "^ error", xrt);
1148         }
1149     }
1150 
1151     @Override
1152     protected Object visit(final ASTBlock node, final Object data) {
1153         final int cnt = node.getSymbolCount();
1154         if (cnt <= 0) {
1155             return visitBlock(node, data);
1156         }
1157         try {
1158             block = new LexicalFrame(frame, block);
1159             return visitBlock(node, data);
1160         } finally {
1161             block = block.pop();
1162         }
1163     }
1164 
1165     @Override
1166     protected Object visit(final ASTBreak node, final Object data) {
1167         throw new JexlException.Break(node);
1168     }
1169 
1170     @Override
1171     protected Object visit(final ASTConstructorNode node, final Object data) {
1172         if (isCancelled()) {
1173             throw new JexlException.Cancel(node);
1174         }
1175         // first child is class or class name
1176         final Object target = node.jjtGetChild(0).jjtAccept(this, data);
1177         // get the ctor args
1178         final int argc = node.jjtGetNumChildren() - 1;
1179         Object[] argv = new Object[argc];
1180         for (int i = 0; i < argc; i++) {
1181             argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
1182         }
1183 
1184         try {
1185             final boolean cacheable = cache;
1186             // attempt to reuse last funcall cached in volatile JexlNode.value
1187             if (cacheable) {
1188                 final Object cached = node.jjtGetValue();
1189                 if (cached instanceof Funcall) {
1190                     final Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv);
1191                     if (JexlEngine.TRY_FAILED != eval) {
1192                         return eval;
1193                     }
1194                 }
1195             }
1196             boolean narrow = false;
1197             Funcall funcall = null;
1198             JexlMethod ctor;
1199             while (true) {
1200                 // try as stated
1201                 ctor = uberspect.getConstructor(target, argv);
1202                 if (ctor != null) {
1203                     if (cacheable && ctor.isCacheable()) {
1204                         funcall = new Funcall(ctor, narrow);
1205                     }
1206                     break;
1207                 }
1208                 // try with prepending context as first argument
1209                 final Object[] nargv = callArguments(context, narrow, argv);
1210                 ctor = uberspect.getConstructor(target, nargv);
1211                 if (ctor != null) {
1212                     if (cacheable && ctor.isCacheable()) {
1213                         funcall = new ContextualCtor(ctor, narrow);
1214                     }
1215                     argv = nargv;
1216                     break;
1217                 }
1218                 // if we did not find an exact method by name and we haven't tried yet,
1219                 // attempt to narrow the parameters and if this succeeds, try again in next loop
1220                 if (!narrow && arithmetic.narrowArguments(argv)) {
1221                     narrow = true;
1222                     continue;
1223                 }
1224                 // we are done trying
1225                 break;
1226             }
1227             // we have either evaluated and returned or might have found a ctor
1228             if (ctor != null) {
1229                 final Object eval = ctor.invoke(target, argv);
1230                 // cache executor in volatile JexlNode.value
1231                 if (funcall != null) {
1232                     node.jjtSetValue(funcall);
1233                 }
1234                 return eval;
1235             }
1236             final String tstr = Objects.toString(target, "?");
1237             return unsolvableMethod(node, tstr, argv);
1238         } catch (final JexlException.Method xmethod) {
1239             throw xmethod;
1240         } catch (final Exception xany) {
1241             final String tstr = Objects.toString(target, "?");
1242             throw invocationException(node, tstr, xany);
1243         }
1244     }
1245 
1246     @Override
1247     protected Object visit(final ASTCaseStatement node, final Object data) {
1248         final int argc = node.jjtGetNumChildren();
1249         Object result = null;
1250         for (int i = 0; i < argc; i++) {
1251             result = node.jjtGetChild(i).jjtAccept(this, data);
1252         }
1253         return result;
1254     }
1255 
1256     @Override
1257     protected Object visit(final ASTCaseExpression node, final Object data) {
1258         return node.jjtGetChild(0).jjtAccept(this, data);
1259     }
1260 
1261     @Override
1262     protected Object visit(final ASTContinue node, final Object data) {
1263         throw new JexlException.Continue(node);
1264     }
1265 
1266     @Override
1267     protected Object visit(final ASTDecrementGetNode node, final Object data) {
1268         return executeAssign(node, JexlOperator.DECREMENT_AND_GET, data);
1269     }
1270 
1271     @Override
1272     protected Object visit(final ASTDefineVars node, final Object data) {
1273         final int argc = node.jjtGetNumChildren();
1274         Object result = null;
1275         for (int i = 0; i < argc; i++) {
1276             result = node.jjtGetChild(i).jjtAccept(this, data);
1277         }
1278         return result;
1279     }
1280 
1281     @Override
1282     protected Object visit(final ASTDivNode node, final Object data) {
1283         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1284         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1285         try {
1286             final Object result = operators.tryOverload(node, JexlOperator.DIVIDE, left, right);
1287             return result != JexlEngine.TRY_FAILED ? result : arithmetic.divide(left, right);
1288         } catch (final ArithmeticException xrt) {
1289             if (!arithmetic.isStrict()) {
1290                 return 0.0d;
1291             }
1292             throw new JexlException(findNullOperand(node, left, right), "/ error", xrt);
1293         }
1294     }
1295     @Override
1296     protected Object visit(final ASTDoWhileStatement node, final Object data) {
1297         Object result = null;
1298         final int nc = node.jjtGetNumChildren();
1299         /* last objectNode is the condition */
1300         final JexlNode condition = node.jjtGetChild(nc - 1);
1301         do {
1302             cancelCheck(node);
1303             if (nc > 1) {
1304                 try {
1305                     // execute statement
1306                     result = node.jjtGetChild(0).jjtAccept(this, data);
1307                 } catch (final JexlException.Break stmtBreak) {
1308                     break;
1309                 } catch (final JexlException.Continue stmtContinue) {
1310                     //continue;
1311                 }
1312             }
1313         } while (testPredicate(condition, condition.jjtAccept(this, data)));
1314         return result;
1315     }
1316 
1317     /**
1318      * Evaluates the argument of {@code empty()}/{@code size()} while preserving cancellation and
1319      * strict-mode error propagation (JEXL-security f028).
1320      * <p>
1321      * {@link JexlArithmetic#evaluate(Log, Supplier)} logs and swallows <em>every</em> {@link JexlException}
1322      * (including {@link JexlException.Cancel}), masking the failure as the empty/0 result even in a strict
1323      * engine. Here cancellation is always rethrown and, in a strict engine, so is any other error; only a
1324      * lenient engine maps a failed argument evaluation to the {@link JexlEngine#TRY_FAILED} sentinel.
1325      * </p>
1326      *
1327      * @param arg the argument supplier
1328      * @return the evaluated value, or {@link JexlEngine#TRY_FAILED} on a swallowed lenient failure
1329      */
1330     private Object evaluateArgument(final Supplier<Object> arg) {
1331         try {
1332             return arg.get();
1333         } catch (final JexlException.Cancel xcancel) {
1334             // cancellation (and timeouts) must never be masked as an empty/0 result
1335             throw xcancel;
1336         } catch (final JexlException xjexl) {
1337             if (isStrictEngine()) {
1338                 // a strict engine surfaces the underlying error instead of masking it
1339                 throw xjexl;
1340             }
1341             // lenient: delegate to the arithmetic hook so subclasses can override fallback/logging
1342             return arithmetic.evaluate(logger, () -> { throw xjexl; });
1343         }
1344     }
1345 
1346     @Override
1347     protected Object visit(final ASTEmptyFunction node, final Object data) {
1348         final JexlNode arg = node.jjtGetChild(0);
1349         final Supplier<Object> eval = () -> arg.jjtAccept(this, data);
1350         Object value = evaluateArgument(eval);
1351         return value == JexlEngine.TRY_FAILED ? true : operators.empty(node, value);
1352     }
1353 
1354     @Override
1355     protected Object visit(final ASTEQNode node, final Object data) {
1356         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1357         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1358         try {
1359             final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
1360             return result != JexlEngine.TRY_FAILED ? result : arithmetic.equals(left, right);
1361         } catch (final ArithmeticException xrt) {
1362             throw new JexlException(findNullOperand(node, left, right), "== error", xrt);
1363         }
1364     }
1365 
1366     @Override
1367     protected Object visit(final ASTEQSNode node, final Object data) {
1368         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1369         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1370         try {
1371             final Object result = operators.tryOverload(node, JexlOperator.EQSTRICT, left, right);
1372             return result != JexlEngine.TRY_FAILED ? result : arithmetic.strictEquals(left, right);
1373         } catch (final ArithmeticException xrt) {
1374             throw new JexlException(findNullOperand(node, left, right), "=== error", xrt);
1375         }
1376     }
1377 
1378     @Override
1379     protected Object visit(final ASTERNode node, final Object data) {
1380         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1381         final JexlNode rightNode = node.jjtGetChild(1);
1382         final Object right = resolvePattern(rightNode, rightNode.jjtAccept(this, data));
1383         // note the arguments inversion between 'in'/'matches' and 'contains'
1384         // if x in y then y contains x
1385         return operators.contains(node, JexlOperator.CONTAINS, right, left);
1386     }
1387 
1388     /**
1389      * If the right operand of {@code =~} / {@code !~} is a string literal, compile it to a Pattern once and
1390      * cache the result in the node's value slot (same mechanism as negated numeric literals).
1391      * Dynamic string values (from variables) are returned unchanged.
1392      * The regex string length is validated before compilation (JEXL-security f012).
1393      * Uses double-check locking: first check without lock, then synchronized recheck-and-set to avoid
1394      * holding the lock during expensive Pattern.compile() when the same compiled script runs concurrently.
1395      */
1396     private static Object resolvePattern(final JexlNode rightNode, final Object right) {
1397         if (right instanceof CharSequence && rightNode instanceof JexlNode.Constant) {
1398             // First check with lock to avoid expensive Pattern.compile() if already cached
1399             synchronized (rightNode) {
1400                 Object cached = rightNode.jjtGetValue();
1401                 if (cached instanceof Pattern) {
1402                     return cached;
1403                 }
1404             }
1405             // Compile without holding lock
1406             final String regex = right.toString();
1407             if (regex.length() > JexlArithmetic.REGEX_PATTERN_MAX_LENGTH) {
1408                 throw new ArithmeticException("regular expression too long: " + regex.length()
1409                     + " > " + JexlArithmetic.REGEX_PATTERN_MAX_LENGTH);
1410             }
1411             final Pattern compiled = Pattern.compile(regex);
1412             // Double-check and set under lock
1413             synchronized (rightNode) {
1414                 Object cached = rightNode.jjtGetValue();
1415                 if (!(cached instanceof Pattern)) {
1416                     rightNode.jjtSetValue(compiled);
1417                 }
1418                 return cached instanceof Pattern ? cached : compiled;
1419             }
1420         }
1421         return right;
1422     }
1423 
1424     @Override
1425     protected Object visit(final ASTEWNode node, final Object data) {
1426         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1427         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1428         return operators.endsWith(node, JexlOperator.ENDSWITH, left, right);
1429     }
1430 
1431     @Override
1432     protected Object visit(final ASTExtendedLiteral node, final Object data) {
1433         return node;
1434     }
1435 
1436     @Override
1437     protected Object visit(final ASTFalseNode node, final Object data) {
1438         return Boolean.FALSE;
1439     }
1440 
1441     @Override
1442     protected Object visit(final ASTForeachStatement node, final Object data) {
1443         return node.getLoopForm() == 0 ? forIterator(node, data) : forLoop(node, data);
1444     }
1445 
1446     @Override
1447     protected Object visit(final ASTFunctionNode node, final Object data) {
1448         final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
1449         final String nsid = functionNode.getNamespace();
1450         final Object namespace = nsid != null? resolveNamespace(nsid, node) : context;
1451         final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
1452         return call(node, namespace, functionNode, argNode);
1453     }
1454 
1455     @Override
1456     protected Object visit(final ASTGENode node, final Object data) {
1457         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1458         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1459         try {
1460             final Object result = operators.tryOverload(node, JexlOperator.GTE, left, right);
1461             return result != JexlEngine.TRY_FAILED
1462                    ? result
1463                    : arithmetic.greaterThanOrEqual(left, right);
1464         } catch (final ArithmeticException xrt) {
1465             throw new JexlException(findNullOperand(node, left, right), ">= error", xrt);
1466         }
1467     }
1468 
1469     @Override
1470     protected Object visit(final ASTGetDecrementNode node, final Object data) {
1471         return executeAssign(node, JexlOperator.GET_AND_DECREMENT, data);
1472     }
1473 
1474     @Override
1475     protected Object visit(final ASTGetIncrementNode node, final Object data) {
1476         return executeAssign(node, JexlOperator.GET_AND_INCREMENT, data);
1477     }
1478 
1479     @Override
1480     protected Object visit(final ASTGTNode node, final Object data) {
1481         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1482         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1483         try {
1484             final Object result = operators.tryOverload(node, JexlOperator.GT, left, right);
1485             return result != JexlEngine.TRY_FAILED
1486                    ? result
1487                    : arithmetic.greaterThan(left, right);
1488         } catch (final ArithmeticException xrt) {
1489             throw new JexlException(findNullOperand(node, left, right), "> error", xrt);
1490         }
1491     }
1492 
1493     @Override
1494     protected Object visit(final ASTIdentifier identifier, final Object data) {
1495         cancelCheck(identifier);
1496         return data != null
1497                 ? getAttribute(data, identifier.getName(), identifier)
1498                 : getVariable(frame, block, identifier);
1499     }
1500 
1501     @Override
1502     protected Object visit(final ASTIdentifierAccess node, final Object data) {
1503         if (data == null) {
1504             return null;
1505         }
1506         final Object id = evalIdentifier(node);
1507         return getAttribute(data, id, node);
1508     }
1509 
1510     @Override
1511     protected Object visit(final ASTIfStatement node, final Object data) {
1512         final int n = 0;
1513         final int numChildren = node.jjtGetNumChildren();
1514         try {
1515             Object result = null;
1516             // pairs of { conditions , 'then' statement }
1517             for(int ifElse = 0; ifElse < numChildren - 1; ifElse += 2) {
1518                 final JexlNode testNode = node.jjtGetChild(ifElse);
1519                 final Object condition = testNode.jjtAccept(this, null);
1520                 if (testPredicate(testNode, condition)) {
1521                     // first objectNode is true statement
1522                     return node.jjtGetChild(ifElse + 1).jjtAccept(this, null);
1523                 }
1524             }
1525             // if odd...
1526             if ((numChildren & 1) == 1) {
1527                 // If there is an else, it is the last child of an odd number of children in the statement,
1528                 // execute it.
1529                 result = node.jjtGetChild(numChildren - 1).jjtAccept(this, null);
1530             }
1531             return result;
1532         } catch (final ArithmeticException xrt) {
1533             throw new JexlException(node.jjtGetChild(n), "if error", xrt);
1534         }
1535     }
1536 
1537     @Override
1538     protected Object visit(final ASTIncrementGetNode node, final Object data) {
1539         return executeAssign(node, JexlOperator.INCREMENT_AND_GET, data);
1540     }
1541 
1542     @Override
1543     protected Object visit(final ASTInstanceOf node, final Object data) {
1544         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1545         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1546         return isInstance(left, right);
1547     }
1548 
1549     @Override
1550     protected Object visit(final ASTJexlScript script, final Object data) {
1551         if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
1552             final Closure closure = new Closure(this, (ASTJexlLambda) script);
1553             // if the function is named, assign in the local frame
1554             final JexlNode child0 = script.jjtGetChild(0);
1555             if (child0 instanceof ASTVar) {
1556                 final ASTVar variable = (ASTVar) child0;
1557                 this.visit(variable, data);
1558                 final int symbol = variable.getSymbol();
1559                 frame.set(symbol, closure);
1560                 // make the closure accessible to itself, ie capture the 'function' variable after frame creation
1561                 closure.captureSelfIfRecursive(frame, symbol);
1562             }
1563             return closure;
1564         }
1565         block = new LexicalFrame(frame, block).defineArgs();
1566         try {
1567             return runScript(script, data);
1568         } finally {
1569             block = block.pop();
1570         }
1571     }
1572 
1573     protected final Object runScript(final ASTJexlScript script, final Object data) {
1574         final int numChildren = script.jjtGetNumChildren();
1575         Object result = null;
1576         for (int i = 0; i < numChildren; i++) {
1577             final JexlNode child = script.jjtGetChild(i);
1578             result = child.jjtAccept(this, data);
1579             cancelCheck(child);
1580         }
1581         return result;
1582     }
1583 
1584     @Override
1585     protected Object visit(final ASTLENode node, final Object data) {
1586         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1587         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1588         try {
1589             final Object result = operators.tryOverload(node, JexlOperator.LTE, left, right);
1590             return result != JexlEngine.TRY_FAILED
1591                    ? result
1592                    : arithmetic.lessThanOrEqual(left, right);
1593         } catch (final ArithmeticException xrt) {
1594             throw new JexlException(findNullOperand(node, left, right), "<= error", xrt);
1595         }
1596     }
1597 
1598     @Override
1599     protected Object visit(final ASTLTNode node, final Object data) {
1600         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1601         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1602         try {
1603             final Object result = operators.tryOverload(node, JexlOperator.LT, left, right);
1604             return result != JexlEngine.TRY_FAILED
1605                    ? result
1606                    : arithmetic.lessThan(left, right);
1607         } catch (final ArithmeticException xrt) {
1608             throw new JexlException(findNullOperand(node, left, right), "< error", xrt);
1609         }
1610     }
1611 
1612     @Override
1613     protected Object visit(final ASTMapEntry node, final Object data) {
1614         final Object key = node.jjtGetChild(0).jjtAccept(this, data);
1615         final Object value = node.jjtGetChild(1).jjtAccept(this, data);
1616         return new Object[]{key, value};
1617     }
1618 
1619     @Override
1620     protected Object visit(final ASTMapLiteral node, final Object data) {
1621         final int childCount = node.jjtGetNumChildren();
1622         final JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount, node.isExtended());
1623         for (int i = 0; i < childCount; i++) {
1624             cancelCheck(node);
1625             final JexlNode child = node.jjtGetChild(i);
1626             if (!(child instanceof ASTExtendedLiteral)) {
1627                 final Object[] entry = (Object[]) child.jjtAccept(this, data);
1628                 mb.put(entry[0], entry[1]);
1629             }
1630         }
1631         return mb.create();
1632     }
1633 
1634     @Override
1635     protected Object visit(final ASTMethodNode node, final Object data) {
1636         return visit(node, null, data);
1637     }
1638 
1639     /**
1640      * Execute a method call, ie syntactically written as name.call(...).
1641      *
1642      * @param node The actual method call node
1643      * @param antish non-null when name.call is an antish variable
1644      * @param data The context
1645      * @return The method call result
1646      */
1647     private Object visit(final ASTMethodNode node, final Object antish, final Object data) {
1648         Object object = antish;
1649         // left contains the reference to the method
1650         final JexlNode methodNode = node.jjtGetChild(0);
1651         Object method;
1652         // 1: determine object and method or functor
1653         if (methodNode instanceof ASTIdentifierAccess) {
1654             method = methodNode;
1655             if (object == null) {
1656                 object = data;
1657                 if (object == null) {
1658                     // no object, we fail
1659                     return node.isSafeLhs(isSafe())
1660                         ? null
1661                         : unsolvableMethod(methodNode, "<null>.<?>(...)");
1662                 }
1663             } else {
1664                 // edge case of antish var used as functor
1665                 method = object;
1666             }
1667         } else {
1668             method = methodNode.jjtAccept(this, data);
1669         }
1670         Object result = method;
1671         for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
1672             if (result == null) {
1673                 // no method, we fail// variable unknown in context and not a local
1674                 return node.isSafeLhs(isSafe())
1675                         ? null
1676                         : unsolvableMethod(methodNode, "<?>.<null>(...)");
1677             }
1678             final ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
1679             result = call(node, object, result, argNode);
1680             object = result;
1681         }
1682         return result;
1683     }
1684 
1685     @Override
1686     protected Object visit(final ASTModNode node, final Object data) {
1687         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1688         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1689         try {
1690             final Object result = operators.tryOverload(node, JexlOperator.MOD, left, right);
1691             return result != JexlEngine.TRY_FAILED ? result : arithmetic.mod(left, right);
1692         } catch (final ArithmeticException xrt) {
1693             if (!arithmetic.isStrict()) {
1694                 return 0.0d;
1695             }
1696             throw new JexlException(findNullOperand(node, left, right), "% error", xrt);
1697         }
1698     }
1699 
1700     @Override
1701     protected Object visit(final ASTMulNode node, final Object data) {
1702         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1703         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1704         try {
1705             final Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, left, right);
1706             return result != JexlEngine.TRY_FAILED ? result : arithmetic.multiply(left, right);
1707         } catch (final ArithmeticException xrt) {
1708             throw new JexlException(findNullOperand(node, left, right), "* error", xrt);
1709         }
1710     }
1711 
1712     @Override
1713     protected Object visit(final ASTNENode node, final Object data) {
1714         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1715         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1716         try {
1717             final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
1718             return result != JexlEngine.TRY_FAILED
1719                    ? !arithmetic.toBoolean(result)
1720                    : !arithmetic.equals(left, right);
1721         } catch (final ArithmeticException xrt) {
1722             throw new JexlException(findNullOperand(node, left, right), "!= error", xrt);
1723         }
1724     }
1725 
1726     @Override
1727     protected Object visit(final ASTNESNode node, final Object data) {
1728         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1729         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1730         try {
1731             final Object result = operators.tryOverload(node, JexlOperator.EQSTRICT, left, right);
1732             return result != JexlEngine.TRY_FAILED
1733                 ? !arithmetic.toBoolean(result)
1734                 : !arithmetic.strictEquals(left, right);
1735         } catch (final ArithmeticException xrt) {
1736             throw new JexlException(findNullOperand(node, left, right), "!== error", xrt);
1737         }
1738     }
1739 
1740     @Override
1741     protected Object visit(final ASTNEWNode node, final Object data) {
1742         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1743         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1744         return operators.endsWith(node, JexlOperator.NOT_ENDSWITH, left, right);
1745     }
1746 
1747     @Override
1748 
1749     protected Object visit(final ASTNotInstanceOf node, final Object data) {
1750         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1751         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1752         return !isInstance(left, right);
1753     }
1754 
1755     @Override
1756     protected Object visit(final ASTNotNode node, final Object data) {
1757         final Object val = node.jjtGetChild(0).jjtAccept(this, data);
1758         try {
1759             final Object result = operators.tryOverload(node, JexlOperator.NOT, val);
1760             return result != JexlEngine.TRY_FAILED ? result : arithmetic.not(val);
1761         } catch (final ArithmeticException xrt) {
1762             throw new JexlException(node, "! error", xrt);
1763         }
1764     }
1765 
1766     @Override
1767     protected Object visit(final ASTNRNode node, final Object data) {
1768         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1769         final JexlNode rightNode = node.jjtGetChild(1);
1770         final Object right = resolvePattern(rightNode, rightNode.jjtAccept(this, data));
1771         // note the arguments inversion between (not) 'in'/'matches' and  (not) 'contains'
1772         // if x not-in y then y not-contains x
1773         return operators.contains(node, JexlOperator.NOT_CONTAINS, right, left);
1774     }
1775 
1776     @Override
1777     protected Object visit(final ASTNSWNode node, final Object data) {
1778         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1779         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1780         return operators.startsWith(node, JexlOperator.NOT_STARTSWITH, left, right);
1781     }
1782 
1783     @Override
1784     protected Object visit(final ASTNullLiteral node, final Object data) {
1785         return null;
1786     }
1787 
1788     @Override
1789     protected Object visit(final ASTNullpNode node, final Object data) {
1790         Object lhs;
1791         try {
1792             lhs = node.jjtGetChild(0).jjtAccept(this, data);
1793         } catch (final JexlException xany) {
1794             if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
1795                 throw xany;
1796             }
1797             lhs = null;
1798         }
1799         // null elision as in "x ?? z"
1800         return lhs != null ? lhs : node.jjtGetChild(1).jjtAccept(this, data);
1801     }
1802 
1803     @Override
1804     protected Object visit(final ASTNumberLiteral node, final Object data) {
1805         if (data != null && node.isInteger()) {
1806             return getAttribute(data, node.getLiteral(), node);
1807         }
1808         return node.getLiteral();
1809     }
1810 
1811     @Override
1812     protected Object visit(final ASTQualifiedIdentifier node, final Object data) {
1813         return resolveClassName(node.getName());
1814     }
1815 
1816     @Override
1817     protected Object visit(final ASTRangeNode node, final Object data) {
1818         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
1819         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
1820         try {
1821             return arithmetic.createRange(left, right);
1822         } catch (final ArithmeticException xrt) {
1823             throw new JexlException(findNullOperand(node, left, right), ".. error", xrt);
1824         }
1825     }
1826 
1827     @Override
1828     protected Object visit(final ASTReference node, final Object data) {
1829         cancelCheck(node);
1830         final int numChildren = node.jjtGetNumChildren();
1831         final JexlNode parent = node.jjtGetParent();
1832         // pass first piece of data in and loop through children
1833         Object object = null;
1834         JexlNode objectNode = null;
1835         JexlNode ptyNode = null;
1836         StringBuilder ant = null;
1837         boolean antish = !(parent instanceof ASTReference) && options.isAntish();
1838         int v = 1;
1839         main:
1840         for (int c = 0; c < numChildren; c++) {
1841             objectNode = node.jjtGetChild(c);
1842             if (objectNode instanceof ASTMethodNode) {
1843                 antish = false;
1844                 if (object == null) {
1845                     // we may be performing a method call on an antish var
1846                     if (ant != null) {
1847                         final JexlNode child = objectNode.jjtGetChild(0);
1848                         if (child instanceof ASTIdentifierAccess) {
1849                             final int alen = ant.length();
1850                             ant.append('.');
1851                             ant.append(((ASTIdentifierAccess) child).getName());
1852                             object = context.get(ant.toString());
1853                             if (object != null) {
1854                                 object = visit((ASTMethodNode) objectNode, object, context);
1855                                 continue;
1856                             }
1857                             // remove method name from antish
1858                             ant.delete(alen, ant.length());
1859                             ptyNode = objectNode;
1860                         }
1861                     }
1862                     break;
1863                 }
1864             } else if (objectNode instanceof ASTArrayAccess) {
1865                 antish = false;
1866                 if (object == null) {
1867                     ptyNode = objectNode;
1868                     break;
1869                 }
1870             }
1871             // attempt to evaluate the property within the object (visit(ASTIdentifierAccess node))
1872             object = objectNode.jjtAccept(this, object);
1873             cancelCheck(node);
1874             if (object != null) {
1875                 // disallow mixing antish variable & bean with same root; avoid ambiguity
1876                 antish = false;
1877             } else if (antish) {
1878                 // create first from first node
1879                 if (ant == null) {
1880                     // if we still have a null object, check for an antish variable
1881                     final JexlNode first = node.jjtGetChild(0);
1882                     if (!(first instanceof ASTIdentifier)) {
1883                         // not an identifier, not antish
1884                         ptyNode = objectNode;
1885                         break main;
1886                     }
1887                     final ASTIdentifier afirst = (ASTIdentifier) first;
1888                     ant = new StringBuilder(afirst.getName());
1889                     continue;
1890                     // skip the first node case since it was trialed in jjtAccept above and returned null
1891                 }
1892                 // catch up to current node
1893                 for (; v <= c; ++v) {
1894                     final JexlNode child = node.jjtGetChild(v);
1895                     if (!(child instanceof ASTIdentifierAccess)) {
1896                         // not an identifier, not antish
1897                         ptyNode = objectNode;
1898                         break main;
1899                     }
1900                     final ASTIdentifierAccess achild = (ASTIdentifierAccess) child;
1901                     if (achild.isSafe() || achild.isExpression()) {
1902                         break main;
1903                     }
1904                     ant.append('.');
1905                     ant.append(achild.getName());
1906                 }
1907                 // solve antish
1908                 object = context.get(ant.toString());
1909             } else if (c != numChildren - 1) {
1910                 // only the last one may be null
1911                 ptyNode = c == 0 && numChildren > 1 ? node.jjtGetChild(1) : objectNode;
1912                 break; //
1913             }
1914         }
1915         // dealing with null
1916         if (object == null) {
1917             if (ptyNode != null) {
1918                 if (ptyNode.isSafeLhs(isSafe())) {
1919                     return null;
1920                 }
1921                 if (ant != null) {
1922                     final String aname = ant.toString();
1923                     final boolean defined = isVariableDefined(frame, block, aname);
1924                     return unsolvableVariable(node, aname, !defined);
1925                 }
1926                 return unsolvableProperty(node,
1927                         stringifyProperty(ptyNode), ptyNode == objectNode, null);
1928             }
1929             if (antish) {
1930                 if (node.isSafeLhs(isSafe())) {
1931                     return null;
1932                 }
1933                 final String aname = Objects.toString(ant, "?");
1934                 final boolean defined = isVariableDefined(frame, block, aname);
1935                 // defined but null; arg of a strict operator?
1936                 if (defined && !isStrictOperand(node)) {
1937                     return null;
1938                 }
1939                 return unsolvableVariable(node, aname, !defined);
1940             }
1941         }
1942         return object;
1943     }
1944 
1945     @Override
1946     protected Object visit(final ASTReferenceExpression node, final Object data) {
1947         return node.jjtGetChild(0).jjtAccept(this, data);
1948     }
1949 
1950     @Override
1951     protected Object visit(final ASTRegexLiteral node, final Object data) {
1952         return node.getLiteral();
1953     }
1954 
1955     @Override
1956     protected Object visit(final ASTReturnStatement node, final Object data) {
1957         final Object val = node.jjtGetNumChildren() == 1
1958             ? node.jjtGetChild(0).jjtAccept(this, data)
1959             : null;
1960         cancelCheck(node);
1961         final JexlNode parent = node.jjtGetParent();
1962         // if return is last child of script, no need to throw
1963         if (parent instanceof ASTJexlScript &&
1964             parent.jjtGetChild(parent.jjtGetNumChildren() - 1) == node) {
1965             return val;
1966         }
1967         throw new JexlException.Return(node, null, val);
1968     }
1969 
1970     @Override
1971     protected Object visit(final ASTSetAddNode node, final Object data) {
1972         return executeAssign(node, JexlOperator.SELF_ADD, data);
1973     }
1974 
1975     @Override
1976     protected Object visit(final ASTSetAndNode node, final Object data) {
1977         return executeAssign(node, JexlOperator.SELF_AND, data);
1978     }
1979 
1980     @Override
1981     protected Object visit(final ASTSetDivNode node, final Object data) {
1982         return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
1983     }
1984 
1985     @Override
1986     protected Object visit(final ASTSetLiteral node, final Object data) {
1987         final int childCount = node.jjtGetNumChildren();
1988         final JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount, node.isExtended());
1989         for (int i = 0; i < childCount; i++) {
1990             cancelCheck(node);
1991             final JexlNode child = node.jjtGetChild(i);
1992             if (!(child instanceof ASTExtendedLiteral)) {
1993                 final Object entry = child.jjtAccept(this, data);
1994                 mb.add(entry);
1995             }
1996         }
1997         return mb.create();
1998     }
1999 
2000     @Override
2001     protected Object visit(final ASTSetModNode node, final Object data) {
2002         return executeAssign(node, JexlOperator.SELF_MOD, data);
2003     }
2004 
2005     @Override
2006     protected Object visit(final ASTSetMultNode node, final Object data) {
2007         return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
2008     }
2009 
2010     @Override
2011     protected Object visit(final ASTSetOrNode node, final Object data) {
2012         return executeAssign(node, JexlOperator.SELF_OR, data);
2013     }
2014 
2015     @Override
2016     protected Object visit(final ASTSetShiftLeftNode node, final Object data) {
2017         return executeAssign(node, JexlOperator.SELF_SHIFTLEFT, data);
2018     }
2019 
2020     @Override
2021     protected Object visit(final ASTSetShiftRightNode node, final Object data) {
2022         return executeAssign(node, JexlOperator.SELF_SHIFTRIGHT, data);
2023     }
2024 
2025     @Override
2026     protected Object visit(final ASTSetShiftRightUnsignedNode node, final Object data) {
2027         return executeAssign(node, JexlOperator.SELF_SHIFTRIGHTU, data);
2028     }
2029 
2030     @Override
2031     protected Object visit(final ASTSetSubNode node, final Object data) {
2032         return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
2033     }
2034 
2035     @Override
2036     protected Object visit(final ASTSetXorNode node, final Object data) {
2037         return executeAssign(node, JexlOperator.SELF_XOR, data);
2038     }
2039 
2040     @Override
2041     protected Object visit(final ASTShiftLeftNode node, final Object data) {
2042         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2043         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2044         try {
2045             final Object result = operators.tryOverload(node, JexlOperator.SHIFTLEFT, left, right);
2046             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftLeft(left, right);
2047         } catch (final ArithmeticException xrt) {
2048             throw new JexlException(findNullOperand(node, left, right), "<< error", xrt);
2049         }
2050     }
2051 
2052     @Override
2053     protected Object visit(final ASTShiftRightNode node, final Object data) {
2054         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2055         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2056         try {
2057             final Object result = operators.tryOverload(node, JexlOperator.SHIFTRIGHT, left, right);
2058             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRight(left, right);
2059         } catch (final ArithmeticException xrt) {
2060             throw new JexlException(findNullOperand(node, left, right), ">> error", xrt);
2061         }
2062     }
2063 
2064     @Override
2065     protected Object visit(final ASTShiftRightUnsignedNode node, final Object data) {
2066         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2067         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2068         try {
2069             final Object result = operators.tryOverload(node, JexlOperator.SHIFTRIGHTU, left, right);
2070             return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRightUnsigned(left, right);
2071         } catch (final ArithmeticException xrt) {
2072             throw new JexlException(findNullOperand(node, left, right), ">>> error", xrt);
2073         }
2074     }
2075 
2076     @Override
2077     protected Object visit(final ASTSizeFunction node, final Object data) {
2078         final JexlNode arg = node.jjtGetChild(0);
2079         final Supplier<Object> eval = () -> arg.jjtAccept(this, data);
2080         Object value = evaluateArgument(eval);
2081         return value == JexlEngine.TRY_FAILED ? 0 : operators.size(node, value);
2082     }
2083 
2084     @Override
2085     protected Object visit(final ASTStringLiteral node, final Object data) {
2086         if (data != null) {
2087             return getAttribute(data, node.getLiteral(), node);
2088         }
2089         return node.getLiteral();
2090     }
2091 
2092     @Override
2093     protected Object visit(final ASTSubNode node, final Object data) {
2094         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2095         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2096         try {
2097             final Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, left, right);
2098             return result != JexlEngine.TRY_FAILED ? result : arithmetic.subtract(left, right);
2099         } catch (final ArithmeticException xrt) {
2100             throw new JexlException(findNullOperand(node, left, right), "- error", xrt);
2101         }
2102     }
2103 
2104     @Override
2105     protected Object visit(final ASTSWNode node, final Object data) {
2106         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
2107         final Object right = node.jjtGetChild(1).jjtAccept(this, data);
2108         return operators.startsWith(node, JexlOperator.STARTSWITH, left, right);
2109     }
2110 
2111 
2112     @Override
2113     protected Object visit(final ASTSwitchExpression node, final Object data) {
2114         final Object value = node.jjtGetChild(0).jjtAccept(this, data);
2115         final int index = node.switchIndex(value);
2116         if (index >= 0) {
2117           return node.jjtGetChild(index).jjtAccept(this, data);
2118         }
2119         if (isStrictEngine()) {
2120           throw new JexlException(node, "no case in switch expression for: " + value);
2121         }
2122         return null;
2123     }
2124 
2125     @Override
2126     protected Object visit(final ASTSwitchStatement node, final Object data) {
2127         final int count = node.jjtGetNumChildren();
2128         Object value = node.jjtGetChild(0).jjtAccept(this, data);
2129         final int index = node.switchIndex(value);
2130         if (index > 0) {
2131             if (!node.isStatement()) {
2132                 return node.jjtGetChild(index).jjtAccept(this, data);
2133             }
2134             for (int i = index; i < count; ++i) {
2135                 try {
2136                     // evaluate the switch body
2137                     value = node.jjtGetChild(i).jjtAccept(this, data);
2138                 } catch (final JexlException.Break xbreak) {
2139                    break; // break out of the switch
2140                 }
2141                 // NOTE: JexlException.Continue is intentionally NOT caught here. Like Java, 'continue'
2142                 // targets the enclosing loop, not the switch, so it must propagate out of the switch
2143                 // rather than fall through into the following cases (JEXL-security f029).
2144             }
2145             return value;
2146         }
2147         if (isStrictEngine()) {
2148             throw new JexlException(node, "no case in switch statement for: " + value);
2149         }
2150         return null;
2151     }
2152 
2153     @Override
2154     protected Object visit(final ASTTernaryNode node, final Object data) {
2155         Object condition;
2156         try {
2157             condition = node.jjtGetChild(0).jjtAccept(this, data);
2158         } catch (final JexlException xany) {
2159             if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
2160                 throw xany;
2161             }
2162             condition = null;
2163         }
2164         // ternary as in "x ? y : z"
2165         if (node.jjtGetNumChildren() == 3) {
2166             if (condition != null && arithmetic.testPredicate(condition)) {
2167                 return node.jjtGetChild(1).jjtAccept(this, data);
2168             }
2169             return node.jjtGetChild(2).jjtAccept(this, data);
2170         }
2171         // elvis as in "x ?: z"
2172         if (condition != null && arithmetic.testPredicate(condition)) {
2173             return condition;
2174         }
2175         return node.jjtGetChild(1).jjtAccept(this, data);
2176     }
2177 
2178     @Override
2179     protected Object visit(final ASTThrowStatement node, final Object data) {
2180         final Object thrown = node.jjtGetChild(0).jjtAccept(this, data);
2181         throw new JexlException.Throw(node, thrown);
2182     }
2183 
2184     @Override
2185     protected Object visit(final ASTTrueNode node, final Object data) {
2186         return Boolean.TRUE;
2187     }
2188 
2189     @Override
2190     protected Object visit(final ASTTryResources node, final Object data) {
2191         final int bodyChild = node.jjtGetNumChildren() - 1;
2192         final JexlNode tryBody = node.jjtGetChild(bodyChild);
2193         final Queue<Object> tryResult = new ArrayDeque<>(bodyChild);
2194         final LexicalFrame locals;
2195         // sequence of var declarations with/without assignment
2196         if (node.getSymbolCount() > 0) {
2197             locals = new LexicalFrame(frame, block);
2198             block = locals;
2199         } else {
2200             locals = null;
2201         }
2202         try {
2203             for(int c = 0; c < bodyChild; ++c) {
2204                 final JexlNode tryResource = node.jjtGetChild(c);
2205                 final Object result = tryResource.jjtAccept(this, data);
2206                 if (result != null) {
2207                     tryResult.add(result);
2208                 }
2209             }
2210             // evaluate the body
2211             return tryBody.jjtAccept(this, data);
2212         } finally {
2213             closeIfSupported(tryResult);
2214             // restore lexical frame
2215             if (locals != null) {
2216                 block = block.pop();
2217             }
2218         }
2219     }
2220 
2221     @Override
2222     protected Object visit(final ASTTryStatement node, final Object data) {
2223         int nc = 0;
2224         final JexlNode tryBody = node.jjtGetChild(nc++);
2225         JexlException rethrow = null;
2226         JexlException flowControl = null;
2227         Object result = null;
2228         try {
2229             // evaluate the try
2230             result = tryBody.jjtAccept(this, data);
2231         } catch(JexlException.Return | JexlException.Cancel |
2232                 JexlException.Break | JexlException.Continue xflow) {
2233             // flow control exceptions do not trigger the catch clause
2234             flowControl = xflow;
2235         } catch(final JexlException xany) {
2236             rethrow = xany;
2237         }
2238         JexlException thrownByCatch = null;
2239         if (rethrow != null && node.hasCatchClause()) {
2240             final ASTReference catchVar = (ASTReference) node.jjtGetChild(nc++);
2241             final JexlNode catchBody = node.jjtGetChild(nc++);
2242             // if we caught an exception and have a catch body, evaluate it
2243             try {
2244                 // evaluate the catch
2245                 result = evalCatch(catchVar, catchBody, rethrow, data);
2246                 // if catch body evaluates, do not rethrow
2247                 rethrow = null;
2248             } catch (JexlException.Return | JexlException.Cancel |
2249                      JexlException.Break | JexlException.Continue alterFlow) {
2250                 flowControl = alterFlow;
2251             } catch (final JexlException exception) {
2252                 // catching an exception thrown from catch body; can be a (re)throw
2253                 rethrow = thrownByCatch = exception;
2254             }
2255         }
2256         // if we have a 'finally' block, no matter what, evaluate it: its control flow will
2257         // take precedence over what the 'catch' block might have thrown.
2258         if (node.hasFinallyClause()) {
2259             final JexlNode finallyBody = node.jjtGetChild(node.jjtGetNumChildren() - 1);
2260             try {
2261                 finallyBody.jjtAccept(this, data);
2262             } catch (JexlException.Break | JexlException.Continue | JexlException.Return flowException) {
2263                 // potentially swallow previous, even return but not cancel
2264                 if (!(flowControl instanceof JexlException.Cancel)) {
2265                     flowControl = flowException;
2266                 }
2267             } catch (final JexlException.Cancel cancelException) {
2268                 // cancel swallows everything
2269                 flowControl = cancelException;
2270             } catch (final JexlException exception) {
2271                 // catching an exception thrown in finally body
2272                 if (jexl.logger.isDebugEnabled()) {
2273                     jexl.logger.debug("exception thrown in finally", exception);
2274                 }
2275                 // swallow the caught one
2276                 rethrow = exception;
2277             }
2278         }
2279         if (flowControl != null) {
2280             if (thrownByCatch != null && jexl.logger.isDebugEnabled()) {
2281                 jexl.logger.debug("finally swallowed exception thrown by catch", thrownByCatch);
2282             }
2283             throw flowControl;
2284         }
2285         if (rethrow != null) {
2286             throw rethrow;
2287         }
2288         return result;
2289     }
2290 
2291     @Override
2292     protected Object visit(final ASTUnaryMinusNode node, final Object data) {
2293         // use cached value if literal
2294         final Object value = node.jjtGetValue();
2295         if (value instanceof Number) {
2296             return value;
2297         }
2298         final JexlNode valNode = node.jjtGetChild(0);
2299         final Object val = valNode.jjtAccept(this, data);
2300         try {
2301             final Object result = operators.tryOverload(node, JexlOperator.NEGATE, val);
2302             if (result != JexlEngine.TRY_FAILED) {
2303                 return result;
2304             }
2305             Object number = arithmetic.negate(val);
2306             // attempt to recoerce to literal class
2307             // cache if number literal and negate is idempotent
2308             if (number instanceof Number && valNode instanceof ASTNumberLiteral) {
2309                 number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
2310                 if (arithmetic.isNegateStable()) {
2311                     node.jjtSetValue(number);
2312                 }
2313             }
2314             return number;
2315         } catch (final ArithmeticException xrt) {
2316             throw new JexlException(valNode, "- error", xrt);
2317         }
2318     }
2319 
2320     @Override
2321     protected Object visit(final ASTUnaryPlusNode node, final Object data) {
2322         // use cached value if literal
2323         final Object value = node.jjtGetValue();
2324         if (value instanceof Number) {
2325             return value;
2326         }
2327         final JexlNode valNode = node.jjtGetChild(0);
2328         final Object val = valNode.jjtAccept(this, data);
2329         try {
2330             final Object result = operators.tryOverload(node, JexlOperator.POSITIVIZE, val);
2331             if (result != JexlEngine.TRY_FAILED) {
2332                 return result;
2333             }
2334             final Object number = arithmetic.positivize(val);
2335             if (valNode instanceof ASTNumberLiteral
2336                 && number instanceof Number
2337                 && arithmetic.isPositivizeStable()) {
2338                 node.jjtSetValue(number);
2339             }
2340             return number;
2341         } catch (final ArithmeticException xrt) {
2342             throw new JexlException(valNode, "+ error", xrt);
2343         }
2344     }
2345 
2346     @Override
2347     protected Object visit(final ASTVar node, final Object data) {
2348         final int symbol = node.getSymbol();
2349         // if we have a var, we have a scope thus a frame
2350         if (!options.isLexical() && !node.isLexical()) {
2351             if (frame.has(symbol)) {
2352                 return frame.get(symbol);
2353             }
2354         } else if (!defineVariable(node, block)) {
2355             return redefinedVariable(node, node.getName());
2356         }
2357         frame.set(symbol, null);
2358         return null;
2359     }
2360 
2361     @Override
2362     protected Object visit(final ASTWhileStatement node, final Object data) {
2363         Object result = null;
2364         /* first objectNode is the condition */
2365         final JexlNode condition = node.jjtGetChild(0);
2366         while (testPredicate(condition, condition.jjtAccept(this, data))) {
2367             cancelCheck(node);
2368             if (node.jjtGetNumChildren() > 1) {
2369                 try {
2370                     // execute statement
2371                     result = node.jjtGetChild(1).jjtAccept(this, data);
2372                 } catch (final JexlException.Break stmtBreak) {
2373                     break;
2374                 } catch (final JexlException.Continue stmtContinue) {
2375                     //continue;
2376                 }
2377             }
2378         }
2379         return result;
2380     }
2381 
2382     /**
2383      * Base visitation for blocks.
2384      *
2385      * @param node The block
2386      * @param data The usual data
2387      * @return The result of the last expression evaluation
2388      */
2389     private Object visitBlock(final ASTBlock node, final Object data) {
2390         final int numChildren = node.jjtGetNumChildren();
2391         Object result = null;
2392         for (int i = 0; i < numChildren; i++) {
2393             cancelCheck(node);
2394             result = node.jjtGetChild(i).jjtAccept(this, data);
2395         }
2396         return result;
2397     }
2398 
2399     /**
2400      * Runs a node.
2401      *
2402      * @param node The node
2403      * @param data The usual data, always null
2404      * @return The return value
2405      */
2406     protected Object visitLexicalNode(final JexlNode node, final Object data) {
2407         block = new LexicalFrame(frame, null);
2408         try {
2409             return node.jjtAccept(this, data);
2410         } finally {
2411             block = block.pop();
2412         }
2413     }
2414 }