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    *      http://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.apache.commons.jexl3.internal.Util.debuggerCheck;
20  import static org.apache.commons.jexl3.introspection.JexlPermissions.RESTRICTED;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  
30  import java.io.StringReader;
31  import java.io.StringWriter;
32  import java.lang.reflect.Proxy;
33  import java.math.MathContext;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.LinkedHashMap;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.concurrent.atomic.AtomicInteger;
44  import java.util.stream.Collectors;
45  import java.util.stream.Stream;
46  
47  import org.apache.commons.jexl3.internal.Engine32;
48  import org.apache.commons.jexl3.internal.OptionsContext;
49  import org.apache.commons.jexl3.introspection.JexlSandbox;
50  import org.junit.jupiter.api.Test;
51  
52  /**
53   * Test cases for reported issue between JEXL-300 and JEXL-399.
54   */
55  public class Issues300Test {
56      public static class Arithmetic383 extends JexlArithmetic {
57          public Arithmetic383(final boolean astrict) {
58              super(astrict);
59          }
60  
61          @Override
62          public boolean isStrict(final JexlOperator op) {
63              switch (op) {
64              case NOT:
65              case CONDITION:
66                  return false;
67              }
68              return super.isStrict(op);
69          }
70      }
71  
72      public static class Arithmetic384 extends JexlArithmetic {
73          public Arithmetic384(final boolean astrict) {
74              super(astrict);
75          }
76  
77          @Override
78          public boolean isStrict(final JexlOperator op) {
79              if (JexlOperator.ADD == op) {
80                  return false;
81              }
82              return super.isStrict(op);
83          }
84      }
85  
86      public static class Arithmetic384c extends JexlArithmetic {
87          AtomicInteger cmp = new AtomicInteger();
88  
89          public Arithmetic384c(final boolean astrict) {
90              super(astrict);
91          }
92  
93          public Arithmetic384c(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
94              super(astrict, bigdContext, bigdScale);
95          }
96  
97          @Override
98          protected int compare(final Object l, final Object r, final String op) {
99              cmp.incrementAndGet();
100             return super.compare(l, r, op);
101         }
102 
103         int getCmpCalls() {
104             return cmp.get();
105         }
106     }
107 
108     public static class Arithmetic384d extends Arithmetic384c {
109         public Arithmetic384d(final boolean astrict) {
110             super(astrict);
111         }
112 
113         public Arithmetic384d(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
114             super(astrict, bigdContext, bigdScale);
115         }
116     }
117 
118     private static class Class397 implements Interface397i {
119         @Override
120         public String summary() {
121             return getClass().getName();
122         }
123     }
124 
125     public static class Context0930 extends MapContext {
126         /**
127          * This allows using a JEXL lambda as a filter.
128          *
129          * @param stream the stream
130          * @param filter the lambda to use as filter
131          * @return the filtered stream
132          */
133         public Stream<?> filter(final Stream<?> stream, final JexlScript filter) {
134             return stream.filter(x -> Boolean.TRUE.equals(filter.execute(this, x)));
135         }
136     }
137 
138     /**
139      * Mock driver.
140      */
141     public static class Driver0930 {
142         private final String name;
143 
144         Driver0930(final String n) {
145             name = n;
146         }
147 
148         public String getAttributeName() {
149             return name;
150         }
151     }
152 
153     public interface Interface397i {
154         String summary();
155     }
156 
157     public static class Session322 {
158         public User322 getUser() {
159             return new User322();
160         }
161     }
162 
163     public static class TestObject374 {
164         private String name;
165         private TestObject374 nested;
166 
167         public String getName() {
168             return name;
169         }
170 
171         public TestObject374 getNested() {
172             return nested;
173         }
174 
175         public void setName(final String pName) {
176             this.name = pName;
177         }
178 
179         public void setNested(final TestObject374 pNested) {
180             nested = pNested;
181         }
182     }
183 
184     public enum Type375 {
185         DELIVERY_ADDRESS, DOMICILE
186     }
187 
188     public static class User322 {
189         public String getName() {
190             return "user322";
191         }
192     }
193 
194     public static class VaContext extends MapContext {
195         VaContext(final Map<String, Object> vars) {
196             super(vars);
197         }
198 
199         public int cell(final List<?> l, final String... ms) {
200             return 42 + cell(ms);
201         }
202 
203         public int cell(final String... ms) {
204             return ms.length;
205         }
206     }
207 
208     public static class Var370 {
209         private String name;
210 
211         public String getName() {
212             return name;
213         }
214 
215         public void setName(final String s) {
216             name = s;
217         }
218     }
219 
220     static JexlContext pragmaticContext() {
221         final JexlOptions opts = new JexlOptions();
222         opts.setFlags("-strict", "-cancellable", "-lexical", "-lexicalShade", "+safe", "+sharedInstance");
223         return new JexlTestCase.PragmaticContext(opts);
224     }
225 
226     <T> T createProxy(final JexlEngine jexl, final Object o, final Class[] clazzz) {
227         // a JEX-based delegating proxy
228         return (T) Proxy.newProxyInstance(getClass().getClassLoader(), clazzz, (proxy, method, args) -> jexl.invokeMethod(o, method.getName(), args));
229     }
230 
231     private Object run361a(final JexlEngine jexl) {
232         final String src = "()-> { ()-> { if (versionFile != null) { return 'foo'; } else { return 'bar'; }} }";
233         final JexlScript script = jexl.createScript(src);
234         final Object result = script.execute(null);
235         final JexlScript rs = (JexlScript) result;
236         return rs.execute(null);
237     }
238 
239     private Object run361b(final JexlEngine jexl) {
240         // @formatter:off
241         final String src = "()-> { ()-> {" +
242                 "var voa = vaf.value;\n" +
243                 "if (voa != NaN && voa <= 0)" +
244                 "{ return 'foo'; } else { return 'bar'; }" +
245                 "} }";
246         // @formatter:on
247         final JexlContext context = new MapContext();
248         final Map<String, Object> vaf = Collections.singletonMap("value", null);
249         context.set("vaf", vaf);
250         final JexlScript script = jexl.createScript(src);
251         final Object result = script.execute(null);
252         final JexlScript rs = (JexlScript) result;
253         return rs.execute(context);
254     }
255 
256     private String run361c(final JexlEngine jexl) {
257         // @formatter:off
258         final String src = "$$var t = null;\n" +
259                 "$$if (t < 0) {\n" +
260                 "'foo'\n" +
261                 "$$} else {\n" +
262                 "'bar'\n" +
263                 "$$}";
264         // @formatter:on
265         final JxltEngine jxlt = jexl.createJxltEngine();
266         final JexlContext context = new MapContext();
267         final Map<String, Object> vaf = Collections.singletonMap("value", null);
268         context.set("vaf", vaf);
269         final JxltEngine.Template template = jxlt.createTemplate(src);
270         final StringWriter strw = new StringWriter();
271         template.evaluate(context, strw);
272         return strw.toString();
273     }
274 
275     private Object run361d(final JexlEngine jexl) {
276         final String src = "var foo = 42; var foo = 43;";
277         final JexlScript script = jexl.createScript(src);
278         return script.execute(null);
279     }
280 
281     @Test
282     public void test301a() {
283         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new JexlArithmetic(false)).create();
284         final String[] srcs = { "var x = null; x.0", "var x = null; x[0]", "var x = [null,1]; x[0][0]" };
285         for (int i = 0; i < srcs.length; ++i) {
286             final String src = srcs[i];
287             final JexlScript s = jexl.createScript(src);
288             try {
289                 final Object o = s.execute(null);
290                 if (i > 0) {
291                     fail(src + ": Should have failed");
292                 }
293             } catch (final Exception ex) {
294                 assertTrue(ex.getMessage().contains("x"));
295             }
296         }
297     }
298 
299     @Test
300     public void test302() {
301         final JexlContext jc = new MapContext();
302         // @formatter:off
303         final String[] strs = {
304                 "{if (0) 1 else 2; var x = 4;}",
305                 "if (0) 1; else 2; ",
306                 "{ if (0) 1; else 2; }",
307                 "{ if (0) { if (false) 1 else -3 } else 2; }"
308         };
309         // @formatter:on
310         final JexlEngine jexl = new JexlBuilder().create();
311         for (final String str : strs) {
312             final JexlScript e = jexl.createScript(str);
313             final Object o = e.execute(jc);
314             final int oo = ((Number) o).intValue() % 2;
315             assertEquals(0, oo, () -> "Block result is wrong " + str);
316         }
317     }
318 
319     @Test
320     public void test304() {
321         final JexlEngine jexlEngine = new JexlBuilder().strict(false).create();
322         JexlExpression e304 = jexlEngine.createExpression("overview.limit.var");
323 
324         final Map<String, Object> map3 = new HashMap<>();
325         map3.put("var", "4711");
326         final Map<String, Object> map2 = new HashMap<>();
327         map2.put("limit", map3);
328         final Map<String, Object> map = new HashMap<>();
329         map.put("overview", map2);
330 
331         final JexlContext context = new MapContext(map);
332         Object value = e304.evaluate(context);
333         assertEquals("4711", value); // fails
334 
335         map.clear();
336         map.put("overview.limit.var", 42);
337         value = e304.evaluate(context);
338         assertEquals(42, value);
339 
340         // @formatter:off
341         final String[]  keywords = {
342                 "if", "else", "do", "while", "for", "break", "continue", "function", "return", "new", "size", "empty",
343                 "var", "let", "const",
344                 "null", "true", "false",
345                 "not", "div", "mod", "and", "or",
346                 "eq", "ne", "lt", "gt", "ge", "le",
347         };
348         // @formatter:on
349         for (final String keyword : keywords) {
350             final String pkw = "e304." + keyword;
351             map.put(pkw, 42);
352             e304 = jexlEngine.createExpression(pkw);
353             value = e304.evaluate(context);
354             assertEquals(42, value);
355         }
356         for (int i = 0; i < keywords.length; ++i) {
357             final String pkw = "e304." + keywords[i] + "." + keywords[keywords.length - 1 - i];
358             map.put(pkw, 42);
359             e304 = jexlEngine.createExpression(pkw);
360             value = e304.evaluate(context);
361             assertEquals(42, value);
362         }
363         final String allkw = "e304." + String.join(".", keywords);
364         map.put(allkw, 42);
365         e304 = jexlEngine.createExpression(allkw);
366         value = e304.evaluate(context);
367         assertEquals(42, value);
368     }
369 
370     @Test
371     public void test305() {
372         final JexlEngine jexl = new JexlBuilder().create();
373         JexlScript e;
374         e = jexl.createScript("{while(false) {}; var x = 1;}");
375         final String str0 = e.getParsedText();
376         e = jexl.createScript(str0);
377         assertNotNull(e);
378         final String str1 = e.getParsedText();
379         assertEquals(str0, str1);
380     }
381 
382     @Test
383     public void test306() {
384         final JexlContext ctxt = new MapContext();
385         final JexlEngine jexl = new JexlBuilder().create();
386         final JexlScript e = jexl.createScript("x.y ?: 2");
387         final Object o1 = e.execute(null);
388         assertEquals(2, o1);
389         ctxt.set("x.y", null);
390         final Object o2 = e.execute(ctxt);
391         assertEquals(2, o2);
392     }
393 
394     @Test
395     public void test306a() {
396         final JexlEngine jexl = new JexlBuilder().create();
397         final JexlScript e = jexl.createScript("x.y ?: 2", "x");
398         Object o = e.execute(null, new Object());
399         assertEquals(2, o);
400         o = e.execute(null);
401         assertEquals(2, o);
402     }
403 
404     @Test
405     public void test306b() {
406         final JexlEngine jexl = new JexlBuilder().create();
407         final JexlScript e = jexl.createScript("x?.y ?: 2", "x");
408         final Object o1 = e.execute(null, new Object());
409         assertEquals(2, o1);
410         final Object o2 = e.execute(null);
411         assertEquals(2, o2);
412     }
413 
414     @Test
415     public void test306c() {
416         final JexlEngine jexl = new JexlBuilder().safe(true).create();
417         final JexlScript e = jexl.createScript("x.y ?: 2", "x");
418         Object o = e.execute(null, new Object());
419         assertEquals(2, o);
420         o = e.execute(null);
421         assertEquals(2, o);
422     }
423 
424     @Test
425     public void test306d() {
426         final JexlEngine jexl = new JexlBuilder().safe(true).create();
427         final JexlScript e = jexl.createScript("x.y[z.t] ?: 2", "x");
428         Object o = e.execute(null, new Object());
429         assertEquals(2, o);
430         o = e.execute(null);
431         assertEquals(2, o);
432     }
433 
434     @Test
435     public void test309a() {
436         // @formatter:off
437         final String src = "<html lang=\"en\">\n"
438                 + "  <body>\n"
439                 + "    <h1>Hello World!</h1>\n"
440                 + "$$ var i = 12++;\n"
441                 + "  </body>\n"
442                 + "</html>";
443         // @formatter:on
444         final JexlEngine jexl = new JexlBuilder().safe(true).create();
445         final JxltEngine jxlt = jexl.createJxltEngine();
446         final JexlInfo info = new JexlInfo("template", 1, 1);
447         final JexlException.Parsing xerror = assertThrows(JexlException.Parsing.class, () -> jxlt.createTemplate(info, src));
448         assertEquals(4, xerror.getInfo().getLine());
449     }
450 
451     @Test
452     public void test309b() {
453         // @formatter:off
454         final String src = "<html lang=\"en\">\n"
455                 + "  <body>\n"
456                 + "    <h1>Hello World!</h1>\n"
457                 + "$$ var i = a b c;\n"
458                 + "  </body>\n"
459                 + "</html>";
460         // @formatter:on
461         final JexlEngine jexl = new JexlBuilder().safe(true).create();
462         final JxltEngine jxlt = jexl.createJxltEngine();
463         final JexlInfo info = new JexlInfo("template", 1, 1);
464         final JexlException.Parsing xerror = assertThrows(JexlException.Parsing.class, () -> jxlt.createTemplate(info, src));
465         assertEquals(4, xerror.getInfo().getLine());
466     }
467 
468     @Test
469     public void test309c() {
470         // @formatter:off
471         final String src = "<html lang=\"en\">\n"
472                 + "  <body>\n"
473                 + "    <h1>Hello World!</h1>\n"
474                 + "$$ var i =12;\n"
475                 + "  </body>\n"
476                 + "</html>";
477         // @formatter:on
478         final JexlEngine jexl = new JexlBuilder().safe(true).create();
479         final JxltEngine jxlt = jexl.createJxltEngine();
480         final JexlInfo info = new JexlInfo("template", 1, 1);
481         final JxltEngine.Template tmplt = jxlt.createTemplate(info, src);
482         final String src1 = tmplt.asString();
483         final String src2 = tmplt.toString();
484         assertEquals(src1, src2);
485     }
486 
487     @Test
488     public void test314() {
489         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
490         final Map<String, Object> vars = new HashMap<>();
491         final JexlContext ctxt = new VaContext(vars);
492         JexlScript script;
493         Object result;
494         script = jexl.createScript("cell()");
495         result = script.execute(ctxt);
496         assertEquals(0, result);
497         script = jexl.createScript("x.cell()", "x");
498         result = script.execute(ctxt, Arrays.asList(10, 20));
499         assertEquals(42, result);
500         script = jexl.createScript("cell('1', '2')");
501         result = script.execute(ctxt);
502         assertEquals(2, result);
503         script = jexl.createScript("x.cell('1', '2')", "x");
504         result = script.execute(ctxt, Arrays.asList(10, 20));
505         assertEquals(44, result);
506 
507         vars.put("TVALOGAR", null);
508         String jexlExp = "TVALOGAR==null ?'SIMON':'SIMONAZO'";
509         script = jexl.createScript(jexlExp);
510         result = script.execute(ctxt);
511         assertEquals("SIMON", result);
512 
513         jexlExp = "TVALOGAR.PEPITO==null ?'SIMON':'SIMONAZO'";
514         script = jexl.createScript(jexlExp);
515 
516         final Map<String, Object> tva = new LinkedHashMap<>();
517         tva.put("PEPITO", null);
518         vars.put("TVALOGAR", tva);
519         result = script.execute(ctxt);
520         assertEquals("SIMON", result);
521 
522         vars.remove("TVALOGAR");
523         ctxt.set("TVALOGAR.PEPITO", null);
524         result = script.execute(ctxt);
525         assertEquals("SIMON", result);
526     }
527 
528     @Test
529     public void test315() {
530         final JexlEngine jexl = new JexlBuilder().strict(true).create();
531         final Map<String, Object> vars = new HashMap<>();
532         final JexlContext ctxt = new VaContext(vars);
533         JexlScript script;
534         Object result;
535         script = jexl.createScript("a?? 42 + 10", "a");
536         result = script.execute(ctxt, 32);
537         assertEquals(32, result);
538         result = script.execute(ctxt, (Object) null);
539         assertEquals(52, result);
540         script = jexl.createScript("- a??42 + +10", "a");
541         result = script.execute(ctxt, 32);
542         assertEquals(-32, result);
543         result = script.execute(ctxt, (Object) null);
544         assertEquals(52, result);
545         // long version of ternary
546         script = jexl.createScript("a? a : +42 + 10", "a");
547         result = script.execute(ctxt, 32);
548         assertEquals(32, result);
549         result = script.execute(ctxt, (Object) null);
550         assertEquals(52, result);
551         // short one, elvis, equivalent
552         script = jexl.createScript("a ?: +42 + 10", "a");
553         result = script.execute(ctxt, 32);
554         assertEquals(32, result);
555         result = script.execute(ctxt, (Object) null);
556         assertEquals(52, result);
557     }
558 
559     @Test
560     public void test317() {
561         final JexlEngine jexl = new JexlBuilder().strict(true).create();
562         final JexlContext ctxt = new MapContext();
563         JexlScript script;
564         Object result;
565         JexlInfo info = new JexlInfo("test317", 1, 1);
566         // @formatter:off
567         script = jexl.createScript(info, "var f = "
568                         + "()-> {x + x }; f",
569                 "x");
570         // @formatter:on
571         result = script.execute(ctxt, 21);
572         assertTrue(result instanceof JexlScript);
573         script = (JexlScript) result;
574         info = JexlInfo.from(script);
575         assertNotNull(info);
576         assertEquals("test317", info.getName());
577         result = script.execute(ctxt, 21);
578         assertEquals(42, result);
579     }
580 
581     @Test
582     public void test322a() {
583         final JexlEngine jexl = new JexlBuilder().strict(true).create();
584         final JxltEngine jxlt = jexl.createJxltEngine();
585         final JexlContext context = new MapContext();
586 
587         final String[] ins = { "${'{'}", "${\"{\"}", "${\"{}\"}", "${'{42}'}", "${\"{\\\"\\\"}\"}" };
588         final String[] ctls = { "{", "{", "{}", "{42}", "{\"\"}" };
589         StringWriter strw;
590         JxltEngine.Template template;
591         String output;
592 
593         for (int i = 0; i < ins.length; ++i) {
594             final String src = ins[i];
595             template = jxlt.createTemplate("$$", new StringReader(src));
596             strw = new StringWriter();
597             template.evaluate(context, strw);
598             output = strw.toString();
599             assertEquals(ctls[i], output);
600         }
601     }
602 
603     @Test
604     public void test322b() {
605         final JexlContext ctxt = new MapContext();
606         final String src = "L'utilisateur ${session.user.name} s'est connecte";
607         final JexlEngine jexl = new JexlBuilder().strict(true).create();
608         final JxltEngine jxlt = jexl.createJxltEngine();
609         StringWriter strw;
610         JxltEngine.Template template;
611         String output;
612         template = jxlt.createTemplate("$$", new StringReader(src));
613 
614         ctxt.set("session", new Session322());
615         strw = new StringWriter();
616         template.evaluate(ctxt, strw);
617         output = strw.toString();
618         assertEquals("L'utilisateur user322 s'est connecte", output);
619 
620         ctxt.set("session.user", new User322());
621         strw = new StringWriter();
622         template.evaluate(ctxt, strw);
623         output = strw.toString();
624         assertEquals("L'utilisateur user322 s'est connecte", output);
625 
626         ctxt.set("session.user.name", "user322");
627         strw = new StringWriter();
628         template.evaluate(ctxt, strw);
629         output = strw.toString();
630         assertEquals("L'utilisateur user322 s'est connecte", output);
631     }
632 
633     @Test
634     public void test323() {
635         final JexlEngine jexl = new JexlBuilder().safe(false).create();
636         final Map<String, Object> vars = new HashMap<>();
637         final JexlContext jc = new MapContext(vars);
638         Object result;
639 
640         // nothing in context, ex
641         final JexlScript script0 = jexl.createScript("a.n.t.variable");
642         JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> script0.execute(jc), "a.n.t.variable is undefined!");
643         assertTrue(xvar.toString().contains("a.n.t"));
644 
645         // defined and null
646         jc.set("a.n.t.variable", null);
647         final JexlScript script = jexl.createScript("a.n.t.variable");
648         result = script.execute(jc);
649         assertNull(result);
650 
651         // defined and null, dereference
652         jc.set("a.n.t", null);
653         final JexlScript script1 = jexl.createScript("a.n.t[0].variable");
654         xvar = assertThrows(JexlException.Variable.class, () -> script1.execute(jc), "a.n.t is null!");
655         assertTrue(xvar.toString().contains("a.n.t"));
656 
657         // undefined, dereference
658         vars.remove("a.n.t");
659         final JexlScript script2 = jexl.createScript("a.n.t[0].variable");
660         xvar = assertThrows(JexlException.Variable.class, () -> script2.execute(jc), "a.n.t is undefined!");
661         assertTrue(xvar.toString().contains("a.n.t"));
662 
663         // defined, derefence undefined property
664         final List<Object> inner = new ArrayList<>();
665         vars.put("a.n.t", inner);
666         final JexlScript script3 = jexl.createScript("a.n.t[0].variable");
667         JexlException.Property xprop = assertThrows(JexlException.Property.class, () -> script3.execute(jc), "a.n.t is null!");
668         assertTrue(xprop.toString().contains("0"));
669 
670         // defined, derefence undefined property
671         inner.add(42);
672         final JexlScript script4 = jexl.createScript("a.n.t[0].variable");
673         xprop = assertThrows(JexlException.Property.class, () -> script4.execute(jc), "a.n.t is null!");
674         assertTrue(xprop.toString().contains("variable"));
675     }
676 
677     @Test
678     public void test324() {
679         final JexlEngine jexl = new JexlBuilder().create();
680         final String src42 = "new('java.lang.Integer', 42)";
681         final JexlExpression expr0 = jexl.createExpression(src42);
682         assertEquals(42, expr0.evaluate(null));
683         final String parsed = expr0.getParsedText();
684         assertEquals(src42, parsed);
685         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createExpression("new()"), "should not parse");
686         assertTrue(xparse.toString().contains(")"));
687     }
688 
689     @Test
690     public void test325() {
691         final JexlEngine jexl = new JexlBuilder().safe(false).create();
692         final Map<String, Object> map = new HashMap<String, Object>() {
693             private static final long serialVersionUID = 1L;
694 
695             @Override
696             public Object get(final Object key) {
697                 return super.get(key == null ? "" : key);
698             }
699 
700             @Override
701             public Object put(final String key, final Object value) {
702                 return super.put(key == null ? "" : key, value);
703             }
704         };
705         map.put("42", 42);
706         final JexlContext jc = new MapContext();
707         JexlScript script;
708         Object result;
709 
710         script = jexl.createScript("map[null] = 42", "map");
711         result = script.execute(jc, map);
712         assertEquals(42, result);
713         script = jexl.createScript("map[key]", "map", "key");
714         result = script.execute(jc, map, null);
715         assertEquals(42, result);
716         result = script.execute(jc, map, "42");
717         assertEquals(42, result);
718     }
719 
720     @Test
721     public void test330() {
722         final JexlEngine jexl = new JexlBuilder().create();
723         // Extended form of: 'literal' + VARIABLE 'literal'
724         // missing + operator here ---------------^
725         // @formatter:off
726         final String longExpression = ""
727                 + //
728                 "'THIS IS A VERY VERY VERY VERY VERY VERY VERY "
729                 + //
730                 "VERY VERY LONG STRING CONCATENATION ' + VARIABLE ' <--- "
731                 + //
732                 "error: missing + between VARIABLE and literal'";
733         // @formatter:on
734         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createExpression(longExpression),
735                 "parsing malformed expression did not throw exception");
736         assertTrue(xparse.getMessage().contains("VARIABLE"));
737     }
738 
739     @Test
740     public void test331() {
741         final JexlEngine jexl = new JexlBuilder().create();
742         final JexlContext ctxt = new MapContext();
743         JexlScript script;
744         Object result;
745         script = jexl.createScript("a + '\\n' + b", "a", "b");
746         result = script.execute(ctxt, "hello", "world");
747         assertTrue(result.toString().contains("\n"));
748     }
749 
750     @Test
751     public void test347() {
752         final String src = "A.B == 5";
753         JexlEngine jexl = new JexlBuilder().safe(true).create();
754         final JexlScript script = jexl.createScript(src);
755         Object result = script.execute(null);
756         // safe navigation is lenient wrt null
757         assertFalse((Boolean) result);
758 
759         jexl = new JexlBuilder().strict(true).safe(false).create();
760         final JexlContext ctxt = new MapContext();
761         final JexlScript script1 = jexl.createScript(src);
762         // A and A.B undefined
763         assertThrows(JexlException.class, () -> script1.execute(ctxt));
764         // A is null, A.B is undefined
765         ctxt.set("A", null);
766         assertThrows(JexlException.class, () -> script1.execute(ctxt), "should only succeed with safe navigation");
767         // A.B is null
768         ctxt.set("A.B", null);
769         result = script1.execute(ctxt);
770         assertFalse((Boolean) result);
771     }
772 
773     @Test
774     public void test349() {
775         final String text = "(A ? C.D : E)";
776         final JexlEngine jexl = new JexlBuilder().safe(true).create();
777         final JexlExpression expr = jexl.createExpression(text);
778         final JexlScript script = jexl.createScript(text);
779     }
780 
781     @Test
782     public void test361_33() {
783         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
784         assertThrows(JexlException.class, () -> run361c(jexl), "null arg should fail");
785     }
786 
787     @Test
788     public void test361a_32() {
789         final JexlEngine jexl = new Engine32(new JexlBuilder().safe(false));
790         final Object result = run361a(jexl);
791         assertNotNull(result);
792     }
793 
794     @Test
795     public void test361a_33() {
796         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
797         assertThrows(JexlException.class, () -> run361a(jexl), "null arg should fail");
798     }
799 
800     @Test
801     public void test361b_32() {
802         final JexlEngine jexl = new Engine32(new JexlBuilder().safe(false).strict(false));
803         final Object result = run361b(jexl);
804         assertNotNull(result);
805     }
806 
807     @Test
808     public void test361b_33() {
809         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
810         assertThrows(JexlException.class, () -> run361b(jexl), "null arg should fail");
811     }
812 
813     @Test
814     public void test361c_32() {
815         final JexlEngine jexl = new Engine32(new JexlBuilder().safe(false).strict(false));
816         final String result = run361c(jexl);
817         assertNotNull(result);
818     }
819 
820     @Test
821     public void test361d_32() {
822         final JexlEngine jexl = new Engine32(new JexlBuilder().lexical(false).lexicalShade(false).safe(false));
823         final Object result = run361d(jexl);
824         assertNotNull(result);
825     }
826 
827     @Test
828     public void test361d_33() {
829         final JexlEngine jexl = new JexlBuilder().lexical(true).lexicalShade(true).safe(false).strict(true).create();
830         assertThrows(JexlException.class, () -> run361d(jexl), "null arg should fail");
831     }
832 
833     @Test
834     public void test367() {
835         final String text = "var toto; function foo(x) { x }; var tata = 3; foo(3)";
836         final JexlEngine jexl = new JexlBuilder().safe(true).create();
837         final JexlScript script = jexl.createScript(text);
838         final Object result = script.execute(null);
839         assertEquals(3, result);
840         final String s0 = script.getParsedText();
841         final String s1 = script.getSourceText();
842         assertNotEquals(s0, s1);
843     }
844 
845     @Test
846     public void test370() {
847         final Var370 var370 = new Var370();
848         final JexlEngine jexl = new JexlBuilder().safe(true).create();
849         final ObjectContext<Var370> ctxt = new ObjectContext<>(jexl, var370);
850         final JexlExpression get = jexl.createExpression("name");
851         // not null
852         var370.setName("John");
853         assertEquals("John", get.evaluate(ctxt));
854         assertTrue(ctxt.has("name"));
855         // null
856         var370.setName(null);
857         assertNull(get.evaluate(ctxt));
858         assertTrue(ctxt.has("name"));
859         // undefined
860         final JexlExpression get1 = jexl.createExpression("phone");
861         assertFalse(ctxt.has("phone"));
862         final JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> get1.evaluate(ctxt), "phone should be undefined!");
863         assertEquals("phone", xvar.getVariable());
864     }
865 
866     @Test
867     public void test373b() {
868         final String src = "var i = ++1";
869         final JexlEngine jexl = new JexlBuilder().safe(true).create();
870         final JexlInfo info = new JexlInfo("badscript", 0, 0);
871         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(info, src), "should not parse");
872         assertTrue(xparse.getMessage().contains("badscript"));
873     }
874 
875     @Test
876     public void test374() {
877         final JexlEngine engine = new JexlBuilder().cache(512).strict(true).silent(false).antish(false).safe(false).create();
878         // Create expression to evaluate 'name'
879         final JexlExpression expr = engine.createExpression("nested.name");
880         // Create an object with getter for name
881         final TestObject374 myObject = new TestObject374();
882         myObject.setName("John");
883         final JexlContext context = new ObjectContext<>(engine, myObject);
884         // Expect an exception because nested is null, so we are doing null.name
885         assertThrows(JexlException.class, () -> expr.evaluate(context));
886     }
887 
888     @Test
889     public void test375() {
890         final JexlSandbox jexlSandbox = new JexlSandbox(false);
891         jexlSandbox.allow(Type375.class.getName());
892         final JexlEngine engine = new JexlBuilder().sandbox(jexlSandbox).create();
893 
894         final JexlContext context = new MapContext();
895         context.set("Type", Type375.class);
896 
897         Object result = engine.createScript("Type.valueOf('DOMICILE')").execute(context);
898         assertEquals(Type375.DOMICILE, result);
899 
900         result = engine.createScript("Type.DOMICILE").execute(context);
901         assertEquals(Type375.DOMICILE, result);
902     }
903 
904     @Test
905     public void test377() {
906         final String text = "function add(x, y) { x + y } add(a, b)";
907         final JexlEngine jexl = new JexlBuilder().safe(true).create();
908         final JexlScript script = jexl.createScript(text, "a", "b");
909         final Object result = script.execute(null, 20, 22);
910         assertEquals(42, result);
911     }
912 
913     @Test
914     public void test379a() {
915         // @formatter:off
916         final String src =
917                 "#pragma jexl.import java.util\n"+
918                         "const map = new LinkedHashMap({0 : 'zero'});";
919         // @formatter:on
920         final JexlEngine jexl = new JexlBuilder().safe(true).create();
921         final JexlScript script = jexl.createScript(src);
922         assertNotNull(script);
923         final Object result = script.execute(null);
924         assertNotNull(result);
925         assertTrue(result instanceof LinkedHashMap);
926         assertEquals(1, ((Map) result).size());
927     }
928 
929     @Test
930     public void test383() {
931         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new Arithmetic383(true)).create();
932         final String src0 = "if (a) 1; else 2;";
933         final String src1 = "if (!a) 1; else 2;";
934         // local var
935         final JexlScript s0 = jexl.createScript(src0, "a");
936         final JexlScript s1 = jexl.createScript(src1, "a");
937         assertEquals(2, s0.execute(null, (Object) null));
938         assertEquals(1, s1.execute(null, (Object) null));
939         // global var undefined
940         final JexlScript s2 = jexl.createScript(src0);
941         final JexlScript s3 = jexl.createScript(src1);
942         JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> s2.execute(null, (Object) null));
943         assertEquals("a", xvar.getVariable());
944         xvar = assertThrows(JexlException.Variable.class, () -> s3.execute(null, (Object) null));
945         assertEquals("a", xvar.getVariable());
946 
947         // global var null
948         final MapContext ctxt = new MapContext();
949         ctxt.set("a", null);
950         assertEquals(2, s2.execute(ctxt, (Object) null));
951         assertEquals(1, s3.execute(ctxt, (Object) null));
952     }
953 
954     @Test
955     public void test384a() {
956         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
957         // constant
958         for (final String src0 : Arrays.asList("'ABC' + null", "null + 'ABC'")) {
959             final JexlContext ctxt = new MapContext();
960             final JexlScript s0 = jexl.createScript(src0);
961             final JexlException xvar = assertThrows(JexlException.class, () -> s0.execute(ctxt, (Object) null), "null argument should throw");
962             assertTrue(xvar.toString().contains("+"));
963         }
964         // null local a
965         for (final String src1 : Arrays.asList("'ABC' + a", "a + 'ABC'")) {
966             final JexlContext ctxt = new MapContext();
967             final JexlScript s1 = jexl.createScript(src1, "a");
968             JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> s1.execute(ctxt, (Object) null), "null argument should throw");
969             assertEquals("a", xvar.getVariable());
970             // undefined a
971             final JexlScript s2 = jexl.createScript(src1);
972             xvar = assertThrows(JexlException.Variable.class, () -> s2.execute(ctxt, (Object) null), "null argument should throw");
973             assertEquals("a", xvar.getVariable());
974             assertTrue(xvar.isUndefined());
975             // null a
976             ctxt.set("a", null);
977             xvar = assertThrows(JexlException.Variable.class, () -> s2.execute(ctxt, (Object) null), "null argument should throw");
978             assertEquals("a", xvar.getVariable());
979             assertFalse(xvar.isUndefined());
980         }
981     }
982 
983     @Test
984     public void test384b() {
985         // be explicit about + handling null
986         // @formatter:off
987         final JexlEngine jexl = new JexlBuilder()
988                 .arithmetic(new Arithmetic384(true))
989                 .safe(false)
990                 .strict(true)
991                 .create();
992         // @formatter:on
993         // constant
994         for (final String src0 : Arrays.asList("'ABC' + null", "null + 'ABC'")) {
995             final JexlContext ctxt = new MapContext();
996             final JexlScript s0 = jexl.createScript(src0);
997             assertEquals("ABC", s0.execute(ctxt));
998         }
999         // null local a
1000         for (final String src1 : Arrays.asList("'ABC' + a", "a + 'ABC'")) {
1001             final JexlContext ctxt = new MapContext();
1002             final JexlScript s1 = jexl.createScript(src1, "a");
1003             assertEquals("ABC", s1.execute(ctxt, (Object) null));
1004             // undefined a
1005             final JexlScript s2 = jexl.createScript(src1);
1006             final JexlException.Variable xvar = assertThrows(JexlException.Variable.class, () -> s2.execute(ctxt, (Object) null), "null argument should throw");
1007             assertEquals("a", xvar.getVariable());
1008             assertTrue(xvar.isUndefined());
1009 
1010             // null a
1011             ctxt.set("a", null);
1012             assertEquals("ABC", s1.execute(ctxt, (Object) null));
1013         }
1014     }
1015 
1016     @Test
1017     public void test384c() {
1018         final Arithmetic384c ja = new Arithmetic384c(true);
1019         // @formatter:off
1020         final JexlEngine jexl = new JexlBuilder()
1021                 .safe(false)
1022                 .strict(true)
1023                 .arithmetic(ja)
1024                 .create();
1025         // @formatter:on
1026         assertTrue(ja.toBoolean(jexl.createExpression("3 < 4").evaluate(null)));
1027         assertTrue(ja.toBoolean(jexl.createExpression("6 <= 8").evaluate(null)));
1028         assertFalse(ja.toBoolean(jexl.createExpression("6 == 7").evaluate(null)));
1029         assertTrue(ja.toBoolean(jexl.createExpression("4 > 2").evaluate(null)));
1030         assertTrue(ja.toBoolean(jexl.createExpression("8 > 6").evaluate(null)));
1031         assertTrue(ja.toBoolean(jexl.createExpression("7 != 6").evaluate(null)));
1032         assertEquals(6, ja.getCmpCalls());
1033     }
1034 
1035     @Test
1036     public void test384d() {
1037         final Arithmetic384c ja = new Arithmetic384d(true);
1038         // @formatter:off
1039         final JexlEngine jexl = new JexlBuilder()
1040                 .safe(false)
1041                 .strict(true)
1042                 .arithmetic(ja)
1043                 .create();
1044         // @formatter:on
1045         assertTrue(ja.toBoolean(jexl.createExpression("3 < 4").evaluate(null)));
1046         assertTrue(ja.toBoolean(jexl.createExpression("6 <= 8").evaluate(null)));
1047         assertFalse(ja.toBoolean(jexl.createExpression("6 == 7").evaluate(null)));
1048         assertTrue(ja.toBoolean(jexl.createExpression("4 > 2").evaluate(null)));
1049         assertTrue(ja.toBoolean(jexl.createExpression("8 > 6").evaluate(null)));
1050         assertTrue(ja.toBoolean(jexl.createExpression("7 != 6").evaluate(null)));
1051         assertEquals(6, ja.getCmpCalls());
1052     }
1053 
1054     @Test
1055     public void test390() throws Exception {
1056         // @formatter:off
1057         final JexlEngine jexl = new JexlBuilder()
1058                 .safe(false)
1059                 .strict(true)
1060                 .debug(true)
1061                 .create();
1062         // @formatter:on
1063         JexlScript script = null;
1064         final String src = "if (true) #pragma one 42";
1065         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src), "should have failed parsing");
1066         assertTrue(xparse.getDetail().contains("pragma"));
1067 
1068         final String src1 = "if (true) { #pragma one 42 }";
1069         script = jexl.createScript(src1);
1070         final Object result = script.execute(null);
1071         debuggerCheck(jexl);
1072     }
1073 
1074     @Test
1075     public void test393() {
1076         // @formatter:off
1077         final String src = "const total = 0;\n" +
1078                 "if (true) {\n" +
1079                 "  total = 1;\n" +
1080                 "}\n" +
1081                 "total; ";
1082         final JexlEngine jexl = new JexlBuilder()
1083                 .safe(false)
1084                 .strict(true)
1085                 .create();
1086         // @formatter:on
1087         final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src), "should fail on const total assignment");
1088         assertTrue(xparse.getMessage().contains("total"));
1089     }
1090 
1091     @Test
1092     public void testBackslashes() throws Exception {
1093         final JexlEngine jexl = new JexlBuilder().safe(false).create();
1094         final String src = "\"\b\t\f\"";
1095         JexlScript s = jexl.createScript(src);
1096         assertNotNull(s);
1097         final String ctl = "\b\t\f";
1098         assertEquals(ctl, s.execute(null));
1099         String parsed = s.getParsedText();
1100         assertEquals("'\\b\\t\\f'", parsed);
1101         s = jexl.createScript(src);
1102         assertNotNull(s);
1103         assertEquals(ctl, s.execute(null));
1104         parsed = s.getParsedText();
1105         assertEquals("'\\b\\t\\f'", parsed);
1106     }
1107 
1108     @Test
1109     public void testDow() {
1110         // @formatter:off
1111         final String src = "(y, m, d)->{\n" +
1112                 "// will return 0 for Sunday, 6 for Saturday\n" +
1113                 "const t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];\n"+
1114                 "if (m < 3) { --y }\n" +
1115                 "(y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;\n" +
1116             "}";
1117         // @formatter:on
1118         final JexlEngine jexl = new JexlBuilder().create();
1119         final JexlScript script = jexl.createScript(src);
1120         Object r = script.execute(null, 2023, 3, 1);
1121         assertTrue(r instanceof Number);
1122         Number dow = (Number) r;
1123         assertEquals(3, dow.intValue());
1124         r = script.execute(null, 1969, 7, 20);
1125         assertTrue(r instanceof Number);
1126         dow = (Number) r;
1127         assertEquals(0, dow.intValue());
1128     }
1129 
1130     @Test
1131     public void testIssue394() {
1132         final StringBuilder x = new StringBuilder("foobar");
1133         assertEquals("foobar", x.toString());
1134         final String src = "x -> x.setLength(3)";
1135         final JexlEngine jexl = new JexlBuilder().create();
1136         final JexlScript script = jexl.createScript(src);
1137         final Object result = script.execute(null, x);
1138         assertEquals("foo", x.toString());
1139     }
1140 
1141     @Test
1142     public void testIssue397() {
1143         String result;
1144         final String control = Class397.class.getName();
1145         final JexlEngine jexl = new JexlBuilder().permissions(RESTRICTED).create();
1146 
1147         final Interface397i instance = new Class397();
1148         result = (String) jexl.invokeMethod(instance, "summary");
1149         assertEquals(control, result);
1150 
1151         final Interface397i proxy = createProxy(jexl, instance, new Class[] { Interface397i.class });
1152         result = (String) jexl.invokeMethod(proxy, "summary");
1153         assertEquals(control, result);
1154 
1155         final JexlScript script = jexl.createScript("dan.summary()", "dan");
1156         result = (String) script.execute(null, instance);
1157         assertEquals(control, result);
1158         result = (String) script.execute(null, proxy);
1159         assertEquals(control, result);
1160     }
1161 
1162     @Test
1163     public void testIssue398a() {
1164         // @formatter:off
1165         final String src = "let m = {\n" +
1166             "  \"foo\": 1,\n" +
1167             "  \"bar\": 2,\n" +
1168             "}";
1169         // @formatter:on
1170         final JexlEngine jexl = new JexlBuilder().create();
1171         final JexlScript script = jexl.createScript(src);
1172         final Object result = script.execute(null);
1173         assertTrue(result instanceof Map);
1174         final Map<?, ?> map = (Map<?, ?>) result;
1175         assertEquals(2, map.size());
1176     }
1177 
1178     @Test
1179     public void testIssue398b() {
1180         final Map<String, Object> foo = Collections.singletonMap("X", "x");
1181         final Map<String, Object> bar = Collections.singletonMap("Y", "y");
1182         final JexlContext ctxt = new MapContext();
1183         ctxt.set("foo", foo);
1184         ctxt.set("bar", bar);
1185         // @formatter:off
1186         final String src = "let m = {\n" +
1187                 "  foo.X: 1,\n" +
1188                 "  bar.Y: 2,\n" +
1189                 "}";
1190         // @formatter:on
1191         final JexlEngine jexl = new JexlBuilder().create();
1192         JexlScript script = jexl.createScript(src);
1193         Object result = script.execute(ctxt);
1194         assertTrue(result instanceof Map);
1195         Map<?, ?> map = (Map<?, ?>) result;
1196         assertEquals(2, map.size());
1197         assertEquals(1, map.get("x"));
1198         assertEquals(2, map.get("y"));
1199 
1200         script = jexl.createScript(src, "foo", "bar");
1201         result = script.execute(null, foo, bar);
1202         assertTrue(result instanceof Map);
1203         map = (Map<?, ?>) result;
1204         assertEquals(2, map.size());
1205         assertEquals(1, map.get("x"));
1206         assertEquals(2, map.get("y"));
1207     }
1208 
1209     @Test
1210     public void testIssue398c() {
1211         final JexlEngine jexl = new JexlBuilder().create();
1212         final Object empty = jexl.createScript("[,...]").execute(null);
1213         assertNotNull(empty);
1214         assertTrue(jexl.createScript("[1]").execute(null) instanceof int[]);
1215         assertTrue(jexl.createScript("[1,...]").execute(null) instanceof ArrayList<?>);
1216         assertTrue(jexl.createScript("{1}").execute(null) instanceof HashSet<?>);
1217         assertTrue(jexl.createScript("{1,...}").execute(null) instanceof LinkedHashSet<?>);
1218         assertTrue(jexl.createScript("{'one': 1}").execute(null) instanceof HashMap<?, ?>);
1219         assertTrue(jexl.createScript("{'one': 1,...}").execute(null) instanceof LinkedHashMap<?, ?>);
1220     }
1221 
1222     @Test
1223     public void testPropagateOptions() {
1224         // @formatter:off
1225         final String src0 = "`${$options.strict?'+':'-'}strict"
1226                 + " ${$options.cancellable?'+':'-'}cancellable"
1227                 + " ${$options.lexical?'+':'-'}lexical"
1228                 + " ${$options.lexicalShade?'+':'-'}lexicalShade"
1229                 + " ${$options.sharedInstance?'+':'-'}sharedInstance"
1230                 + " ${$options.safe?'+':'-'}safe`";
1231         // @formatter:on
1232         final String text = "#pragma script.mode pro50\n" + "()->{ ()->{ " + src0 + "; } }";
1233         final JexlEngine jexl = new JexlBuilder().safe(true).create();
1234         final JexlScript script = jexl.createScript(text);
1235         JexlContext context = pragmaticContext();
1236         final JexlScript closure = (JexlScript) script.execute(context);
1237         final JexlContext opts = new OptionsContext();
1238         final Object result = closure.execute(opts);
1239         assertEquals("+strict +cancellable +lexical +lexicalShade -sharedInstance -safe", result);
1240 
1241         final String text0 = "#pragma script.mode pro50\n" + "()->{ " + src0 + "; }";
1242         final JexlScript script0 = jexl.createScript(text0);
1243         context = pragmaticContext();
1244         final Object result0 = script0.execute(context);
1245         assertEquals("+strict +cancellable +lexical +lexicalShade -sharedInstance -safe", result0);
1246 
1247         final String text1 = "#pragma script.mode pro50\n" + src0;
1248         final JexlScript script1 = jexl.createScript(text1);
1249         context = pragmaticContext();
1250         final Object result1 = script1.execute(context);
1251         assertEquals("+strict +cancellable +lexical +lexicalShade -sharedInstance -safe", result1);
1252 
1253         final String text2 = src0;
1254         final JexlScript script2 = jexl.createScript(text2);
1255         context = pragmaticContext();
1256         final Object result2 = script2.execute(context);
1257         assertEquals("-strict -cancellable -lexical -lexicalShade +sharedInstance +safe", result2);
1258     }
1259 
1260     @Test
1261     public void tests301b() {
1262         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(new JexlArithmetic(false)).create();
1263         final Object[] xs = { null, null, new Object[] { null, 1 } };
1264         final String[] srcs = { "x.0", "x[0]", "x[0][0]" };
1265         final JexlContext ctxt = new MapContext();
1266         for (int i = 0; i < xs.length; ++i) {
1267             ctxt.set("x", xs[i]);
1268             final String src = srcs[i];
1269             final JexlScript s = jexl.createScript(src);
1270             assertThrows(JexlException.class, () -> s.execute(null));
1271         }
1272     }
1273 
1274     @Test
1275     public void testSO20220930() {
1276         // fill some drivers in a list
1277         final List<Driver0930> values = new ArrayList<>();
1278         for (int i = 0; i < 8; ++i) {
1279             values.add(new Driver0930("drvr" + Integer.toOctalString(i)));
1280         }
1281         for (int i = 0; i < 4; ++i) {
1282             values.add(new Driver0930("favorite" + Integer.toOctalString(i)));
1283         }
1284         // Use a context that can filter and that exposes Collectors
1285         final JexlEngine jexl = new JexlBuilder().safe(false).create();
1286         final JexlContext context = new Context0930();
1287         context.set("values", values);
1288         context.set("Collectors", Collectors.class);
1289         // The script with a JEXL 3.2 (lambda function) and 3.3 syntax (lambda expression)
1290         final String src32 = "values.stream().filter((driver) ->{ driver.attributeName =^ 'favorite' }).collect(Collectors.toList())";
1291         final String src33 = "values.stream().filter(driver -> driver.attributeName =^ 'favorite').collect(Collectors.toList())";
1292         for (final String src : Arrays.asList(src32, src33)) {
1293             final JexlExpression s = jexl.createExpression(src);
1294             assertNotNull(s);
1295 
1296             final Object r = s.evaluate(context);
1297             assertNotNull(r);
1298             // got a filtered list of 4 drivers whose attribute name starts with 'favorite'
1299             final List<Driver0930> l = (List<Driver0930>) r;
1300             assertEquals(4, l.size());
1301             for (final Driver0930 d : l) {
1302                 assertTrue(d.getAttributeName().startsWith("favorite"));
1303             }
1304         }
1305     }
1306 
1307     @Test
1308     public void testUnsolvableMethod() throws Exception {
1309         final JexlEngine jexl = new JexlBuilder().create();
1310         // @formatter:off
1311         final JexlScript script = jexl.createScript(
1312             "var myFunction1 = function(object) {"
1313                 + " myNonExistentFunction();"
1314                 + "}"
1315                 + "var myFunction2 = function(object) {"
1316                 + " myFunction1();"
1317                 + "}"
1318                 + "myFunction2();");
1319         // @formatter:on
1320         final JexlException.Method unsolvable = assertThrows(JexlException.Method.class, () -> script.execute(new MapContext()));
1321         assertEquals("myNonExistentFunction", unsolvable.getMethod());
1322     }
1323 }