View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.util.function.Supplier;
27  
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Checks various exception handling cases.
32   */
33  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
34  class ExceptionTest extends JexlTestCase {
35      public static class ThrowNPE {
36          boolean doThrow;
37          public boolean getFail() {
38              if (doThrow) {
39                  throw new NullPointerException("ThrowNPE/get");
40              }
41              return doThrow;
42          }
43  
44          public String npe() {
45              throw new NullPointerException("ThrowNPE");
46          }
47  
48          public void setFail(final boolean f) {
49              doThrow = f;
50              if (f) {
51                  throw new NullPointerException("ThrowNPE/set");
52              }
53          }
54      }
55  
56      public static class ConstructNPE {
57          public ConstructNPE() {
58              throw new NullPointerException("ConstructNPE");
59          }
60      }
61  
62      /** Create a named test */
63      public ExceptionTest() {
64          super("ExceptionTest");
65      }
66  
67      private void doTest206(final String src, final boolean strict, final boolean silent) {
68          final CaptureLog l = new CaptureLog();
69          final JexlContext jc = new MapContext();
70          final JexlEngine jexl = new JexlBuilder().logger(l).strict(strict).silent(silent).create();
71          JexlScript e;
72          Object r = -1;
73          e = jexl.createScript(src);
74          try {
75              r = e.execute(jc);
76              if (strict && !silent) {
77                  fail("should have thrown an exception");
78              }
79          } catch (final JexlException xjexl) {
80              if (!strict || silent) {
81                  fail(src + ": should not have thrown an exception");
82              }
83          }
84          if (strict) {
85              if (silent && l.count("warn") == 0) {
86                  fail(src + ": should have generated a warning");
87              }
88          } else {
89              if (l.count("debug") == 0) {
90                  fail(src + ": should have generated a debug");
91              }
92              assertEquals(42, r);
93          }
94      }
95  
96      @Test
97      void test206() throws Exception {
98          String src = "null.1 = 2; return 42";
99          doTest206(src, false, false);
100         doTest206(src, false, true);
101         doTest206(src, true, false);
102         doTest206(src, true, true);
103         src = "x = null.1; return 42";
104         doTest206(src, false, false);
105         doTest206(src, false, true);
106         doTest206(src, true, false);
107         doTest206(src, true, true);
108         src = "x = y.1; return 42";
109         doTest206(src, false, false);
110         doTest206(src, false, true);
111         doTest206(src, true, false);
112         doTest206(src, true, true);
113     }
114 
115     // Unknown vars and properties versus null operands
116     // JEXL-73
117     @Test
118     void testEx() {
119         final JexlEngine jexl = createEngine(false);
120         final JexlExpression e = jexl.createExpression("c.e * 6");
121         final JexlEvalContext ctxt = new JexlEvalContext();
122         final JexlOptions options = ctxt.getEngineOptions();
123         // ensure errors will throw
124         options.setSilent(false);
125         // make unknown vars throw
126         options.setStrict(true);
127         // empty cotext
128         JexlException.Variable xjexl = assertThrows(JexlException.Variable.class, () -> e.evaluate(ctxt), "c not defined as variable should throw");
129         String msg = xjexl.getMessage();
130         assertTrue(msg.indexOf("variable 'c.e'") > 0);
131 
132         // disallow null operands
133         options.setStrictArithmetic(true);
134         ctxt.set("c.e", null);
135         xjexl = assertThrows(JexlException.Variable.class, () -> e.evaluate(ctxt), "c.e as null operand should throw");
136         msg = xjexl.getMessage();
137         assertTrue(msg.indexOf("variable 'c.e'") > 0);
138 
139         // allow null operands
140         options.setStrictArithmetic(false);
141         /* Object o = */ e.evaluate(ctxt);
142 
143         // ensure c.e is not a defined property
144         ctxt.set("c", "{ 'a' : 3, 'b' : 5}");
145         ctxt.set("e", Integer.valueOf(2));
146         final JexlException.Property ep = assertThrows(JexlException.Property.class, () -> e.evaluate(ctxt));
147         msg = ep.getMessage();
148         assertTrue(msg.indexOf("property 'e") > 0);
149     }
150 
151     // Unknown vars and properties versus null operands
152     @Test
153     void testExMethod() {
154         final JexlEngine jexl = createEngine(false);
155         final JexlExpression e = jexl.createExpression("c.e.foo()");
156         final JexlEvalContext ctxt = new JexlEvalContext();
157         final JexlOptions options = ctxt.getEngineOptions();
158         // ensure errors will throw
159         options.setSilent(false);
160         // make unknown vars throw
161         options.setStrict(true);
162         // empty cotext
163         JexlException xjexl = assertThrows(JexlException.class, () -> e.evaluate(ctxt), "c not declared as variable should throw");
164         String msg = xjexl.getMessage();
165         assertTrue(msg.indexOf("variable 'c.e'") > 0);
166 
167         // disallow null operands
168         options.setStrictArithmetic(true);
169         ctxt.set("c.e", null);
170         xjexl = assertThrows(JexlException.class, () -> e.evaluate(ctxt));
171         msg = xjexl.getMessage();
172         assertTrue(msg.indexOf("variable 'c.e'") > 0);
173     }
174 
175     // null local vars and strict arithmetic effects
176     @Test
177     void testExVar() {
178         final JexlEngine jexl = createEngine(false);
179         final JexlScript e = jexl.createScript("(x)->{ x * 6 }");
180         final JexlEvalContext ctxt = new JexlEvalContext();
181         final JexlOptions options = ctxt.getEngineOptions();
182         // ensure errors will throw
183         options.setSilent(false);
184         // make unknown vars throw
185         options.setStrict(true);
186         options.setStrictArithmetic(true);
187         // empty cotext
188         final JexlException xjexl = assertThrows(JexlException.class, () -> e.execute(ctxt));
189         final String msg = xjexl.getMessage();
190         assertTrue(msg.indexOf("null") > 0);
191 
192         // allow null operands
193         options.setStrictArithmetic(false);
194         assertEquals(0, e.execute(ctxt, (Object) null));
195     }
196 
197     @Test
198     void testWrappedEx() {
199         runWrappedEx(true, true);
200         runWrappedEx(false, true);
201         runWrappedEx(true, false);
202         runWrappedEx(false, false);
203     }
204 
205     /**
206      * Runs various calls that throw NPE wrapped in JexlExceptions.
207      * @param debug if true/false, debug mode is on/off
208      * @param silent if true/false, silent mode is on/off
209      */
210     private static void runWrappedEx(boolean debug, boolean silent) {
211         final CaptureLog log = new CaptureLog();
212         final JexlBuilder builder = new JexlBuilder().safe(false).strict(true)
213                 .logger(log).debug(debug).silent(silent);
214         final JexlEngine jexl = builder.create();
215         final JexlContext jc = new ObjectContext<>(jexl, new ThrowNPE());
216         callWrappedEx(debug, silent, log, () -> jexl.createExpression("npe()").evaluate(jc));
217         callWrappedEx(debug, silent, log, () -> jexl.newInstance(ConstructNPE.class));
218         ThrowNPE npe = new ThrowNPE();
219         callWrappedEx(debug, silent, log, () -> jexl.invokeMethod(npe, "npe"));
220         // side effect to set failure in getter coming next
221         callWrappedEx(debug, silent, log, () -> {
222             jexl.setProperty(npe, "fail", true);
223             return null;
224         });
225         callWrappedEx(debug, silent, log, () -> jexl.getProperty(npe, "fail"));
226     }
227 
228     /**
229      * Calls a NPE throwing call wrapped in JexlException and checks the outcome.
230      * @param debug if true/false, debug mode is on/off
231      * @param silent if true/false, silent mode is on/off
232      * @param npeCall the NPE throwing call
233      */
234     private static void callWrappedEx(boolean debug, boolean silent, CaptureLog log, Supplier<Object> npeCall) {
235         try {
236             npeCall.get();
237             if (!silent) {
238                 fail("should have thrown");
239             } else {
240                 // in silent mode, ensure a log was captured
241                 assertEquals(1, log.count("warn"), "should have 1 warn log");
242                 String msg = log.getCapturedMessages().get(0);
243                 assertEquals(debug, msg.contains("runWrappedEx"), "class/method/line?");
244                 log.clear();
245             }
246         } catch (final JexlException exception) {
247             if (silent) {
248                 fail("should not have throw, should be silent");
249             }
250             assertEquals(debug, exception.getMessage().contains("runWrappedEx"), "class/method/line?");
251             final Throwable xth = exception.getCause();
252             assertEquals(NullPointerException.class, xth.getClass(), "Should have thrown NPE");
253         }
254     }
255 
256     @Test
257     void testWrappedExmore() {
258         final JexlEngine jexl = new JexlBuilder().debug(true).safe(false).create();
259         final ThrowNPE npe = new ThrowNPE();
260         assertNull(assertThrows(JexlException.Property.class, () -> jexl.getProperty(npe, "foo")).getCause());
261         assertNull(assertThrows(JexlException.Property.class, () -> jexl.setProperty(npe, "foo", 42)).getCause());
262 
263         final boolean b = (Boolean) jexl.getProperty(npe, "fail");
264         assertFalse(b);
265         jexl.setProperty(npe, "fail", false);
266         assertEquals(NullPointerException.class, assertThrows(JexlException.Property.class, () -> jexl.setProperty(npe, "fail", true)).getCause().getClass());
267         assertEquals(NullPointerException.class, assertThrows(JexlException.Property.class, () -> jexl.getProperty(npe, "fail")).getCause().getClass());
268         assertNull(assertThrows(JexlException.Method.class, () -> jexl.invokeMethod(npe, "foo", 42)).getCause());
269         assertEquals(NullPointerException.class, assertThrows(JexlException.Method.class, () -> jexl.invokeMethod(npe, "npe")).getCause().getClass());
270     }
271 }