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