View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.io.StringReader;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Deque;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.NoSuchElementException;
38  import java.util.Set;
39  import java.util.TreeSet;
40  import java.util.concurrent.ExecutorService;
41  import java.util.concurrent.Executors;
42  
43  import org.apache.commons.jexl3.internal.TemplateDebugger;
44  import org.apache.commons.jexl3.introspection.JexlSandbox;
45  import org.apache.commons.logging.Log;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  
49  /**
50   * Test cases for reported issue between JEXL-200 and JEXL-299.
51   */
52  @SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
53  class Issues200Test extends JexlTestCase {
54      public static class Arithmetic266 extends JexlArithmetic {
55          static final ThreadLocal<Deque<Iterator266>> TLS_FOREACH = new ThreadLocal<Deque<Iterator266>>() {
56              @Override
57              public Deque<Iterator266> initialValue() {
58                  return new LinkedList<>();
59              }
60          };
61          static void closeIterator(final Iterator266 i266) {
62              final Deque<Iterator266> queue = TLS_FOREACH.get();
63              if (queue != null) {
64                  queue.remove(i266);
65              }
66          }
67  
68          public Arithmetic266(final boolean strict) {
69              super(strict);
70          }
71  
72          public Iterator<?> forEach(final Iterable<?> collection) {
73              final Iterator266 it266 = new Iterator266((Iterator<Object>) collection.iterator());
74              final Deque<Iterator266> queue = TLS_FOREACH.get();
75              queue.addFirst(it266);
76              return it266;
77          }
78  
79          public Iterator<?> forEach(final Map<?,?> collection) {
80              return forEach(collection.values());
81          }
82  
83          public void remove() {
84              final Deque<Iterator266> queue = TLS_FOREACH.get();
85              final Iterator266 i266 = queue.getFirst();
86              if (i266 != null) {
87                  i266.remove();
88                  throw new JexlException.Continue(null);
89              }
90              throw new NoSuchElementException();
91          }
92      }
93  
94      public static class Cls298 {
95          int sz = 42;
96  
97          public boolean isEmpty() {
98              return sz <= 0;
99          }
100 
101         public int size() {
102             return sz;
103         }
104 
105         public int size(final int x) {
106             return sz + x;
107         }
108     }
109 
110     public static class Context225 extends MapContext {
111         public String bar(){
112             return "bar";
113         }
114     }
115 
116     public static class Context279 extends MapContext {
117         public Number identity(final Number x) {
118             return x;
119         }
120         public String identity(final String x) {
121             return x;
122         }
123         public String[] spread(final String str) {
124             if (str == null) {
125                 return null;
126             }
127              final String[] a = new String[str.length()];
128              for(int i = 0; i < str.length(); ++i) {
129                  a[i] = "" + str.charAt(i);
130              }
131              return a;
132         }
133     }
134 
135     public static class Eval {
136         private JexlEngine jexl;
137 
138         public JexlScript fn(final String src) {
139             return jexl.createScript(src);
140         }
141 
142         void setJexl(final JexlEngine je) {
143             jexl = je;
144         }
145     }
146 
147     public static class Foo245 {
148         private Object bar;
149 
150         public Object getBar() {
151             return bar;
152         }
153 
154         void setBar(final Object bar) {
155             this.bar = bar;
156         }
157     }
158 
159     /**
160      * An iterator that implements Closeable (at least implements a close method).
161      */
162     public static class Iterator266 implements /*Closeable,*/ Iterator<Object> {
163         private Iterator<Object> iterator;
164 
165         Iterator266(final Iterator<Object> ator) {
166             iterator = ator;
167         }
168 
169         //@Override
170         public void close() {
171             if (iterator != null) {
172                 Arithmetic266.closeIterator(this);
173                 iterator = null;
174             }
175         }
176 
177         @Override
178         protected void finalize() throws Throwable {
179             close();
180             super.finalize();
181         }
182 
183         @Override
184         public boolean hasNext() {
185             if (iterator == null) {
186                 return false;
187             }
188             final boolean n = iterator.hasNext();
189             if (!n) {
190                 close();
191             }
192             return n;
193         }
194 
195         @Override
196         public Object next() {
197             if (iterator == null) {
198                 throw new NoSuchElementException();
199             }
200             return iterator.next();
201         }
202 
203         @Override
204         public void remove() {
205             if (iterator != null) {
206                 iterator.remove();
207             }
208         }
209     }
210 
211     public static class JexlArithmetic224 extends JexlArithmetic {
212         public JexlArithmetic224(final boolean astrict) {
213             super(astrict);
214         }
215 
216         public Object arrayGet(final Collection<?> c, final Number n) {
217             return nth(c, n.intValue());
218         }
219 
220         public Object call(final Collection<?> c, final Number n) {
221             if (c instanceof List) {
222                 return ((List<?>) c).get(n.intValue());
223             }
224             return nth(c, n.intValue());
225         }
226 
227         protected Object nth(final Collection<?> c, int i) {
228             if (c instanceof List) {
229                 // tell engine to use default
230                 return JexlEngine.TRY_FAILED;
231             }
232             for (final Object o : c) {
233                 if (i-- == 0) {
234                     return o;
235                 }
236             }
237             return null;
238         }
239 
240         public Object propertyGet(final Collection<?> c, final Number n) {
241             return nth(c, n.intValue());
242         }
243     }
244 
245     public static class T210 {
246         public void npe() {
247             throw new NullPointerException("NPE210");
248         }
249     }
250 
251     private static void handle(final ExecutorService pool, final JexlScript script, final Map<String, Object> payload) {
252        pool.submit(() -> script.execute(new MapContext(payload)));
253     }
254 
255     public Issues200Test() {
256         super("Issues200Test", null);
257     }
258 
259     @BeforeEach
260     @Override
261     public void setUp() {
262         // ensure jul logging is only error to avoid warning in silent mode
263         java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
264     }
265 
266     @Test
267     void test200() {
268         final JexlContext jc = new MapContext();
269         final Map<String, Object> funcs = new HashMap<>();
270         final Eval eval = new Eval();
271         funcs.put(null, eval);
272         final JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();
273         eval.setJexl(jexl);
274         final String src = "var f = fn(\'(x)->{x + 42}\'); f(y)";
275         final JexlScript s200 = jexl.createScript(src, "y");
276         assertEquals(142, s200.execute(jc, 100));
277         assertEquals(52, s200.execute(jc, 10));
278     }
279 
280     @Test
281     void test200b() {
282         final JexlContext jc = new MapContext();
283         final JexlEngine jexl = new JexlBuilder().create();
284         final JexlScript e = jexl.createScript("var x = 0; var f = (y)->{ x = y; }; f(42); x");
285         final Object r = e.execute(jc);
286         assertEquals(0, r);
287     }
288 
289     @Test
290     void test209a() {
291         final JexlContext jc = new MapContext();
292         final JexlEngine jexl = new JexlBuilder().create();
293         final JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x.a = ()->{return 1}; x['a']()");
294         final Object r = e.execute(jc);
295         assertEquals(1, r);
296     }
297 
298     @Test
299     void test209b() {
300         final JexlContext jc = new MapContext();
301         final JexlEngine jexl = new JexlBuilder().create();
302         final JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x['a'] = ()->{return 1}; x.a()");
303         final Object r = e.execute(jc);
304         assertEquals(1, r);
305     }
306 
307     @Test
308     void test210() {
309         final JexlContext jc = new MapContext();
310         jc.set("v210", new T210());
311         final JexlEngine jexl = new JexlBuilder().strict(false).silent(false).create();
312         final JexlScript e = jexl.createScript("v210.npe()");
313         final JexlException xjexl = assertThrows(JexlException.class, () -> e.execute(jc));
314         final Throwable th = xjexl.getCause();
315         assertEquals("NPE210", th.getMessage());
316     }
317 
318     @Test
319     void test217() {
320         final JexlEvalContext jc = new JexlEvalContext();
321         final JexlOptions options = jc.getEngineOptions();
322         jc.set("foo", new int[]{0, 1, 2, 42});
323         JexlEngine jexl;
324         JexlScript e;
325         Object r;
326         jexl = new JexlBuilder().strict(false).silent(false).create();
327         e = jexl.createScript("foo[3]");
328         r = e.execute(jc);
329         assertEquals(42, r);
330 
331         // cache and fail?
332         jc.set("foo", new int[]{0, 1});
333         options.setStrict(true);
334         assertTrue(options.isStrict());
335         final JexlException xjexl = assertThrows(JexlException.class, () -> e.execute(jc));
336         assertEquals(ArrayIndexOutOfBoundsException.class, xjexl.getCause().getClass());
337         //
338         options.setStrict(false);
339         r = e.execute(jc);
340         assertNull(r, "oob adverted");
341     }
342 
343     @Test
344     void test221() {
345         final JexlEvalContext jc = new JexlEvalContext();
346         final Map<String, Integer> map = new HashMap<>();
347         map.put("one", 1);
348         jc.set("map", map);
349         final JexlEngine jexl = new JexlBuilder().cache(256).create();
350         final JexlScript e = jexl.createScript("(x)->{ map[x] }");
351         Object r;
352         r = e.execute(jc, (Object) null);
353         assertNull(r);
354         r = e.execute(jc, (Object) null);
355         assertNull(r);
356         r = e.execute(jc, "one");
357         assertEquals(1, r);
358     }
359 
360     @Test
361     void test224() {
362         final List<String> a0 = Arrays.asList("one", "two");
363         final Set<String> a1 = new TreeSet<>(a0);
364         final JexlContext jc = new MapContext();
365         final JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic224(true)).create();
366         Object r;
367         JexlScript e = jexl.createScript("(map, x)->{ map[x] }");
368         r = e.execute(jc, a0, 1);
369         assertEquals("two", r);
370         r = e.execute(jc, a1, 1);
371         assertEquals("two", r);
372         e = jexl.createScript("(map)->{ map.1 }");
373         r = e.execute(jc, a0);
374         assertEquals("two", r);
375         r = e.execute(jc, a1);
376         assertEquals("two", r);
377         e = jexl.createScript("(map, x)->{ map(x) }");
378         r = e.execute(jc, a0, 1);
379         assertEquals("two", r);
380         r = e.execute(jc, a1, 1);
381         assertEquals("two", r);
382     }
383 
384     @Test
385     void test225() {
386         final Context225 df = new Context225();
387         final JexlEngine jexl = new JexlBuilder().create();
388 
389         final JexlExpression expression = jexl.createExpression("bar()");
390         assertEquals("bar", expression.evaluate(df));
391         final ObjectContext<Object> context = new ObjectContext<>(jexl, df);
392         assertEquals("bar", expression.evaluate(context));
393     }
394 
395     @Test
396     void test230() {
397         final JexlEngine jexl = new JexlBuilder().cache(4).create();
398         final JexlContext ctxt = new MapContext();
399         final int[] foo = {42};
400         ctxt.set("fo o", foo);
401         Object value;
402         for (int l = 0; l < 2; ++l) {
403             value = jexl.createExpression("fo\\ o[0]").evaluate(ctxt);
404             assertEquals(42, value);
405             value = jexl.createExpression("fo\\ o[0] = 43").evaluate(ctxt);
406             assertEquals(43, value);
407             value = jexl.createExpression("fo\\ o.0").evaluate(ctxt);
408             assertEquals(43, value);
409             value = jexl.createExpression("fo\\ o.0 = 42").evaluate(ctxt);
410             assertEquals(42, value);
411         }
412     }
413 
414     @Test
415     void test241() {
416         ExecutorService pool;
417         final JexlScript script = new JexlBuilder().create().createScript("`${item}`");
418 
419         pool = Executors.newFixedThreadPool(4);
420 
421         final Map<String, Object> m1 = new HashMap<>();
422         m1.put("item", "A");
423         final Map<String, Object> m2 = new HashMap<>();
424         m2.put("item", "B");
425 
426         handle(pool, script, m1);
427         script.execute(new MapContext(m2));
428         pool.shutdown();
429     }
430 
431     @Test
432     void test242() {
433         final Double a = -40.05d;
434         final Double b = -8.01d;
435         final Double c = a + b;
436         final JexlContext context = new MapContext();
437         context.set("a", a);
438         context.set("b", b);
439         final JexlEngine JEXL_ENGINE = new JexlBuilder().strict(true).silent(true).create();
440         final JexlExpression jsp = JEXL_ENGINE.createExpression("a + b");
441         final Double e = (Double) jsp.evaluate(context);
442         assertEquals(c, e, 0.0, () -> Double.doubleToLongBits(e) + " != " + Double.doubleToLongBits(c));
443         assertEquals(a + b, e, 0.0, () -> Double.doubleToLongBits(e) + " != " + Double.doubleToLongBits(c));
444     }
445 
446     @Test
447     void test243a() {
448         final JexlEngine jexl = new JexlBuilder().cache(32).create();
449         final JexlScript script = jexl.createScript("while(true);");
450         assertThrows(JexlException.class, () -> jexl.createExpression("while(true);"), "expr do not allow 'while' statement");
451     }
452 
453     @Test
454     void test245() {
455         final MapContext ctx = new MapContext();
456         final Foo245 foo245 = new Foo245();
457         ctx.set("foo", foo245);
458 
459         final JexlEngine engine = new JexlBuilder().strict(true).safe(false).silent(false).create();
460         final JexlExpression foobar = engine.createExpression("foo.bar");
461         final JexlExpression foobaz = engine.createExpression("foo.baz");
462         final JexlExpression foobarbaz = engine.createExpression("foo.bar.baz");
463         // add ambiguity with null & not-null
464         final Object[] args = { null, 245 };
465         for (final Object arg : args) {
466             foo245.setBar(arg);
467             // ok
468             assertEquals(foo245.getBar(), foobar.evaluate(ctx));
469             // fail level 1
470             assertThrows(JexlException.Property.class, () -> foobaz.evaluate(ctx), "foo.baz is not solvable, exception expected");
471             // fail level 2
472             assertThrows(JexlException.Property.class, () -> foobarbaz.evaluate(ctx), "foo.bar.baz is not solvable, exception expected");
473         }
474     }
475 
476     @Test
477     void test256() {
478         final MapContext ctx = new MapContext() {
479             @Override
480             public Object get(final String name) {
481                 if ("java".equals(name)) {
482                     return null;
483                 }
484                 return super.get(name);
485             }
486 
487             @Override
488             public boolean has(final String name) {
489                 if ("java".equals(name)) {
490                     return false;
491                 }
492                 return super.has(name);
493             }
494 
495             @Override
496             public void set(final String name, final Object value) {
497                 if ("java".equals(name)) {
498                     throw new JexlException(null, "cannot set " + name);
499                 }
500                 super.set(name, value);
501             }
502         };
503         ctx.set("java.version", 10);
504         final JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
505         final JexlScript script = engine.createScript("java = 3");
506         assertThrows(JexlException.class, () -> script.execute(ctx));
507         assertEquals(10, engine.createScript("java.version").execute(ctx));
508     }
509 
510     @Test
511     void test265() {
512         final JexlEngine jexl = new JexlBuilder().cache(4).create();
513         final JexlContext ctxt = new MapContext();
514         ctxt.set("x", 42);
515         Object result;
516         JexlScript script;
517         assertThrows(JexlException.Parsing.class, () -> jexl.createScript("(true) ? x : abs(1)"), "ambiguous, parsing should fail");
518         script = jexl.createScript("(true) ? (x) : abs(2)");
519         result = script.execute(ctxt);
520         assertEquals(42, result);
521         script = jexl.createScript("(true) ? x : (abs(3))");
522         result = script.execute(ctxt);
523         assertEquals(42, result);
524         script = jexl.createScript("(!true) ? abs(4) : x");
525         result = script.execute(ctxt);
526         assertEquals(42, result);
527     }
528 
529     @Test
530     void test266() {
531         Object result;
532         JexlScript script;
533         final JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic266(true)).create();
534         final JexlContext ctxt = new MapContext();
535 
536         final List<Integer> li = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5 ,6));
537         ctxt.set("list", li);
538         script = jexl.createScript("for (var item : list) { if (item <= 3) remove(); } return size(list)");
539         result = script.execute(ctxt);
540         assertEquals(3, result);
541         assertEquals(3, li.size());
542 
543         final Map<String, Integer> msi = new HashMap<>();
544         msi.put("a", 1);
545         msi.put("b", 2);
546         msi.put("c", 3);
547         msi.put("d", 4);
548         msi.put("e", 5);
549         msi.put("f", 6);
550         ctxt.set("map", msi);
551         script = jexl.createScript("for (var item : map) { if (item <= 2) remove(); } return size(map)");
552         result = script.execute(ctxt);
553         assertEquals(4, result);
554         assertEquals(4, msi.size());
555     }
556 
557     @Test
558     void test267() {
559         Object result;
560         JexlScript script;
561         final JexlEngine jexl = new JexlBuilder().create();
562         final JexlContext ctxt = new MapContext();
563         // API declared params
564         script = jexl.createScript("x + y", "x", "y");
565         result = script.execute(ctxt, 20, 22);
566         assertEquals(42, result);
567         // script declared params
568         script = jexl.createScript("(x, y)->{ x + y}");
569         result = script.execute(ctxt, 22, 20);
570         assertEquals(42, result);
571         // explicitly returning the lambda
572         script = jexl.createScript("return (x, y)->{ x + y}");
573         result = script.execute(ctxt);
574         assertInstanceOf(JexlScript.class, result);
575     }
576 
577     @Test
578     void test274() {
579         JexlEngine jexl = new JexlBuilder().strict(true).safe(true).stackOverflow(5).create();
580         final JexlContext ctxt = new MapContext();
581         final JexlScript script = jexl.createScript("var f = (x)->{ x > 1? x * f(x - 1) : x }; f(a)", "a");
582         final Object result = script.execute(ctxt, 3);
583         assertEquals(6, result);
584         JexlException.StackOverflow xstack = assertThrows(JexlException.StackOverflow.class, () -> script.execute(ctxt, 32));
585         assertTrue(xstack.toString().contains("jexl"));
586         jexl = new JexlBuilder().strict(true).create();
587         final JexlScript script2 = jexl.createScript("var f = (x)->{ x * f(x - 1) }; f(a)", "a");
588         xstack = assertThrows(JexlException.StackOverflow.class, () -> script2.execute(ctxt, 32));
589         assertTrue(xstack.toString().contains("jvm"));
590     }
591 
592     @Test
593     void test275a() {
594         final JexlContext ctxt = new MapContext();
595         ctxt.set("out", System.out);
596         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
597         final JexlScript e = jexl.createScript("out.println(xyz)");
598         final JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> e.execute(ctxt));
599         assertEquals("xyz", xvar.getVariable());
600     }
601 
602     @Test
603     void test275b() {
604         final JexlContext ctxt = new MapContext();
605         // ctxt.set("out", System.out);
606         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
607         final JexlScript e = jexl.createScript("var xyz = xyz");
608         final Object o = e.execute(ctxt);
609         assertNull(o);
610     }
611 
612     @Test
613     void test275c() {
614         final JexlContext ctxt = new MapContext();
615         // ctxt.set("out", System.out);
616         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).silent(true).create();
617         JexlScript e;
618         Object r;
619         e = jexl.createScript("(s, v)->{  var x = y ; 42; }");
620         // wont make an error
621         r = e.execute(ctxt, false, true);
622         assertEquals(42, r);
623     }
624 
625     @Test
626     void test275d() {
627         final JexlContext ctxt = new MapContext();
628         ctxt.set("out", System.out);
629         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
630         final JexlScript e = jexl.createScript("{ var xyz = 42 } out.println(xyz)");
631         final Object o = e.execute(ctxt);
632         assertNull(o);
633     }
634 
635     @Test
636     void test278() {
637         // @formatter:off
638         final String[] srcs = {
639             "return union x143('arg',5,6) ",
640             "return union y143('arg',5,6)   ;",
641             "return union\n z143('arg',5,6)   ;",
642             "var f =()->{ return union 143 } foo[0]"
643         };
644         final Object[] ctls = {
645             "42","42","42", 42
646         };
647         // @formatter:on
648         final JexlEngine jexl = new JexlBuilder().cache(4).create();
649         final JexlContext ctxt = new MapContext();
650         final int[] foo = { 42 };
651         ctxt.set("foo", foo);
652         ctxt.set("union", "42");
653         Object value;
654         JexlScript jc;
655         for (int i = 0; i < srcs.length; ++i) {
656             final String src = srcs[i];
657             final JexlException.Ambiguous xa = assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src), src);
658             final String str = xa.toString();
659             assertTrue(str.contains("143"));
660             final String clean = xa.tryCleanSource(src);
661 
662             jc = jexl.createScript(clean);
663             value = jc.execute(ctxt);
664             assertEquals(ctls[i], value, clean);
665         }
666     }
667 
668     @Test
669     void test279() {
670         final Log logger = null; // LogFactory.getLog(Issues200Test.class);
671         Object result;
672         JexlScript script;
673         final JexlContext ctxt = new Context279();
674         // @formatter:off
675         final String[] srcs = {
676             "var z = null; identity(z[0]);",
677              "var z = null; z.0;",
678              "var z = null; z.foo();",
679             "z['y']['z']",
680             "z.y.any()",
681              "identity(z.any())",
682              "z[0]",
683              "z.0",
684              "z.foo()",
685              "z.y[0]",
686              "z.y[0].foo()",
687              "z.y.0",
688              "z.y.foo()",
689              "var z = { 'y' : [42] }; z.y[1]",
690              "var z = { 'y' : [42] }; z.y.1",
691              "var z = { 'y' : [-42] }; z.y[1].foo()",
692              "var z = { 'y' : [42] }; z.y.1.foo()",
693              "var z = { 'y' : [null, null] }; z.y[1].foo()",
694              "var z = { 'y' : [null, null] }; z.y.1.foo()"
695         };
696         // @formatter:on
697         for (int i = 0; i < 2; ++i) {
698             for (final boolean strict : new boolean[] { true, false }) {
699                 final JexlEngine jexl = new JexlBuilder().safe(false).strict(strict).create();
700                 for (final String src : srcs) {
701                     script = jexl.createScript(src);
702                     try {
703                         result = script.execute(ctxt);
704                         if (strict) {
705                             if (logger != null) {
706                                 logger.warn(ctxt.has("z") + ": " + src + ": no fail, " + result);
707                             }
708                             fail("should have failed: " + src);
709                         }
710                         // not reachable
711                         assertNull(result, "non-null result ?!");
712                     } catch (final JexlException.Variable xvar) {
713                         if (logger != null) {
714                             logger.warn(ctxt.has("z") + ": " + src + ": fail, " + xvar);
715                         }
716                         if (!strict) {
717                             fail(src + ", should not have thrown " + xvar);
718                         } else {
719                             assertTrue(xvar.toString().contains("z"), () -> src + ": " + xvar.toString());
720                         }
721                     } catch (final JexlException.Property xprop) {
722                         if (logger != null) {
723                             logger.warn(ctxt.has("z") + ": " + src + ": fail, " + xprop);
724                         }
725                         if (!strict) {
726                             fail(src + ", should not have thrown " + xprop);
727                         } else {
728                             assertTrue(xprop.toString().contains("1"), () -> src + ": " + xprop.toString());
729                         }
730                     }
731                 }
732             }
733             ctxt.set("z.y", null);
734         }
735     }
736 
737     @Test
738     void test279b() {
739         Object result;
740         JexlScript script;
741         final JexlContext ctxt = new Context279();
742         ctxt.set("ctxt", ctxt);
743         final String src = "(x)->{ spread(x)[0].toString() }";
744         final JexlEngine jexl = new JexlBuilder().safe(true).strict(true).create();
745         script = jexl.createScript(src);
746         result = script.execute(ctxt, "abc");
747         assertEquals("a", result);
748         result = script.execute(ctxt, (Object) null);
749         assertNull(result);
750     }
751 
752     @Test
753     void test285() {
754         final List<String> out = new ArrayList<>(6);
755         final JexlContext ctxt = new MapContext();
756         ctxt.set("$out", out);
757         // @formatter:off
758         final String src = "for(var b: ['g','h','i']) {\n"
759                 + "  var c = b;\n"
760                 + "  $out.add(c);\n"
761                 + "}\n"
762                 + " \n"
763                 + "for(var dc: ['j','k','l']) {\n"
764                 + "  $out.add(dc);\n"
765                 + "}"
766                 + " \n"
767                 + "$out.size()";
768         // @formatter:on
769         final JexlFeatures features = new JexlFeatures();
770         features.lexical(true);
771         final JexlEngine jexl = new JexlBuilder()
772                 //.features(features)
773                 .safe(false).strict(true).lexical(true).create();
774         final JexlScript script = jexl.createScript(src);
775         final Object result = script.execute(ctxt, (Object) null);
776         assertEquals(6, result);
777         final List<String> ctl = Arrays.asList("g", "h", "i", "j", "k", "l");
778         assertEquals(ctl, out);
779     }
780 
781     @Test
782     void test285a() {
783         final List<String> out = new ArrayList<>(6);
784         final JexlContext ctxt = new MapContext();
785         ctxt.set("$out", out);
786         final String src =
787                   "for(var b: ['g','h','i']) { $out.add(b); }\n"
788                 + "for(b: ['j','k','l']) { $out.add(b);}\n"
789                 + "$out.size()";
790 
791         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).lexical(false).create();
792         final JexlScript script = jexl.createScript(src);
793         final Object result = script.execute(ctxt, (Object) null);
794         assertEquals(6, result);
795         final List<String> ctl = Arrays.asList("g", "h", "i", "j", "k", "l");
796         assertEquals(ctl, out);
797     }
798 
799     @Test
800     void test285b() {
801         final List<String> out = new ArrayList<>(6);
802         final JexlContext ctxt = new MapContext();
803         ctxt.set("$out", out);
804         final String src =
805                   "for(b: ['g','h','i']) { $out.add(b); }\n"
806                 + "for(var b: ['j','k','l']) { $out.add(b);}\n"
807                 + "$out.size()";
808 
809         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
810         final JexlScript script = jexl.createScript(src);
811         final Object result = script.execute(ctxt, (Object) null);
812         assertEquals(6, result);
813         final List<String> ctl = Arrays.asList("g", "h", "i", "j", "k", "l");
814         assertEquals(ctl, out);
815     }
816 
817     @Test
818     void test286() {
819         final String s286 = "var x = 0; for(x : 1..2){}; return x";
820         final JexlEngine jexl = new JexlBuilder().strict(true).create();
821         assertEquals(2, jexl.createScript(s286).execute(null));
822     }
823 
824     @Test
825     void test287() {
826         final JexlEvalContext ctxt = new JexlEvalContext();
827         final JexlOptions options = ctxt.getEngineOptions();
828         final JexlEngine jexl = new JexlBuilder().strict(true).create();
829         String src;
830         JexlScript script;
831         Object result;
832         // declared, not defined
833         src = "x = 1; if (false) var x = 2; x";
834         script = jexl.createScript(src);
835         result = script.execute(ctxt);
836         assertEquals(1, result);
837         // declared and defined
838         src = "x = 1; if (true) var x = 2; x";
839         script = jexl.createScript(src);
840         result = script.execute(ctxt);
841         assertEquals(2, result);
842         // definition using shadowed global
843         src = "x = 1; var x = x + 41; x";
844         script = jexl.createScript(src);
845         result = script.execute(ctxt);
846         assertEquals(42, result);
847         // definition using shadowed global
848         options.setLexical(false);
849         src = "(x)->{ if (x==1) { var y = 2; } else if (x==2) { var y = 3; }; y }";
850         final JexlScript script1 = jexl.createScript(src);
851         result = script1.execute(ctxt, 1);
852         assertEquals(2, result);
853         result = script1.execute(ctxt, 2);
854         assertEquals(3, result);
855         options.setStrict(true);
856         final JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> script1.execute(ctxt, 0));
857         assertTrue(xvar.getMessage().contains("y"));
858         options.setStrict(false);
859         result = script1.execute(ctxt, 0);
860         assertNull(result);
861     }
862 
863     @Test
864     void test289() {
865         final JexlContext ctxt = new MapContext();
866         final JexlEngine jexl = new JexlBuilder().strict(true).create();
867         String src;
868         JexlScript script;
869         Object result;
870         src = "var x = function(a) { var b; return b}; x(1,2)";
871         script = jexl.createScript(src);
872         result = script.execute(ctxt);
873         assertNull(result);
874     }
875 
876     @Test
877     void test290a() {
878         Object result;
879         JexlScript script;
880         final String[] srcs = {
881             "(x)->{ x.nothing().toString() }",
882             "(x)->{ x.toString().nothing() }",
883             "(x)->{ x.nothing().nothing() }",
884         };
885         for (final boolean safe : new boolean[]{true, false}) {
886             final JexlEngine jexl = new JexlBuilder().safe(safe).strict(true).create();
887             for (final String src : srcs) {
888                 script = jexl.createScript(src);
889                 try {
890                     result = script.execute(null, "abc");
891                     if (!safe) {
892                         fail("should have failed: " + src);
893                     } else {
894                         assertNull(result, "non-null result ?!");
895                     }
896                 } catch (final JexlException.Method xmethod) {
897                     if (safe) {
898                         fail(src + ", should not have thrown " + xmethod);
899                     } else {
900                         assertTrue(xmethod.toString().contains("nothing"), () -> src + ": " + xmethod.toString());
901                     }
902                 }
903             }
904         }
905     }
906 
907     @Test
908     void test290b() {
909         Object result;
910         JexlScript script;
911         final String[] srcs = {
912             "(x)->{ x?.nothing()?.toString() }",
913             "(x)->{ x.toString()?.nothing() }",
914             "(x)->{ x?.nothing().nothing() }",};
915         final JexlEngine jexl = new JexlBuilder().strict(true).create();
916         for (final String src : srcs) {
917             script = jexl.createScript(src);
918             result = script.execute(null, "abc");
919             assertNull(result);
920         }
921     }
922 
923     @Test
924     void test291() {
925         final String str = "{1:'one'}[1]";
926         final JexlContext ctxt = new MapContext();
927         final JexlEngine jexl = new JexlBuilder().create();
928         JexlExpression e = jexl.createExpression(str);
929         Object value = e.evaluate(ctxt);
930         assertEquals("one", value);
931 
932         final JexlEngine sandboxedJexlEngine = new JexlBuilder().
933                 sandbox(new JexlSandbox(true)). // add a whitebox sandbox
934                 create();
935          e = sandboxedJexlEngine.createExpression(str);
936         value = e.evaluate(ctxt);
937         assertEquals("one", value);
938     }
939 
940     @Test
941     void test298() {
942         final Cls298 c298 = new Cls298();
943         final JexlContext ctxt = new MapContext();
944         final JexlEngine jexl = new JexlBuilder().create();
945 
946         String str = "c.size()";
947         JexlScript e = jexl.createScript(str, "c");
948         Object value = e.execute(ctxt, c298);
949         assertEquals(42, value, str);
950 
951         str = "size c";
952         e = jexl.createScript(str, "c");
953         value = e.execute(ctxt, c298);
954         assertEquals(42, value, str);
955 
956         str = "c.size(127)";
957         e = jexl.createScript(str, "c");
958         value = e.execute(ctxt, c298);
959         assertEquals(169, value, str);
960     }
961 
962     @Test
963     void testTemplate6565a() {
964         final JexlEngine jexl = new JexlBuilder().create();
965         final JxltEngine jexlt = jexl.createJxltEngine();
966         final String source =
967             "$$ var res = '';\n" +
968             "$$ var meta = session.data['METADATA'];\n" +
969             "$$ if (meta) {\n" +
970             "$$   var entry = meta['ID'];\n" +
971             "$$   if (entry) {\n" +
972             "$$     var value = session.data[entry];\n" +
973             "$$     res = value?: '';\n" +
974             "$$   }\n" +
975             "$$ }\n" +
976             "${res}\n";
977         final JxltEngine.Template script = jexlt.createTemplate("$$", new StringReader(source));
978         assertNotNull(script);
979         final TemplateDebugger dbg = new TemplateDebugger();
980         final String refactored = dbg.debug(script) ? dbg.toString() : "";
981         assertNotNull(refactored);
982         assertEquals(source, refactored);
983     }
984 
985     @Test
986     void testTemplate6565b() {
987         final JexlEngine jexl = new JexlBuilder().create();
988         final JxltEngine jexlt = jexl.createJxltEngine();
989         final String source =
990             "$$ var res = '';\n" +
991             "$$ var meta = session.data['METADATA'];\n" +
992             "$$ if (meta) {\n" +
993             "$$   var entry = meta['ID'];\n" +
994             "$$   if (entry) {\n" +
995             "$$     var value = session.data[entry];\n" +
996             "$$     res = value?: '';\n" +
997             "${res}\n" +
998             "$$   }\n" +
999             "$$ }\n";
1000         final JxltEngine.Template script = jexlt.createTemplate("$$", new StringReader(source));
1001         assertNotNull(script);
1002         final TemplateDebugger dbg = new TemplateDebugger();
1003         final String refactored = dbg.debug(script) ? dbg.toString() : "";
1004         assertNotNull(refactored);
1005         assertEquals(source, refactored);
1006     }
1007 }