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.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import org.apache.commons.jexl3.internal.Engine;
30  import org.apache.commons.jexl3.internal.introspection.Uberspect;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  
34  /**
35   * Test cases for reported issue between JEXL-1 and JEXL-100.
36   */
37  @SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
38  class IssuesTest extends JexlTestCase {
39      // JEXL-48: bad assignment detection
40      public static class Another {
41          public String name = "whatever";
42          private final Boolean foo = Boolean.TRUE;
43  
44          public Boolean foo() {
45              return foo;
46          }
47  
48          public int goo() {
49              return 100;
50          }
51      }
52  
53      // JEXL-40: failed to discover all methods (non public class implements public method)
54      // fixed in ClassMap by taking newer version of populateCache from Velocity
55      public abstract static class Base {
56          public abstract boolean foo();
57      }
58  
59      static class Derived extends Base {
60          @Override
61          public boolean foo() {
62              return true;
63          }
64      }
65  
66      public static class fn98 {
67          public String replace(final String str, final String target, final String replacement) {
68              return str.replace(target, replacement);
69          }
70      }
71  
72      public static class Foo {
73          private final Another inner;
74  
75          Foo() {
76              inner = new Another();
77          }
78  
79          public Another getInner() {
80              return inner;
81          }
82      }
83  
84      public IssuesTest() {
85          super("IssuesTest", null);
86      }
87  
88      @BeforeEach
89      @Override
90      public void setUp() throws Exception {
91          // ensure jul logging is only error to avoid warning in silent mode
92          java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
93      }
94  
95      // JEXL-10/JEXL-11: variable checking, null operand is error
96      @Test
97      void test11() throws Exception {
98          final JexlEngine jexl = createEngine(false);
99          final JexlEvalContext ctxt = new JexlEvalContext();
100         final JexlOptions options = ctxt.getEngineOptions();
101         // ensure errors will throw
102         options.setSilent(false);
103         options.setStrict(true);
104 
105         ctxt.set("a", null);
106 
107         final String[] exprs = {
108             //"10 + null",
109             //"a - 10",
110             //"b * 10",
111             "a % b"//,
112         //"1000 / a"
113         };
114         for (final String s : exprs) {
115             final JexlExpression expr = jexl.createExpression(s);
116             assertThrows(JexlException.class, () -> expr.evaluate(ctxt), () -> s + " : should have failed due to null argument");
117         }
118     }
119 
120     @Test
121     void test40() throws Exception {
122         final JexlEngine jexl = new Engine();
123         final JexlEvalContext ctxt = new JexlEvalContext();
124         final JexlOptions options = ctxt.getEngineOptions();
125         options.set(jexl);
126         // ensure errors will throw
127         options.setSilent(false);
128 
129         ctxt.set("derived", new Derived());
130 
131         final JexlExpression expr = jexl.createExpression("derived.foo()");
132         final Object value = expr.evaluate(ctxt);
133         assertTrue((Boolean) value);
134     }
135 
136     // JEXL-42: NullPointerException evaluating an expression
137     // fixed in JexlArithmetic by allowing add operator to deal with string, null
138     @Test
139     void test42() throws Exception {
140         final JexlEngine jexl = new JexlBuilder().create();
141         final JxltEngine uel = jexl.createJxltEngine();
142         // ensure errors will throw
143         //jexl.setSilent(false);
144         final JexlEvalContext ctxt = new JexlEvalContext();
145         final JexlOptions options = ctxt.getEngineOptions();
146         options.set(jexl);
147         options.setStrict(false);
148         options.setStrictArithmetic(false);
149         ctxt.set("ax", "ok");
150 
151         final JxltEngine.Expression expr = uel.createExpression("${ax+(bx)}");
152         final Object value = expr.evaluate(ctxt);
153         assertEquals("ok", value);
154     }
155 
156     // JEXL-44
157     @Test
158     void test44() throws Exception {
159         final JexlEngine jexl = createEngine(false);
160         final JexlEvalContext ctxt = new JexlEvalContext();
161         final JexlOptions options = ctxt.getEngineOptions();
162         // ensure errors will throw
163         options.setSilent(false);
164         JexlScript script;
165         script = jexl.createScript("'hello world!'//commented");
166         assertEquals("hello world!", script.execute(ctxt));
167         script = jexl.createScript("'hello world!'; //commented\n'bye...'");
168         assertEquals("bye...", script.execute(ctxt));
169         script = jexl.createScript("'hello world!'## commented");
170         assertEquals("hello world!", script.execute(ctxt));
171         script = jexl.createScript("'hello world!';## commented\n'bye...'");
172         assertEquals("bye...", script.execute(ctxt));
173     }
174 
175     // JEXL-47: C style comments (single & multi line) (fixed in Parser.jjt)
176     // JEXL-44: comments don't allow double quotes (fixed in Parser.jjt)
177     @Test
178     void test47() throws Exception {
179         final JexlEngine jexl = new Engine();
180         final JexlEvalContext ctxt = new JexlEvalContext();
181         final JexlOptions options = ctxt.getEngineOptions();
182         // ensure errors will throw
183         options.setSilent(false);
184 
185         JexlExpression expr = jexl.createExpression("true//false\n");
186         Object value = expr.evaluate(ctxt);
187         assertTrue((Boolean) value);
188 
189         expr = jexl.createExpression("/*true*/false");
190         value = expr.evaluate(ctxt);
191         assertFalse((Boolean) value);
192 
193         expr = jexl.createExpression("/*\"true\"*/false");
194         value = expr.evaluate(ctxt);
195         assertFalse((Boolean) value);
196     }
197 
198     @Test
199     void test48() throws Exception {
200         final JexlEngine jexl = new Engine();
201         final JexlEvalContext jc = new JexlEvalContext();
202         final JexlOptions options = jc.getEngineOptions();
203         // ensure errors will throw
204         options.setStrict(true);
205         options.setSilent(false);
206         final String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
207         jc.set("foo", new Foo());
208         assertNotNull(assertThrows(JexlException.Assignment.class, () -> jexl.createExpression(jexlExp)).toString());
209     }
210 
211     // JEXL-49: blocks not parsed (fixed)
212     @Test
213     void test49() throws Exception {
214         final JexlEngine jexl = new Engine();
215         final Map<String, Object> vars = new HashMap<>();
216         final JexlContext ctxt = new MapContext(vars);
217         final String stmt = "a = 'b'; c = 'd';";
218         final JexlScript expr = jexl.createScript(stmt);
219         /* Object value = */ expr.execute(ctxt);
220         assertTrue(vars.get("a").equals("b") && vars.get("c").equals("d"), "JEXL-49 is not fixed");
221     }
222 
223     // JEXL-52: can be implemented by deriving Interpreter.{g,s}etAttribute; later
224     @Test
225     void test52base() throws Exception {
226         final Engine jexl = (Engine) createEngine(false);
227         final Uberspect uber = (Uberspect) jexl.getUberspect();
228         // most likely, call will be in an Interpreter, getUberspect
229         String[] names = uber.getMethodNames(Another.class);
230         assertTrue(names.length > 0, "should find methods");
231         int found = 0;
232         for (final String name : names) {
233             if ("foo".equals(name) || "goo".equals(name)) {
234                 found += 1;
235             }
236         }
237         assertEquals(2, found, "should have foo & goo");
238 
239         names = uber.getFieldNames(Another.class);
240         assertTrue(names.length > 0, "should find fields");
241         found = 0;
242         for (final String name : names) {
243             if ("name".equals(name)) {
244                 found += 1;
245             }
246         }
247         assertEquals(1, found, "should have name");
248     }
249 
250     // JEXL-62
251     @Test
252     void test62() throws Exception {
253         final JexlEngine jexl = createEngine(false);
254         final MapContext vars = new MapContext();
255         final JexlEvalContext ctxt = new JexlEvalContext(vars);
256         final JexlOptions options = ctxt.getEngineOptions();
257         options.setStrict(true);
258         options.setSilent(true); // to avoid throwing JexlException on null method call
259 
260         JexlScript jscript;
261 
262         jscript = jexl.createScript("dummy.hashCode()");
263         assertNull(jscript.execute(ctxt), jscript::getSourceText); // OK
264 
265         ctxt.set("dummy", "abcd");
266         assertEquals(Integer.valueOf("abcd".hashCode()), jscript.execute(ctxt), jscript::getSourceText); // OK
267 
268         jscript = jexl.createScript("dummy.hashCode");
269         assertNull(jscript.execute(ctxt), jscript::getSourceText); // OK
270 
271         JexlExpression jexpr;
272         vars.clear();
273         jexpr = jexl.createExpression("dummy.hashCode()");
274         assertNull(jexpr.evaluate(ctxt), jexpr::toString); // OK
275 
276         ctxt.set("dummy", "abcd");
277         assertEquals(Integer.valueOf("abcd".hashCode()), jexpr.evaluate(ctxt), jexpr::toString); // OK
278 
279         jexpr = jexl.createExpression("dummy.hashCode");
280         assertNull(jexpr.evaluate(ctxt), jexpr::toString); // OK
281     }
282 
283     // JEXL-87
284     @Test
285     void test87() throws Exception {
286         final JexlEngine jexl = createEngine(false);
287         final JexlEvalContext ctxt = new JexlEvalContext();
288         final JexlOptions options = ctxt.getEngineOptions();
289         // ensure errors will throw
290         options.setSilent(false);
291         final JexlExpression divide = jexl.createExpression("l / r");
292         final JexlExpression modulo = jexl.createExpression("l % r");
293 
294         ctxt.set("l", java.math.BigInteger.valueOf(7));
295         ctxt.set("r", java.math.BigInteger.valueOf(2));
296         assertEquals(java.math.BigInteger.valueOf(3), divide.evaluate(ctxt));
297         assertTrue(jexl.getArithmetic().equals(1, modulo.evaluate(ctxt)));
298 
299         ctxt.set("l", java.math.BigDecimal.valueOf(7));
300         ctxt.set("r", java.math.BigDecimal.valueOf(2));
301         assertEquals(java.math.BigDecimal.valueOf(3.5), divide.evaluate(ctxt));
302         assertTrue(jexl.getArithmetic().equals(1, modulo.evaluate(ctxt)));
303     }
304 
305     // JEXL-90
306     @Test
307     void test90() throws Exception {
308         final JexlEngine jexl = createEngine(false);
309         final JexlEvalContext ctxt = new JexlEvalContext();
310         final JexlOptions options = ctxt.getEngineOptions();
311         // ensure errors will throw
312         options.setSilent(false);
313         // ';' is necessary between expressions
314         // @formatter:off
315         final String[] fexprs = {
316             "a=3 b=4",
317             "while(a) while(a)",
318             "1 2",
319             "if (true) 2; 3 {}",
320             "while (x) 1 if (y) 2 3"
321         };
322         // @formatter:on
323         for (final String fexpr : fexprs) {
324             assertThrows(JexlException.class, () -> jexl.createScript(fexpr));
325         }
326         // ';' is necessary between expressions and only expressions
327         // @formatter:off
328         final String[] exprs = {
329             "if (x) {1} if (y) {2}",
330             "if (x) 1 if (y) 2",
331             "while (x) 1 if (y) 2 else 3",
332             "for(z : [3, 4, 5]) { z } y ? 2 : 1",
333             "for(z : [3, 4, 5]) { z } if (y) 2 else 1"
334         };
335         // @formatter:on
336         ctxt.set("x", Boolean.FALSE);
337         ctxt.set("y", Boolean.TRUE);
338         for (final String expr : exprs) {
339             final JexlScript s = jexl.createScript(expr);
340             assertEquals(Integer.valueOf(2), s.execute(ctxt));
341         }
342         debuggerCheck(jexl);
343     }
344 
345     @Test
346     void test97() throws Exception {
347         final JexlEngine jexl = createEngine(false);
348         final JexlEvalContext ctxt = new JexlEvalContext();
349         final JexlOptions options = ctxt.getEngineOptions();
350         // ensure errors will throw
351         options.setSilent(false);
352         for (char v = 'a'; v <= 'z'; ++v) {
353             ctxt.set(Character.toString(v), 10);
354         }
355         final String input
356                 = "(((((((((((((((((((((((((z+y)/x)*w)-v)*u)/t)-s)*r)/q)+p)-o)*n)-m)+l)*k)+j)/i)+h)*g)+f)/e)+d)-c)/b)+a)";
357 
358         JexlExpression script;
359         // Make sure everything is loaded...
360         final long start = System.nanoTime();
361         script = jexl.createExpression(input);
362         final Object value = script.evaluate(ctxt);
363         assertEquals(Integer.valueOf(11), value);
364         final long end = System.nanoTime();
365         final double millisec = (end - start) / 1e6;
366         final double limit = 200.0; // Allow plenty of slack
367         assertTrue(millisec < limit, () -> "Expected parse to take less than " + limit + "ms, actual " + millisec);
368     }
369 
370     @Test
371     void test98() throws Exception {
372         final String[] exprs = {
373             "fn:replace('DOMAIN\\somename', '\\\\', '\\\\\\\\')",
374             "fn:replace(\"DOMAIN\\somename\", \"\\\\\", \"\\\\\\\\\")",
375             "fn:replace('DOMAIN\\somename', '\\u005c', '\\u005c\\u005c')"
376         };
377         final Map<String, Object> funcs = new HashMap<>();
378         funcs.put("fn", new fn98());
379         final JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();
380         for (final String expr : exprs) {
381             final Object value = jexl.createExpression(expr).evaluate(null);
382             assertEquals("DOMAIN\\\\somename", value, expr);
383         }
384     }
385 
386 }