1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3;
18
19 import static org.apache.commons.jexl3.JexlTestCase.createEngine;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotEquals;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import static org.junit.jupiter.api.Assertions.fail;
28
29 import java.io.StringReader;
30 import java.io.StringWriter;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.BitSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.concurrent.Callable;
38
39 import org.apache.commons.jexl3.internal.LexicalScope;
40 import org.apache.commons.jexl3.internal.Script;
41 import org.junit.jupiter.api.Test;
42
43
44
45
46 public class LexicalTest {
47
48 public static class DebugContext extends MapContext {
49 public DebugContext() {
50 }
51 public Object debug(final Object arg) {
52 return arg;
53 }
54 }
55
56 public static class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
57 @Override
58 public Object processAnnotation(final String name, final Object[] args, final Callable<Object> statement) throws Exception {
59
60 if ("scale".equals(name)) {
61 final JexlOptions options = getEngineOptions();
62 final int scale = options.getMathScale();
63 final int newScale = (Integer) args[0];
64 options.setMathScale(newScale);
65 try {
66 return statement.call();
67 } finally {
68 options.setMathScale(scale);
69 }
70 }
71 return statement.call();
72 }
73 }
74
75
76
77
78 public static class TestContext extends JexlEvalContext {
79 public TestContext() {}
80 public TestContext(final Map<String, Object> map) {
81 super(map);
82 }
83
84
85
86
87
88
89
90
91 public Object tryCatch(final JexlScript tryFn, final JexlScript catchFn, final Object... args) {
92 Object result;
93 try {
94 result = tryFn.execute(this, args);
95 } catch (final Throwable xthrow) {
96 result = catchFn != null ? catchFn.execute(this, xthrow) : xthrow;
97 }
98 return result;
99 }
100 }
101
102 public static class VarContext extends MapContext implements JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
103 private JexlOptions options = new JexlOptions();
104
105 @Override
106 public JexlOptions getEngineOptions() {
107 return options;
108 }
109
110 @Override
111 public void processPragma(final String key, final Object value) {
112 if ("jexl.options".equals(key) && "canonical".equals(value)) {
113 options.setStrict(true);
114 options.setLexical(true);
115 options.setLexicalShade(true);
116 options.setSafe(false);
117 }
118 }
119
120 JexlOptions snatchOptions() {
121 final JexlOptions o = options;
122 options = new JexlOptions();
123 return o;
124 }
125 }
126
127 private void checkParse(final JexlFeatures f, final List<String> srcs, final boolean expected) {
128 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
129 for(final String src : srcs) {
130 if (!src.isEmpty()) {
131 try {
132 final JexlScript script = jexl.createScript(src);
133 if (!expected) {
134 fail(src);
135 }
136 } catch (final JexlException.Parsing xlexical) {
137 if (expected) {
138 fail(src);
139 }
140
141 }
142 }
143 }
144
145 }
146
147 private void checkParse(final List<String> srcs, final boolean expected) {
148 checkParse(null, srcs, expected);
149 }
150
151 void runLexical0(final boolean feature) {
152 final JexlFeatures f = new JexlFeatures();
153 f.lexical(feature);
154 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
155 final JexlEvalContext ctxt = new JexlEvalContext();
156 final JexlOptions options = ctxt.getEngineOptions();
157
158 options.setLexical(true);
159 runLexical0(jexl, ctxt, "var x = 0; var x = 1;", feature);
160 runLexical0(jexl, ctxt, "var x = 0; for(var y : null) { var y = 1;", feature);
161 runLexical0(jexl, ctxt, "var x = 0; for(var x : null) {};", feature);
162 runLexical0(jexl, ctxt, "(x)->{ var x = 0; x; }", feature);
163 runLexical0(jexl, ctxt, "var x; if (true) { if (true) { var x = 0; x; } }", feature);
164 runLexical0(jexl, ctxt, "if (a) { var y = (x)->{ var x = 0; x; }; y(2) }", feature);
165 final JexlException xany = assertThrows(JexlException.class, () -> {
166 final JexlScript script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
167 if (!feature) {
168 script.execute(ctxt, 42);
169 }
170 });
171 assertNotNull(xany.toString());
172
173 final JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
174 if (!feature) {
175 script.execute(ctxt, 42);
176 }
177 }
178
179 private void runLexical0(final JexlEngine jexl, final JexlEvalContext ctxt, final String source, final boolean feature) {
180 final JexlException xany = assertThrows(JexlException.class, () -> {
181 final JexlScript script = jexl.createScript(source);
182 if (!feature) {
183 script.execute(ctxt);
184 }
185 });
186 assertNotNull(xany.toString());
187 }
188
189 void runLexical1(final boolean shade) {
190 final JexlEngine jexl = new JexlBuilder().strict(true).create();
191 final JexlEvalContext ctxt = new JexlEvalContext();
192 Object result;
193 ctxt.set("x", 4242);
194 final JexlOptions options = ctxt.getEngineOptions();
195
196 options.setLexical(true);
197 options.setLexicalShade(shade);
198 JexlScript script;
199 try {
200
201 script = jexl.createScript("{ var x = 0; } x");
202 script.execute(ctxt);
203 if (shade) {
204 fail("local shade means 'x' should be undefined");
205 }
206 } catch (final JexlException xany) {
207 if (!shade) {
208 throw xany;
209 }
210 }
211 try {
212
213 script = jexl.createScript("{ var x = 0; } x = 42");
214 script.execute(ctxt);
215 if (shade) {
216 fail("local shade means 'x = 42' should be undefined");
217 }
218 } catch (final JexlException xany) {
219 if (!shade) {
220 throw xany;
221 }
222 }
223 try {
224
225 script = jexl.createScript("{ var x = 0; } y = 42");
226 script.execute(ctxt);
227 if (shade) {
228 fail("local shade means 'y = 42' should be undefined (y is undefined)");
229 }
230 } catch (final JexlException xany) {
231 if (!shade) {
232 throw xany;
233 }
234 }
235
236 script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
237
238 script.execute(ctxt, 42);
239
240
241 ctxt.set("y", 4242);
242 try {
243
244 script = jexl.createScript("{ var y = 0; } y = 42");
245 result = script.execute(ctxt);
246 if (!shade) {
247 assertEquals(42, result);
248 } else {
249 fail("local shade means 'y = 42' should be undefined");
250 }
251 } catch (final JexlException xany) {
252 if (!shade) {
253 throw xany;
254 }
255 }
256 }
257
258 protected void runLexical2(final boolean lexical) {
259 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(lexical).create();
260 final JexlContext ctxt = new MapContext();
261 final JexlScript script = jexl.createScript("{var x = 42}; {var x; return x; }");
262 final Object result = script.execute(ctxt);
263 if (lexical) {
264 assertNull(result);
265 } else {
266 assertEquals(42, result);
267 }
268 }
269
270 void runTestScope(final LexicalScope scope, final int init, final int count, final int step) {
271 final int size = (count - init) / step;
272 for(int i = init; i < count; i += step) {
273 assertTrue(scope.addSymbol(i));
274 if (i % (step + 1) == 1) {
275 assertTrue(scope.addConstant(i));
276 }
277 assertFalse(scope.addSymbol(i));
278 }
279 for(int i = init; i < count; i += step) {
280 assertTrue(scope.hasSymbol(i));
281 for(int s = 1; s < step; ++s) {
282 assertFalse(scope.hasSymbol(i + s));
283 }
284 if (i % (step + 1) == 1) {
285 assertTrue(scope.isConstant(i));
286 }
287 }
288 assertEquals(size, scope.getSymbolCount());
289 final BitSet collect = new BitSet();
290 scope.clearSymbols(b -> collect.set(b));
291 for(int i = init; i < count; i += step) {
292 assertTrue(collect.get(i), "missing " + i);
293 }
294 assertEquals(0, scope.getSymbolCount());
295 }
296
297 private JexlFeatures runVarLoop(final boolean flag, final String src) {
298 final VarContext vars = new VarContext();
299 final JexlOptions options = vars.getEngineOptions();
300 options.setLexical(true);
301 options.setLexicalShade(true);
302 options.setSafe(false);
303 final JexlFeatures features = new JexlFeatures();
304 if (flag) {
305 features.lexical(true).lexicalShade(true);
306 }
307 final JexlEngine jexl = new JexlBuilder().features(features).create();
308 final JexlScript script = jexl.createScript(src);
309 final List<Integer> out = new ArrayList<>(10);
310 vars.set("$out", out);
311 final Object result = script.execute(vars);
312 assertEquals(true, result);
313 assertEquals(10, out.size());
314 return features;
315 }
316
317 @Test
318 public void testAnnotation() {
319 final JexlFeatures f = new JexlFeatures();
320 f.lexical(true);
321 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
322 final JexlScript script = jexl.createScript("@scale(13) @test var i = 42");
323 final JexlContext jc = new OptAnnotationContext();
324 final Object result = script.execute(jc);
325 assertEquals(42, result);
326 }
327
328 @Test
329 public void testCaptured0() {
330 final JexlFeatures f = new JexlFeatures();
331 f.lexical(true);
332 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
333 final JexlScript script = jexl.createScript(
334 "var x = 10; (b->{ x + b })(32)");
335 final JexlContext jc = new MapContext();
336 final Object result = script.execute(jc);
337 assertEquals(42, result);
338 }
339
340 @Test
341 public void testCaptured1() {
342 final JexlFeatures f = new JexlFeatures();
343 f.lexical(true);
344 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
345 final JexlScript script = jexl.createScript(
346 "{ var x = 10; } (b->{ x + b })(32)");
347 final JexlContext jc = new MapContext();
348 jc.set("x", 11);
349 final Object result = script.execute(jc);
350 assertEquals(43, result);
351 }
352
353 @Test
354 public void testConst0a() {
355 final JexlFeatures f = new JexlFeatures();
356 final JexlEngine jexl = new JexlBuilder().strict(true).create();
357 final JexlScript script = jexl.createScript(
358 "{ const x = 10; x + 1 }; { let x = 20; x = 22}");
359 final JexlContext jc = new MapContext();
360 final Object result = script.execute(jc);
361 assertEquals(22, result);
362 }
363
364 @Test
365 public void testConst0b() {
366 final JexlFeatures f = new JexlFeatures();
367 final JexlEngine jexl = new JexlBuilder().strict(true).create();
368 final JexlScript script = jexl.createScript(
369 "{ const x = 10; }{ const x = 20; }");
370 final JexlContext jc = new MapContext();
371 final Object result = script.execute(jc);
372 assertEquals(20, result);
373 }
374
375 @Test
376 public void testConst1() {
377 final JexlFeatures f = new JexlFeatures();
378 final JexlEngine jexl = new JexlBuilder().strict(true).create();
379 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript("const foo; foo"),
380 "should fail, const foo must be followed by assign.");
381 assertTrue(xparse.getMessage().contains("const"));
382
383 }
384
385 @Test
386 public void testConst2a() {
387 final JexlEngine jexl = new JexlBuilder().strict(true).create();
388 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
389 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript("const foo = 42; foo " + op + " 1;"),
390 "should fail, const precludes assignment");
391 assertTrue(xparse.getMessage().contains("foo"));
392 }
393 }
394
395 @Test
396 public void testConst2b() {
397 final JexlEngine jexl = new JexlBuilder().strict(true).create();
398 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
399 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class,
400 () -> jexl.createScript("const foo = 42; if (true) { foo " + op + " 1; }"), "should fail, const precludes assignment");
401 assertTrue(xparse.getMessage().contains("foo"));
402 }
403 }
404
405 @Test
406 public void testConst2c() {
407 final JexlEngine jexl = new JexlBuilder().strict(true).create();
408 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
409 final JexlScript script = jexl.createScript("{ const foo = 42; } { let foo = 0; foo " + op + " 1; }");
410 assertNotNull(script);
411 }
412 }
413
414 @Test
415 public void testConst3a() {
416 final JexlEngine jexl = new JexlBuilder().create();
417
418 final List<String> srcs = Arrays.asList(
419 "const f = ()->{ var foo = 3; foo = 5; }",
420 "const y = '42'; const f = (let y)->{ var foo = 3; foo = 5; }",
421 "const foo = '34'; const f = ()->{ var foo = 3; foo = 5; };",
422 "const bar = '34'; const f = ()->{ var f = 3; f = 5; };",
423 "const bar = '34'; const f = ()->{ var bar = 3; z ->{ bar += z; } };");
424
425 for (final String src : srcs) {
426 final JexlScript script = jexl.createScript(src);
427 final Object result = script.execute(null);
428 assertNotNull(result, src);
429 }
430 }
431
432 @Test
433 public void testConst3b() {
434 final JexlEngine jexl = new JexlBuilder().create();
435
436 final List<String> srcs = Arrays.asList(
437 "const f = ()->{ var foo = 3; f = 5; }",
438 "const y = '42'; const f = (let z)->{ y += z; }",
439 "const foo = '34'; const f = ()->{ foo = 3; };",
440 "const bar = '34'; const f = ()->{ bar = 3; z ->{ bar += z; } };",
441 "let bar = '34'; const f = ()->{ const bar = 3; z ->{ bar += z; } };");
442
443 for (final String src : srcs) {
444 final JexlException.Assignment xassign = assertThrows(JexlException.Assignment.class, () -> jexl.createScript(src), src);
445 assertNotNull(xassign, src);
446 }
447 }
448
449 @Test
450 public void testConstCaptures() {
451
452 final List<String> srcsFalse = Arrays.asList(
453 "const x = 0; x = 1;",
454 "const x = 0; x *= 1;",
455 "const x = 0; var x = 1;",
456 "const x = 0; if (true) { var x = 1;}" ,
457 "const x = 0; if (true) { x = 1;}" ,
458 "const x = 0; if (true) { var f = y -> { x = y + 1; x } }" ,
459 "const x = 0; if (true) { var f = y -> { z -> { x = y + 1; x } } }" ,
460 "const x = 0; if (true) { if (false) { y -> { x = y + 1; x } } }" ,
461 "const x = 0; if (true) { if (false) { y -> { z -> { x = y + 1; x } } }" ,
462 ""
463 );
464
465 checkParse(srcsFalse, false);
466
467 final List<String> srcsTrue = Arrays.asList(
468 "const x = 0; if (true) { var f = x -> x + 1;}" ,
469 "const x = 0; if (true) { var f = y -> { var x = y + 1; x } }" ,
470 "const x = 0; if (true) { var f = y -> { const x = y + 1; x } }" ,
471 "const x = 0; if (true) { var f = y -> { z -> { let x = y + 1; x } } }" ,
472 "");
473
474 checkParse(srcsTrue, true);
475 }
476
477 @Test
478 public void testContextualOptions0() {
479 final JexlFeatures f = new JexlFeatures();
480 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
481 final JexlEvalContext ctxt = new JexlEvalContext();
482 final JexlOptions options = ctxt.getEngineOptions();
483 options.setSharedInstance(false);
484 options.setLexical(true);
485 options.setLexicalShade(true);
486 ctxt.set("options", options);
487 final JexlScript script = jexl.createScript("{var x = 42;} options.lexical = false; options.lexicalShade=false; x");
488 assertThrows(JexlException.class, () -> script.execute(ctxt), "setting options.lexical should have no effect during execution");
489 }
490
491 @Test
492 public void testContextualOptions1() {
493 final JexlFeatures f = new JexlFeatures();
494 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
495 final JexlEvalContext ctxt = new TestContext();
496 final JexlOptions options = ctxt.getEngineOptions();
497 options.setSharedInstance(true);
498 options.setLexical(true);
499 options.setLexicalShade(true);
500 ctxt.set("options", options);
501
502 final JexlScript runner = jexl.createScript(
503 "options.lexical = flag; options.lexicalShade = flag;"
504 + "tryCatch(test, catcher, 42);",
505 "flag", "test", "catcher");
506
507 final JexlScript tested = jexl.createScript("(y)->{ {var x = y;} x }");
508 final JexlScript catchFn = jexl.createScript("(xany)-> { xany }");
509 Object result;
510
511 result = runner.execute(ctxt, false, tested, catchFn);
512
513 assertEquals(42, result);
514
515 result = runner.execute(ctxt, true, tested, catchFn);
516
517 assertTrue(result instanceof JexlException.Variable);
518 }
519
520 @Test
521 public void testForVariable0a() {
522 final JexlFeatures f = new JexlFeatures();
523 f.lexical(true);
524 f.lexicalShade(true);
525 final JexlEngine jexl = createEngine(f);
526 assertThrows(JexlException.class, () -> jexl.createScript("for(let x : 1..3) { let c = 0}; return x", "Should not have been parsed"));
527 }
528
529 @Test
530 public void testForVariable0b() {
531 final JexlFeatures f = new JexlFeatures();
532 f.lexical(true);
533 f.lexicalShade(true);
534 final JexlEngine jexl = createEngine(f);
535 assertThrows(JexlException.class, () -> jexl.createScript("for(var x : 1..3) { var c = 0}; return x", "Should not have been parsed"));
536 }
537
538 @Test
539 public void testForVariable1a() {
540 final JexlFeatures f = new JexlFeatures();
541 f.lexical(true);
542 f.lexicalShade(true);
543 final JexlEngine jexl = createEngine(f);
544 assertThrows(JexlException.class, () -> jexl.createScript("for(var x : 1..3) { var c = 0} for(var x : 1..3) { var c = 0}; return x"),
545 "Should not have been parsed");
546 }
547
548 @Test
549 public void testForVariable1b() {
550 final JexlFeatures f = new JexlFeatures();
551 f.lexical(true);
552 f.lexicalShade(true);
553 final JexlEngine jexl = createEngine(f);
554 assertThrows(JexlException.class, () -> jexl.createScript("for(let x : 1..3) { let c = 0} for(let x : 1..3) { var c = 0}; return x"),
555 "Should not have been parsed");
556 }
557
558 @Test
559 public void testInnerAccess0() {
560 final JexlFeatures f = new JexlFeatures();
561 f.lexical(true);
562 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
563
564 final JexlScript script = jexl.createScript(
565 "var x = 32; ("
566 + "()->{ for(var x : null) { var c = 0; {return x; }} })"
567 + "();");
568
569 assertNull(script.execute(null));
570 }
571
572 @Test
573 public void testInnerAccess1a() {
574 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
575 final JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { var c = 0; {return x; }} })();");
576 assertNotNull(script);
577 }
578
579 @Test
580 public void testInnerAccess1b() {
581 final JexlEngine jexl = new JexlBuilder().strict(true).create();
582 final JexlScript script = jexl.createScript("let x = 32; (()->{ for(let x : null) { let c = 0; { return x; } } } )(); ");
583 assertNotNull(script);
584 final String dbg = JexlTestCase.toString(script);
585 final String src = script.getSourceText();
586 assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbg));
587 }
588
589 @Test
590 public void testInternalLexicalFeatures() {
591 final String str = "42";
592 final JexlFeatures f = new JexlFeatures();
593 f.lexical(true);
594 f.lexicalShade(true);
595 final JexlEngine jexl = new JexlBuilder().features(f).create();
596 final JexlScript e = jexl.createScript(str);
597 final VarContext vars = new VarContext();
598 final JexlOptions opts = vars.getEngineOptions();
599
600 opts.setSharedInstance(true);
601 final Script script = (Script) e;
602 final JexlFeatures features = script.getFeatures();
603 assertTrue(features.isLexical());
604 assertTrue(features.isLexicalShade());
605 final Object result = e.execute(vars);
606 assertEquals(42, result);
607 assertTrue(opts.isLexical());
608 assertTrue(opts.isLexicalShade());
609 }
610
611 @Test
612 public void testLet0() {
613 final JexlFeatures f = new JexlFeatures();
614 final JexlEngine jexl = new JexlBuilder().strict(true).create();
615 final JexlScript script = jexl.createScript(
616 "{ let x = 10; } (b->{ x + b })(32)");
617 final JexlContext jc = new MapContext();
618 jc.set("x", 11);
619 final Object result = script.execute(jc);
620 assertEquals(43, result);
621
622 }
623
624 @Test
625 public void testLetFail() {
626 final List<String> srcs = Arrays.asList(
627 "let x = 0; var x = 1;",
628 "var x = 0; let x = 1;",
629 "let x = 0; let x = 1;",
630 "var x = 0; const f = (var x) -> { let x = 1; } f()",
631 "var x = 0; const f = (let x) -> { let x = 1; } f()",
632 "var x = 0; const f = (let x) -> { var x = 1; } f()",
633 ""
634 );
635 checkParse(srcs, false);
636 }
637
638 @Test
639 public void testLetSucceed() {
640 final List<String> srcs = Arrays.asList(
641 "var x = 1; var x = 0;",
642 "{ let x = 0; } var x = 1;",
643 "var x = 0; var f = () -> { let x = 1; } f()",
644
645 "var x = 0; var f = (let x) -> { x = 1; } f()",
646 "var x = 0; let f = (let x) -> { x = 1; } f()",
647 "var x = 0; const f = (let x) -> { x = 1; } f()",
648 ""
649 );
650 checkParse(srcs, true);
651 }
652
653 @Test
654 public void testLexical0a() {
655 runLexical0(false);
656 }
657
658 @Test
659 public void testLexical0b() {
660 runLexical0(true);
661 }
662
663 @Test
664 public void testLexical1() {
665 final JexlEngine jexl = new JexlBuilder().strict(true).create();
666 final JexlEvalContext ctxt = new JexlEvalContext();
667 final JexlOptions options = ctxt.getEngineOptions();
668
669 options.setLexical(true);
670 Object result;
671
672 final JexlScript script = jexl.createScript("var x = 0; for(var y : [1]) { var x = 42; return x; };");
673 JexlException xany = assertThrows(JexlException.class, () -> script.execute(ctxt));
674 assertNotNull(xany.toString());
675
676 final JexlScript script1 = jexl.createScript("(x)->{ if (x) { var x = 7 * (x + x); x; } }");
677 xany = assertThrows(JexlException.class, () -> script.execute(ctxt, 3));
678 assertNotNull(xany.toString());
679
680 final JexlScript script3 = jexl.createScript("{ var x = 0; } var x = 42; x");
681 result = script3.execute(ctxt, 21);
682 assertEquals(42, result);
683 }
684
685 @Test
686 public void testLexical1a() {
687 runLexical1(false);
688 }
689
690 @Test
691 public void testLexical1b() {
692 runLexical1(true);
693 }
694
695 @Test
696 public void testLexical2a() {
697 runLexical2(true);
698 }
699
700 @Test
701 public void testLexical2b() {
702 runLexical2(false);
703 }
704
705 @Test
706 public void testLexical3() {
707 final String str = "var s = {}; for (var i : [1]) s.add(i); s";
708 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
709 JexlScript e = jexl.createScript(str);
710 final JexlContext jc = new MapContext();
711 Object o = e.execute(jc);
712 assertTrue(((Set)o).contains(1));
713
714 e = jexl.createScript(str);
715 o = e.execute(jc);
716 assertTrue(((Set)o).contains(1));
717 }
718
719 @Test
720 public void testLexical4() {
721 final JexlEngine Jexl = new JexlBuilder().silent(false).strict(true).lexical(true).create();
722 final JxltEngine Jxlt = Jexl.createJxltEngine();
723 final JexlContext ctxt = new MapContext();
724 final String rpt
725 = "<report>\n"
726 + "\n$$var y = 1; var x = 2;"
727 + "\n${x + y}"
728 + "\n</report>\n";
729 final JxltEngine.Template t = Jxlt.createTemplate("$$", new StringReader(rpt));
730 final StringWriter strw = new StringWriter();
731 t.evaluate(ctxt, strw);
732 final String output = strw.toString();
733 final String ctl = "<report>\n\n3\n</report>\n";
734 assertEquals(ctl, output);
735 }
736
737 @Test
738 public void testLexical5() {
739 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
740 final JexlContext ctxt = new DebugContext();
741 Object result;
742 final JexlScript script = jexl.createScript("var x = 42; var y = () -> { {var x = debug(-42); }; return x; }; y()");
743 result = script.execute(ctxt);
744 assertEquals(42, result);
745 }
746
747 @Test
748 public void testLexical6a() {
749 final String str = "i = 0; { var i = 32; }; i";
750 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
751 final JexlScript e = jexl.createScript(str);
752 final JexlContext ctxt = new MapContext();
753 final Object o = e.execute(ctxt);
754 assertEquals(0, o);
755 }
756
757 @Test
758 public void testLexical6a1() {
759 final String str = "i = 0; { var i = 32; }; i";
760 final JexlFeatures f = new JexlFeatures();
761 f.lexical(true);
762 final JexlEngine jexl = createEngine(f);
763 final JexlScript e = jexl.createScript(str);
764 final JexlContext ctxt = new MapContext();
765 final Object o = e.execute(ctxt);
766 assertEquals(0, o);
767 }
768
769 @Test
770 public void testLexical6b() {
771 final String str = "i = 0; { var i = 32; }; i";
772 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
773 final JexlScript e = jexl.createScript(str);
774 final JexlContext ctxt = new MapContext();
775 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
776 assertNotNull(xany.toString());
777 }
778
779 @Test
780 public void testLexical6c() {
781 final String str = "i = 0; for (var i : [42]) i; i";
782 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(false).create();
783 final JexlScript e = jexl.createScript(str);
784 final JexlContext ctxt = new MapContext();
785 final Object o = e.execute(ctxt);
786 assertEquals(0, o);
787 }
788
789 @Test
790 public void testLexical6d() {
791 final String str = "i = 0; for (var i : [42]) i; i";
792 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
793 final JexlScript e = jexl.createScript(str);
794 final JexlContext ctxt = new MapContext();
795 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
796 assertNotNull(xany.toString());
797 }
798
799 @Test public void testManyConst() {
800 final String text = "const x = 1, y = 41; x + y";
801 final JexlEngine jexl = new JexlBuilder().safe(true).create();
802 final JexlScript script = jexl.createScript(text);
803 final Object result = script.execute(null);
804 assertEquals(42, result);
805 final String s0 = script.getParsedText();
806 final String s1 = script.getSourceText();
807 assertNotEquals(s0, s1);
808 }
809
810 @Test public void testManyLet() {
811 final String text = "let x = 1, y = 41, z; x + y";
812 final JexlEngine jexl = new JexlBuilder().safe(true).create();
813 final JexlScript script = jexl.createScript(text);
814 final Object result = script.execute(null);
815 assertEquals(42, result);
816 final String s0 = script.getParsedText();
817 final String s1 = script.getSourceText();
818 assertNotEquals(s0, s1);
819 }
820
821 @Test
822 public void testNamed() {
823 final JexlFeatures f = new JexlFeatures();
824 f.lexical(true);
825 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
826 final JexlScript script = jexl.createScript("var i = (x, y, z)->{return x + y + z}; i(22,18,2)");
827 final JexlContext jc = new MapContext();
828 final Object result = script.execute(jc);
829 assertEquals(42, result);
830 }
831
832 @Test
833 public void testOptionsPragma() {
834 try {
835 JexlOptions.setDefaultFlags("+safe", "-lexical", "-lexicalShade");
836 final VarContext vars = new VarContext();
837 final JexlEngine jexl = new JexlBuilder().create();
838 int n42;
839 JexlOptions o;
840
841 n42 = (Integer) jexl.createScript("#pragma jexl.options none\n-42").execute(vars);
842 assertEquals(-42, n42);
843 o = vars.snatchOptions();
844 assertNotNull(o);
845 assertTrue(o.isStrict());
846 assertTrue(o.isSafe());
847 assertTrue(o.isCancellable());
848 assertFalse(o.isLexical());
849 assertFalse(o.isLexicalShade());
850
851 n42 = (Integer) jexl.createScript("#pragma jexl.options canonical\n42").execute(vars);
852 assertEquals(42, n42);
853 o = vars.snatchOptions();
854 assertNotNull(o);
855 assertTrue(o.isStrict());
856 assertFalse(o.isSafe());
857 assertTrue(o.isCancellable());
858 assertTrue(o.isLexical());
859 assertTrue(o.isLexicalShade());
860 assertFalse(o.isSharedInstance());
861 } finally {
862 JexlOptions.setDefaultFlags("-safe", "+lexical");
863 }
864 }
865
866 @Test
867 public void testParameter0() {
868 final String str = "function(u) {}";
869 final JexlEngine jexl = new JexlBuilder().create();
870 JexlScript e = jexl.createScript(str);
871 assertEquals(1, e.getParameters().length);
872 e = jexl.createScript(new JexlInfo("TestScript", 1, 1), str);
873 assertEquals(1, e.getParameters().length);
874 }
875
876 @Test
877 public void testParameter1() {
878 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
879 final JexlContext jc = new MapContext();
880 final String strs = "var s = function(x) { for (var i : 1..3) {if (i > 2) return x}}; s(42)";
881 final JexlScript s42 = jexl.createScript(strs);
882 final Object result = s42.execute(jc);
883 assertEquals(42, result);
884 }
885
886 @Test
887 public void testPragmaNoop() {
888
889 final String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i";
890 final JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create();
891 final JexlScript e = jexl.createScript(str);
892 final JexlContext ctxt = new MapContext();
893 final Object result = e.execute(ctxt);
894 assertEquals(42, result);
895 }
896
897 @Test
898 public void testPragmaOptions() {
899
900 final String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n"
901 + "i = 0; for (var i : [42]) i; i";
902 final JexlEngine jexl = new JexlBuilder().strict(false).create();
903 final JexlScript e = jexl.createScript(str);
904 final JexlContext ctxt = new MapContext();
905 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
906 assertNotNull(xany.toString());
907 }
908
909 @Test
910 public void testScopeFrame() {
911 final LexicalScope scope = new LexicalScope();
912 runTestScope(scope, 0, 128, 2);
913 runTestScope(scope, 33, 55, 1);
914 runTestScope(scope, 15, 99, 3);
915 runTestScope(scope, 3, 123, 5);
916 }
917
918 @Test
919 public void testSingleStatementDeclFail() {
920 final List<String> srcs = Arrays.asList(
921 "if (true) let x ;",
922 "if (true) let x = 1;",
923 "if (true) var x = 1;",
924 "if (true) { 1 } else let x ;",
925 "if (true) { 1 } else let x = 1;",
926 "if (true) { 1 } else var x = 1;",
927 "while (true) let x ;",
928 "while (true) let x = 1;",
929 "while (true) var x = 1;",
930 "do let x ; while (true)",
931 "do let x = 1; while (true)",
932 "do var x = 1; while (true)",
933 "for (let i:ii) let x ;",
934 "for (let i:ii) let x = 1;",
935 "for (let i:ii) var x = 1;",
936 ""
937 );
938 final JexlFeatures f= new JexlFeatures();
939 f.lexical(true).lexicalShade(true);
940 checkParse(f, srcs, false);
941 }
942
943 @Test
944 public void testSingleStatementVarSucceed() {
945 final List<String> srcs = Arrays.asList(
946 "if (true) var x = 1;",
947 "if (true) { 1 } else var x = 1;",
948 "while (true) var x = 1;",
949 "do var x = 1 while (true)",
950 "for (let i:ii) var x = 1;",
951 ""
952 );
953 checkParse(srcs, true);
954 }
955
956 @Test
957 public void testUndeclaredVariable() {
958 final JexlFeatures f = new JexlFeatures();
959 f.lexical(true);
960 f.lexicalShade(true);
961 final JexlEngine jexl = createEngine(f);
962 assertThrows(JexlException.class, () -> jexl.createScript("{var x = 0}; return x"), "Should not have been parsed");
963 }
964
965 @Test
966 public void testVarFail() {
967 final List<String> srcs = Arrays.asList(
968 "var x = 0; var x = 1;",
969 "var x = 0; let x = 1;",
970 "let x = 0; var x = 1;",
971 "var x = 0; const f = (var x) -> { let x = 1; } f()",
972 "var x = 0; const f = (let x) -> { var x = 1; } f()",
973 "var x = 0; const f = (var x) -> { var x = 1; } f()",
974 ""
975 );
976 final JexlFeatures f= new JexlFeatures();
977 f.lexical(true).lexicalShade(true);
978 checkParse(f, srcs, false);
979 }
980
981 @Test
982 public void testVarLoop0() {
983 final String src0 = "var count = 10;\n"
984 + "for (var i : 0 .. count-1) {\n"
985 + " $out.add(i);\n"
986 + "}";
987 final String src1 = "var count = [0,1,2,3,4,5,6,7,8,9];\n"
988 + "for (var i : count) {\n"
989 + " $out.add(i);\n"
990 + "}";
991 final String src2 = "var count = 10;\n" +
992 " var outer = 0;\n"
993 + "for (var i : 0 .. count-1) {\n"
994 + " $out.add(i);\n"
995 + " outer = i;"
996 + "}\n"
997 + "outer == 9";
998 final JexlFeatures ff0 = runVarLoop(false, src0);
999 final JexlFeatures ft0= runVarLoop(true, src0);
1000 final JexlFeatures ff1 = runVarLoop(false, src1);
1001 final JexlFeatures ft1= runVarLoop(true, src1);
1002 final JexlFeatures ff2 = runVarLoop(false, src2);
1003 final JexlFeatures ft2= runVarLoop(true, src2);
1004
1005
1006 assertEquals(ff0, ff1);
1007 assertEquals(ft0, ft1);
1008 assertNotEquals(ff0, ft0);
1009 final String sff0 = ff0.toString();
1010 final String sff1 = ff1.toString();
1011 assertEquals(sff0, sff1);
1012 final String sft1 = ft1.toString();
1013 assertNotEquals(sff0, sft1);
1014 }
1015 }