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.introspection;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.jexl3.JexlArithmetic;
25  import org.apache.commons.jexl3.JexlBuilder;
26  import org.apache.commons.jexl3.JexlContext;
27  import org.apache.commons.jexl3.JexlEngine;
28  import org.apache.commons.jexl3.JexlException;
29  import org.apache.commons.jexl3.JexlExpression;
30  import org.apache.commons.jexl3.JexlScript;
31  import org.apache.commons.jexl3.JexlTestCase;
32  import org.apache.commons.jexl3.MapContext;
33  import org.apache.commons.jexl3.annotations.NoJexl;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  import org.junit.Assert;
39  import org.junit.Test;
40  
41  /**
42   * Tests sandbox features.
43   */
44  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
45  public class SandboxTest extends JexlTestCase {
46      static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName());
47  
48      public SandboxTest() {
49          super("SandboxTest");
50      }
51  
52  
53      public static class CantSeeMe {
54          public boolean doIt() {
55              return false;
56          }
57      }
58  
59      @NoJexl
60      public interface CantCallMe {
61          void tryMe();
62      }
63  
64      public interface TryCallMe {
65          @NoJexl
66          void tryMeARiver();
67      }
68  
69      public static abstract class CallMeNot {
70          public @NoJexl
71          String NONO = "should not be accessible!";
72  
73          @NoJexl
74          public void callMeNot() {
75              throw new RuntimeException("should not be callable!");
76          }
77  
78          public String allowInherit() {
79              return "this is allowed";
80          }
81      }
82  
83      public static class Foo extends CallMeNot implements CantCallMe, TryCallMe {
84          String name;
85          public String alias;
86  
87          public @NoJexl
88          Foo(final String name, final String notcallable) {
89              throw new RuntimeException("should not be callable!");
90          }
91  
92          public Foo(final String name) {
93              this.name = name;
94              this.alias = name + "-alias";
95          }
96  
97          public String getName() {
98              return name;
99          }
100 
101         public void setName(final String name) {
102             this.name = name;
103         }
104 
105         public String Quux() {
106             return name + "-quux";
107         }
108 
109         public int doIt() {
110             return 42;
111         }
112 
113         @NoJexl
114         public String cantCallMe() {
115             throw new RuntimeException("should not be callable!");
116         }
117 
118         @Override
119         public void tryMe() {
120             throw new RuntimeException("should not be callable!");
121         }
122 
123         @Override
124         public void tryMeARiver() {
125             throw new RuntimeException("should not be callable!");
126         }
127     }
128 
129     @Test
130     public void testCtorBlock() throws Exception {
131         final String expr = "new('" + Foo.class.getName() + "', '42')";
132         JexlScript script = JEXL.createScript(expr);
133         Object result;
134         result = script.execute(null);
135         Assert.assertEquals("42", ((Foo) result).getName());
136 
137         final JexlSandbox sandbox = new JexlSandbox();
138         sandbox.block(Foo.class.getName()).execute("");
139         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
140 
141         script = sjexl.createScript(expr);
142         try {
143             result = script.execute(null);
144             Assert.fail("ctor should not be accessible");
145         } catch (final JexlException.Method xmethod) {
146             // ok, ctor should not have been accessible
147             LOGGER.debug(xmethod.toString());
148         }
149     }
150 
151     @Test
152     public void testMethodBlock() throws Exception {
153         final String expr = "foo.Quux()";
154         JexlScript script = JEXL.createScript(expr, "foo");
155         final Foo foo = new Foo("42");
156         Object result;
157         result = script.execute(null, foo);
158         Assert.assertEquals(foo.Quux(), result);
159 
160         final JexlSandbox sandbox = new JexlSandbox();
161         sandbox.block(Foo.class.getName()).execute("Quux");
162         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
163 
164         script = sjexl.createScript(expr, "foo");
165         try {
166             result = script.execute(null, foo);
167             Assert.fail("Quux should not be accessible");
168         } catch (final JexlException.Method xmethod) {
169             // ok, Quux should not have been accessible
170             LOGGER.debug(xmethod.toString());
171         }
172     }
173 
174     @Test
175     public void testGetBlock() throws Exception {
176         final String expr = "foo.alias";
177         JexlScript script = JEXL.createScript(expr, "foo");
178         final Foo foo = new Foo("42");
179         Object result;
180         result = script.execute(null, foo);
181         Assert.assertEquals(foo.alias, result);
182 
183         final JexlSandbox sandbox = new JexlSandbox();
184         sandbox.block(Foo.class.getName()).read("alias");
185         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
186 
187         script = sjexl.createScript(expr, "foo");
188         try {
189             result = script.execute(null, foo);
190             Assert.fail("alias should not be accessible");
191         } catch (final JexlException.Property xvar) {
192             // ok, alias should not have been accessible
193             LOGGER.debug(xvar.toString());
194         }
195     }
196 
197     @Test
198     public void testSetBlock() throws Exception {
199         final String expr = "foo.alias = $0";
200         JexlScript script = JEXL.createScript(expr, "foo", "$0");
201         final Foo foo = new Foo("42");
202         Object result;
203         result = script.execute(null, foo, "43");
204         Assert.assertEquals("43", result);
205 
206         final JexlSandbox sandbox = new JexlSandbox();
207         sandbox.block(Foo.class.getName()).write("alias");
208         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
209 
210         script = sjexl.createScript(expr, "foo", "$0");
211         try {
212             result = script.execute(null, foo, "43");
213             Assert.fail("alias should not be accessible");
214         } catch (final JexlException.Property xvar) {
215             // ok, alias should not have been accessible
216             LOGGER.debug(xvar.toString());
217         }
218     }
219 
220     @Test
221     public void testCantSeeMe() throws Exception {
222         final JexlContext jc = new MapContext();
223         final String expr = "foo.doIt()";
224         JexlScript script;
225         Object result;
226 
227         final JexlSandbox sandbox = new JexlSandbox(false);
228         sandbox.allow(Foo.class.getName());
229         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
230 
231         jc.set("foo", new CantSeeMe());
232         script = sjexl.createScript(expr);
233         try {
234             result = script.execute(jc);
235             Assert.fail("should have failed, doIt()");
236         } catch (final JexlException xany) {
237             //
238         }
239         jc.set("foo", new Foo("42"));
240             result = script.execute(jc);
241         Assert.assertEquals(42, ((Integer) result).intValue());
242     }
243 
244     @Test
245     public void testCtorAllow() throws Exception {
246         final String expr = "new('" + Foo.class.getName() + "', '42')";
247         JexlScript script;
248         Object result;
249 
250         final JexlSandbox sandbox = new JexlSandbox();
251         sandbox.allow(Foo.class.getName()).execute("");
252         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
253 
254         script = sjexl.createScript(expr);
255         result = script.execute(null);
256         Assert.assertEquals("42", ((Foo) result).getName());
257     }
258 
259     @Test
260     public void testMethodAllow() throws Exception {
261         final Foo foo = new Foo("42");
262         final String expr = "foo.Quux()";
263         JexlScript script;
264         Object result;
265 
266         final JexlSandbox sandbox = new JexlSandbox();
267         sandbox.allow(Foo.class.getName()).execute("Quux");
268         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
269 
270         script = sjexl.createScript(expr, "foo");
271         result = script.execute(null, foo);
272         Assert.assertEquals(foo.Quux(), result);
273     }
274 
275     @Test
276     public void testMethodNoJexl() throws Exception {
277         final Foo foo = new Foo("42");
278         final String[] exprs = {
279             "foo.cantCallMe()",
280             "foo.tryMe()",
281             "foo.tryMeARiver()",
282             "foo.callMeNot()",
283             "foo.NONO",
284             "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')"
285         };
286         JexlScript script;
287         Object result;
288 
289         final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create();
290         for (final String expr : exprs) {
291             script = sjexl.createScript(expr, "foo");
292             try {
293                 result = script.execute(null, foo);
294                 Assert.fail("should have not been possible");
295             } catch (JexlException.Method | JexlException.Property xjm) {
296                 // ok
297                 LOGGER.debug(xjm.toString());
298             }
299         }
300     }
301 
302     @Test
303     public void testGetAllow() throws Exception {
304         final Foo foo = new Foo("42");
305         final String expr = "foo.alias";
306         JexlScript script;
307         Object result;
308 
309         final JexlSandbox sandbox = new JexlSandbox();
310         sandbox.allow(Foo.class.getName()).read("alias");
311         sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS");
312         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
313 
314         script = sjexl.createScript(expr, "foo");
315         result = script.execute(null, foo);
316         Assert.assertEquals(foo.alias, result);
317 
318         script = sjexl.createScript("foo.ALIAS", "foo");
319         result = script.execute(null, foo);
320         Assert.assertEquals(foo.alias, result);
321     }
322 
323     @Test
324     public void testSetAllow() throws Exception {
325         final Foo foo = new Foo("42");
326         final String expr = "foo.alias = $0";
327         JexlScript script;
328         Object result;
329 
330         final JexlSandbox sandbox = new JexlSandbox();
331         sandbox.allow(Foo.class.getName()).write("alias");
332         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
333 
334         script = sjexl.createScript(expr, "foo", "$0");
335         result = script.execute(null, foo, "43");
336         Assert.assertEquals("43", result);
337         Assert.assertEquals("43", foo.alias);
338     }
339 
340     @Test
341     public void testRestrict() throws Exception {
342         final JexlContext context = new MapContext();
343         context.set("System", System.class);
344         final JexlSandbox sandbox = new JexlSandbox();
345         // only allow call to currentTimeMillis (avoid exit, gc, loadLibrary, etc)
346         sandbox.allow(System.class.getName()).execute("currentTimeMillis");
347         // can not create a new file
348         sandbox.block(java.io.File.class.getName()).execute("");
349 
350         final JexlEngine sjexl = new JexlBuilder()
351                 .permissions(JexlPermissions.UNRESTRICTED)
352                 .sandbox(sandbox)
353                 .safe(false)
354                 .strict(true)
355                 .create();
356 
357         String expr;
358         JexlScript script;
359         Object result;
360 
361         script = sjexl.createScript("System.exit()");
362         try {
363             result = script.execute(context);
364             Assert.fail("should not allow calling exit!");
365         } catch (final JexlException xjexl) {
366             LOGGER.debug(xjexl.toString());
367         }
368 
369         script = sjexl.createScript("System.exit(1)");
370         try {
371             result = script.execute(context);
372             Assert.fail("should not allow calling exit!");
373         } catch (final JexlException xjexl) {
374             LOGGER.debug(xjexl.toString());
375         }
376 
377         script = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
378         try {
379             result = script.execute(context);
380             Assert.fail("should not allow creating a file");
381         } catch (final JexlException xjexl) {
382             LOGGER.debug(xjexl.toString());
383         }
384 
385         expr = "System.currentTimeMillis()";
386         script = sjexl.createScript("System.currentTimeMillis()");
387         result = script.execute(context);
388         Assert.assertNotNull(result);
389     }
390 
391     @Test
392        public void testSandboxInherit0() throws Exception {
393         Object result;
394         final JexlContext ctxt = null;
395         final List<String> foo = new ArrayList<>();
396         final JexlSandbox sandbox = new JexlSandbox(false, true);
397         sandbox.allow(java.util.List.class.getName());
398 
399         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
400         final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y");
401         final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y");
402         final JexlScript get = sjexl.createScript("foo[x]", "foo", "x");
403 
404         result = method.execute(ctxt, foo, "nothing");
405         Assert.assertEquals(true, result);
406         result = null;
407         result = get.execute(null, foo, 0);
408         Assert.assertEquals("nothing", result);
409         result = null;
410         result = set.execute(null, foo, 0, "42");
411         Assert.assertEquals("42", result);
412 
413         result = null;
414         result = get.execute(null, foo, 0);
415         Assert.assertEquals("42", result);
416     }
417 
418     public abstract static class Operation {
419         protected final int base;
420         public Operation(final int sz) {
421          base = sz;
422         }
423 
424         public abstract int someOp(int x);
425         public abstract int nonCallable(int y);
426     }
427 
428     public static class Operation2 extends Operation {
429         public Operation2(final int sz) {
430             super(sz);
431         }
432 
433         @Override
434         public int someOp(final int x) {
435             return base + x;
436         }
437 
438         @Override
439         public int nonCallable(final int y) {
440             throw new UnsupportedOperationException("do NOT call");
441         }
442     }
443 
444     @Test
445     public void testSandboxInherit1() throws Exception {
446         Object result;
447         final JexlContext ctxt = null;
448         final Operation2 foo = new Operation2(12);
449         final JexlSandbox sandbox = new JexlSandbox(false, true);
450         sandbox.allow(Operation.class.getName());
451         sandbox.block(Operation.class.getName()).execute("nonCallable");
452         //sandbox.block(Foo.class.getName()).execute();
453         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
454         final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y");
455         result = someOp.execute(ctxt, foo, 30);
456         Assert.assertEquals(42, result);
457         final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y");
458         try {
459             result = nonCallable.execute(null, foo, 0);
460             Assert.fail("should not be possible");
461         } catch (final JexlException xjm) {
462             // ok
463             LOGGER.debug(xjm.toString());
464         }
465     }
466     public interface SomeInterface {
467         int bar();
468     }
469 
470     public static class Foo386 implements SomeInterface {
471         @Override
472         public int bar() {
473             return 42;
474         }
475     }
476     public static class Quux386 extends Foo386 {
477         @Override
478         public int bar() {
479             return -42;
480         }
481     }
482     @Test
483     public void testInheritedPermission0() {
484         final Foo386 foo = new Foo386();
485         final JexlSandbox sandbox = new JexlSandbox(false, true);
486         sandbox.permissions(SomeInterface.class.getName(), true, true, true, true);
487         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
488         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
489         Assert.assertEquals(42, someOp.execute(null, foo));
490     }
491 
492     @Test
493     public void testNonInheritedPermission0() {
494         final Foo386 foo = new Foo386();
495         final JexlSandbox sandbox = new JexlSandbox(false, true);
496         sandbox.permissions(SomeInterface.class.getName(), false, true, true, true);
497         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
498         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
499 
500         try {
501             someOp.execute(null, foo);
502             Assert.fail("should not be possible");
503         } catch (final JexlException e) {
504             // ok
505             LOGGER.debug(e.toString());
506         }
507     }
508     @Test
509     public void testInheritedPermission1() {
510         final Quux386 foo = new Quux386();
511         final JexlSandbox sandbox = new JexlSandbox(false, true);
512         sandbox.permissions(Foo386.class.getName(), true, true, true, true);
513         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
514         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
515         Assert.assertEquals(-42, someOp.execute(null, foo));
516     }
517     @Test
518     public void testNonInheritedPermission1() {
519         final Quux386 foo = new Quux386();
520         final JexlSandbox sandbox = new JexlSandbox(false, true);
521         sandbox.permissions(Foo386.class.getName(), false, true, true, true);
522         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
523         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
524 
525         try {
526             someOp.execute(null, foo);
527             Assert.fail("should not be possible");
528         } catch (final JexlException e) {
529             // ok
530             LOGGER.debug(e.toString());
531         }
532     }
533     public static class Foo42 {
534         public int getFoo() {
535             return 42;
536         }
537     }
538 
539     public static class Foo43 extends Foo42 {
540         @Override
541         @NoJexl
542         public int getFoo() {
543             return 43;
544         }
545     }
546 
547     public static class Foo44 extends Foo43 {
548         @Override
549         public int getFoo() {
550             return 44;
551         }
552     }
553 
554     @Test
555     public void testNoJexl312() throws Exception {
556         final JexlContext ctxt = new MapContext();
557 
558         final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create();
559         final JexlScript foo = sjexl.createScript("x.getFoo()", "x");
560         try {
561             foo.execute(ctxt, new Foo44());
562             Assert.fail("should have thrown");
563         } catch (final JexlException xany) {
564             Assert.assertNotNull(xany);
565         }
566     }
567 
568     @Test
569     public void testGetNullKeyAllowed0() throws Exception {
570         JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
571         JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
572         Object o = expression.evaluate(null);
573         Assert.assertEquals("foo", o);
574     }
575 
576     @Test
577     public void testGetNullKeyAllowed1() throws Exception {
578         JexlSandbox sandbox = new JexlSandbox(true, true);
579         JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
580         p.read().add("quux");
581         JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
582         // cant read quux
583         String q = "'quux'"; //quotes are important!
584         JexlExpression expression = jexl.createExpression("{"+q+" : 'foo'}["+q+"]");
585         try {
586             Object o = expression.evaluate(null);
587             Assert.fail("should have blocked " + q);
588         } catch (JexlException.Property xp) {
589             Assert.assertTrue(xp.getMessage().contains("undefined"));
590         }
591         // can read foo, null
592         for(String k : Arrays.asList("'foo'", "null")) {
593             expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"]");
594             Object o = expression.evaluate(null);
595             Assert.assertEquals("foo", o);
596         }
597     }
598 
599     @Test
600     public void testGetNullKeyBlocked() throws Exception {
601         JexlSandbox sandbox = new JexlSandbox(true, true);
602         JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
603         p.read().add(null);
604         p.read().add("quux");
605         // can read bar
606         JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
607         JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
608         Object r0 = e0.evaluate(null);
609         Assert.assertEquals("foo", r0);
610         // can not read quux, null
611         for(String k : Arrays.asList("'quux'", "null")) {
612             JexlExpression expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"]");
613             try {
614                 Object o = expression.evaluate(null);
615                 Assert.fail("should have blocked " + k);
616             } catch (JexlException.Property xp) {
617                 Assert.assertTrue(xp.getMessage().contains("undefined"));
618             }
619         }
620     }
621 
622     public static class Arithmetic350 extends JexlArithmetic {
623         // cheat and keep the map builder around
624         MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
625         public Arithmetic350(boolean astrict) {
626             super(astrict);
627         }
628         @Override
629         public MapBuilder mapBuilder(final int size) {
630             return mb;
631         }
632         Map<?,?> getLastMap() {
633             return (Map<Object,Object>) mb.create();
634         }
635     }
636 
637     @Test
638     public void testSetNullKeyAllowed0() throws Exception {
639         Arithmetic350 a350 = new Arithmetic350(true);
640         JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
641         JexlContext jc = new MapContext();
642         JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
643         expression.evaluate(jc);
644         Map<?,?> map = a350.getLastMap();
645         Assert.assertEquals("bar", map.get(null));
646     }
647 
648     @Test
649     public void testSetNullKeyAllowed1() throws Exception {
650         Arithmetic350 a350 = new Arithmetic350(true);
651         JexlSandbox sandbox = new JexlSandbox(true, true);
652         JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
653         p.write().add("quux");
654         JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
655         // can not write quux
656         String q = "'quux'"; //quotes are important!
657         JexlExpression expression = jexl.createExpression("{"+q+" : 'foo'}["+q+"] = '42'");
658         try {
659             Object o = expression.evaluate(null);
660             Assert.fail("should have blocked " + q);
661         } catch (JexlException.Property xp) {
662             Assert.assertTrue(xp.getMessage().contains("undefined"));
663         }
664         // can write bar, null
665         expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
666         expression.evaluate(null);
667         Map<?, ?> map = a350.getLastMap();
668         Assert.assertEquals("42", map.get("bar"));
669         map.clear();
670         expression = jexl.createExpression("{null : 'foo'}[null] = '42'");
671         expression.evaluate(null);
672         map = a350.getLastMap();
673         Assert.assertEquals("42", map.get(null));
674     }
675 
676     @Test
677     public void testSetNullKeyBlocked() throws Exception {
678         Arithmetic350 a350 = new Arithmetic350(true);
679         JexlSandbox sandbox = new JexlSandbox(true, true);
680         JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
681         p.write().add(null);
682         p.write().add("quux");
683         JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
684         // can write bar
685         JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
686         expression.evaluate(null);
687         Map<?,?> map = a350.getLastMap();
688         Assert.assertEquals("42", map.get("bar"));
689         // can not write quux, null
690         for(String k : Arrays.asList("'quux'", "null")) {
691             expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"] = '42'");
692             try {
693                 Object o = expression.evaluate(null);
694                 Assert.fail("should have blocked " + k);
695             } catch (JexlException.Property xp) {
696                 Assert.assertTrue(xp.getMessage().contains("undefined"));
697             }
698         }
699     }
700 }