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.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  import static org.junit.jupiter.api.Assertions.fail;
23  
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.commons.jexl3.internal.Debugger;
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests switch/case statement and expression.
32   */
33  @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
34  public class SwitchTest extends JexlTestCase {
35  
36      public SwitchTest() {
37          super("SwitchTest");
38      }
39  
40      /**
41       * 'continue' inside a switch statement must target the enclosing loop (as in Java), not be
42       * swallowed and fall through into the following cases (JEXL-security f029). 'break' by contrast
43       * only breaks out of the switch, leaving the rest of the loop body to run.
44       */
45      @Test
46      void testContinueInSwitchStatement() {
47          final JexlEngine jexl = new JexlBuilder().create();
48          // continue skips the remainder of the loop iteration -> the append is not reached for i == 2
49          final String continueSrc =
50              "var r = '';\n" +
51              "for (var i : [1, 2, 3]) {\n" +
52              "  switch (i) {\n" +
53              "    case 2 : continue;\n" +
54              "    default : {}\n" +
55              "  }\n" +
56              "  r += i;\n" +
57              "}\n" +
58              "r";
59          assertEquals("13", jexl.createScript(continueSrc).execute(null));
60          // break only leaves the switch -> the append still runs for every i
61          final String breakSrc =
62              "var r = '';\n" +
63              "for (var i : [1, 2, 3]) {\n" +
64              "  switch (i) {\n" +
65              "    case 2 : break;\n" +
66              "    default : {}\n" +
67              "  }\n" +
68              "  r += i;\n" +
69              "}\n" +
70              "r";
71          assertEquals("123", jexl.createScript(breakSrc).execute(null));
72      }
73  
74      @Test
75      void testSwitchExpression() {
76          final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
77          final JexlScript e = jexl.createScript("var j = switch(i) { case 1 -> 2; case 2 -> 3; default -> 4; }; j", "i");
78          final JexlContext jc = new MapContext();
79          final Object o = e.execute(jc, 1);
80          assertEquals(2, o);
81      }
82  
83      @Test
84      void testBrokenSwitchExpression0() {
85          final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
86          try {
87              jexl.createScript("var j = switch(i) { case 1 -> return 2;  }; j", "i");
88              fail("should not be able to create script with return in switch expression");
89          } catch (final JexlException.Parsing xparse) {
90              assertTrue(xparse.getMessage().contains("return"));
91          }
92          try {
93              jexl.createScript("var j = switch(i) { case 1 -> break; }; j", "i");
94              fail("should not be able to create script with break in switch expression");
95          } catch (final JexlException.Parsing xparse) {
96              assertTrue(xparse.getMessage().contains("break"));
97          }
98      }
99  
100     @Test
101     void testSwitchStatement() {
102         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
103         final JexlScript e = jexl.createScript("switch(i) { case 1: i += 2; case 2: i += 3; default: i += 4; }; i + 33", "i");
104         final JexlContext jc = new MapContext();
105         final Object o = e.execute(jc, 2);
106         assertEquals(42, o);
107     }
108 
109     @Test
110     void test440a() {
111         final JexlFeatures f = JexlFeatures.createDefault().ambiguousStatement(true);
112         final JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create();
113         String src =
114             "let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\n" + "default -> { let z = 4; z + x } } y";
115         JexlScript script = jexl.createScript(src, "x");
116         assertNotNull(script);
117         final String dbgStr = script.getParsedText();
118         assertNotNull(dbgStr);
119 
120         Object result = script.execute(null, 10);
121         assertEquals(3, result);
122         result = script.execute(null, 11);
123         assertEquals(3, result);
124         result = script.execute(null, 20);
125         assertEquals(4, result);
126         result = script.execute(null, 21);
127         assertEquals(4, result);
128         result = script.execute(null, 38);
129         assertEquals(42, result);
130         src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } y";
131         try {
132             script = jexl.createScript(src, "x");
133             fail("should not be able to create script with break in switch");
134         } catch (final JexlException.Parsing xparse) {
135             assertTrue(xparse.getMessage().contains("break"));
136         }
137         assertNotNull(script);
138     }
139 
140     @Test
141     void test440b() {
142         final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create();
143         final String src =
144             "switch (x) {\n" +
145                 " case 10 : return 3\n;" +
146                 " case 20 : case 21 : return 4;\n" +
147                 " case 32: break; \n" +
148                 " default : return x + 4;\n" +
149                 " }\n" +
150                 " 169";
151         final JexlScript script = jexl.createScript(src, "x");
152         assertNotNull(script);
153         final String dbgStr = script.getParsedText();
154         assertNotNull(dbgStr);
155 
156         Object result = script.execute(null, 10);
157         assertEquals(3, result);
158         result = script.execute(null, 20);
159         assertEquals(4, result);
160         result = script.execute(null, 21);
161         assertEquals(4, result);
162         result = script.execute(null, 32);
163         assertEquals(169, result);
164         result = script.execute(null, 38);
165         assertEquals(42, result);
166     }
167 
168     public enum Scope440 {
169         UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
170     }
171 
172 
173     @Test
174     void test440p() {
175         assertTrue(TEST_PERMS.allow(this.getClass()));
176     }
177 
178     @Test
179     void test440c() {
180         final JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
181         final String src =
182             "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; case Scope440.THIS -> 'this'; default -> 'OTHER'; } s";
183         final JexlScript script = jexl.createScript(src, "x");
184         assertNotNull(script);
185         final String dbgStr = script.getParsedText();
186         assertNotNull(dbgStr);
187 
188         Object result = script.execute(null, Scope440.UNDEFINED);
189         assertEquals("undefined", result);
190         result = script.execute(null, Scope440.THIS);
191         assertEquals("this", result);
192         result = script.execute(null, 21);
193         assertEquals("OTHER", result);
194     }
195 
196     @Test
197     void test440d() {
198         final JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
199         final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; } s";
200         final JexlScript script = jexl.createScript(src, "x");
201         try {
202             script.execute(null, Scope440.THIS);
203             fail("should not be able to execute script with switch expression with no default");
204         } catch (final JexlException xjexl) {
205             assertTrue(xjexl.getMessage().contains("switch"));
206         }
207     }
208 
209     @Test
210     void test440e() {
211         final JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
212         final String src = "function f(x) { switch (x) { case Scope440.UNDEFINED : return 'undefined'; } } f(x)";
213         final JexlScript script = jexl.createScript(src, "x");
214         final Object result = script.execute(null, Scope440.UNDEFINED);
215         assertEquals("undefined", result);
216         try {
217             script.execute(null, Scope440.THIS);
218             fail("should not be able to execute script with switch expression with no default");
219         } catch (final JexlException xjexl) {
220             assertTrue(xjexl.getMessage().contains("switch"));
221         }
222     }
223 
224     @Test
225     void testSwitchStatement1() {
226         // a one statement script
227         final String src = "switch (x) {\n"
228             + "  case 1: return 'one';\n"
229             + "  case 2: return 'two';\n"
230             + "  case 3: return 'three';\n"
231             + "  default: return 'many';\n"
232             + "}";
233         runSwitch(src);
234         final String src2 = "if (true) { " + src + " }";
235         runSwitch(src2);
236     }
237 
238     @Test
239     void testSwitchExpression1() {
240         // a one statement script that uses the expression syntax
241         final String src = "switch (x) {\n"
242             + "  case 1 -> 'one';\n"
243             + "  case 2 -> 'two';\n"
244             + "  case 3 -> 'three';\n"
245             + "  default -> 'many';\n"
246             + "}";
247         runSwitch(src);
248         final String src2 = "if (true) { " + src + " }";
249         runSwitch(src2);
250     }
251 
252     void runSwitch(final String src) {
253         final JexlEngine jexl = new JexlBuilder().create();
254         final JexlScript script = jexl.createScript(src, "x");
255         Object result;
256         result = script.execute(null, 1);
257         assertEquals("one", result);
258         result = script.execute(null, 2);
259         assertEquals("two", result);
260         result = script.execute(null, 3);
261         assertEquals("three", result);
262         result = script.execute(null, 4);
263         assertEquals("many", result);
264         result = script.execute(null, 42);
265         assertEquals("many", result);
266         final Debugger debugger = new Debugger();
267         assertTrue(debugger.debug(script));
268         final String dbgStr = debugger.toString();
269         assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbgStr));
270     }
271 
272     @Test
273     void testSwitchExpressionFail() {
274         final List<String> err = Arrays.asList("break", "continue");
275         for (final String keyword : err) {
276             switchExpressionFailKeyword(keyword);
277         }
278     }
279 
280     private void switchExpressionFailKeyword(final String keyword) {
281         // a one statement script that uses the expression syntax
282         final String src = "switch (x) {\n"
283             + "  case 1 -> 'one';\n"
284             + "  case 2 -> " + keyword + ";\n"
285             + "  default -> 'many';\n"
286             + "}";
287         final JexlEngine jexl = new JexlBuilder().create();
288         try {
289             jexl.createScript(src, "x");
290             fail("should not be able to create script with " + keyword + " in switch expression");
291         } catch (final JexlException.Parsing xparse) {
292             assertTrue(xparse.getMessage().contains(keyword));
293         }
294     }
295 
296     @Test
297     void testTryCatchFinally() {
298         final JexlEngine jexl = new JexlBuilder().create();
299         final String src = "try { return 42 } catch(const xerror){} finally { return 169 }";
300         final JexlScript script = jexl.createScript(src);
301         assertNotNull(script);
302         final Object result = script.execute(null);
303         assertEquals(169, result);
304     }
305 
306     @Test
307     void testSwitchNaN0() {
308         // a one statement script
309         final String src = "switch (x) {\n"
310             + "  case NaN: return 'NotANumber';\n"
311             + "  case 1: return 'one';\n"
312             + "  case 2: return 'two';\n"
313             + "  case 3: return 'three';\n"
314             + "  default: return 'many';\n"
315             + "}";
316         runSwitchNaN(src);
317     }
318 
319     @Test
320     void testSwitchNaN1() {
321         // a one statement script
322         final String src = "switch (x) {\n"
323             + "  case NaN -> 'NotANumber';\n"
324             + "  case 1 -> 'one';\n"
325             + "  case 2 -> 'two';\n"
326             + "  case 3 -> 'three';\n"
327             + "  default -> 'many';\n"
328             + "}";
329         runSwitchNaN(src);
330     }
331 
332     static void runSwitchNaN(final String src) {
333         final JexlEngine jexl = new JexlBuilder().create();
334         final JexlScript script = jexl.createScript(src, "x");
335         Object result = script.execute(null, Double.NaN);
336         assertEquals("NotANumber", result);
337         result = script.execute(null, 1);
338         assertEquals("one", result);
339         result = script.execute(null, 42);
340         assertEquals("many", result);
341     }
342 
343 }