1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @modules jdk.incubator.code
 27  * @run testng TestExceptionRegionOps
 28  */
 29 
 30 import jdk.incubator.code.dialect.core.CoreType;
 31 import jdk.incubator.code.dialect.java.JavaOp;
 32 import org.testng.Assert;
 33 import org.testng.annotations.Test;
 34 
 35 import jdk.incubator.code.dialect.core.CoreOp;
 36 import jdk.incubator.code.dialect.java.MethodRef;
 37 import jdk.incubator.code.interpreter.Interpreter;
 38 
 39 import java.lang.invoke.MethodHandles;
 40 import jdk.incubator.code.dialect.java.JavaType;
 41 import java.util.ArrayList;
 42 import java.util.List;
 43 import java.util.function.Consumer;
 44 import java.util.function.IntConsumer;
 45 
 46 import static jdk.incubator.code.dialect.core.CoreOp.return_;
 47 import static jdk.incubator.code.dialect.java.JavaOp.throw_;
 48 import static jdk.incubator.code.dialect.core.CoreOp.branch;
 49 import static jdk.incubator.code.dialect.core.CoreOp.constant;
 50 import static jdk.incubator.code.dialect.java.JavaOp.exceptionRegionEnter;
 51 import static jdk.incubator.code.dialect.java.JavaOp.exceptionRegionExit;
 52 import static jdk.incubator.code.dialect.core.CoreOp.func;
 53 import static jdk.incubator.code.dialect.java.JavaType.*;
 54 import static jdk.incubator.code.dialect.java.JavaType.VOID;
 55 
 56 public class TestExceptionRegionOps {
 57 
 58     public void testF(IntConsumer c) {
 59         try {
 60             c.accept(0);
 61             c.accept(-1);
 62         } catch (IllegalStateException e) {
 63             c.accept(1);
 64             c.accept(-1);
 65         } catch (IllegalArgumentException e) {
 66             c.accept(2);
 67             c.accept(-1);
 68         }
 69         c.accept(3);
 70         c.accept(-1);
 71     }
 72 
 73     @Test
 74     public void test() {
 75         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
 76                 .body(fbody -> {
 77                     var fblock = fbody.entryBlock();
 78                     var catchER1ISE = fblock.block(type(IllegalStateException.class));
 79                     var catchER1IAE = fblock.block(type(IllegalArgumentException.class));
 80                     var enterER1 = fblock.block();
 81                     var end = fblock.block();
 82 
 83                     //
 84                     var c = fblock.parameters().get(0);
 85                     fblock.op(exceptionRegionEnter(
 86                             enterER1.successor(),
 87                             catchER1IAE.successor(), catchER1ISE.successor()));
 88 
 89                     // Start of exception region
 90                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
 91                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
 92                     // End of exception region
 93                     enterER1.op(exceptionRegionExit(end.successor(),
 94                         catchER1ISE.successor(), catchER1IAE.successor()));
 95 
 96                     // First catch block for exception region
 97                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, 1))));
 98                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, -1))));
 99                     catchER1ISE.op(branch(end.successor()));
100 
101                     // Second catch for exception region
102                     catchER1IAE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1IAE.op(constant(INT, 2))));
103                     catchER1IAE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1IAE.op(constant(INT, -1))));
104                     catchER1IAE.op(branch(end.successor()));
105 
106                     //
107                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
108                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
109                     end.op(CoreOp.return_());
110                 });
111 
112         System.out.println(f.toText());
113 
114         Consumer<IntConsumer> test = testConsumer(
115                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
116                 this::testF);
117 
118         test.accept(i -> {});
119         test.accept(i -> {
120             if (i == 0) throw new IllegalStateException();
121         });
122         test.accept(i -> {
123             if (i == 0) throw new IllegalArgumentException();
124         });
125         test.accept(i -> {
126             if (i == 0) throw new NullPointerException();
127         });
128         test.accept(i -> {
129             if (i == 0) throw new IllegalStateException();
130             if (i == 1) throw new RuntimeException();
131         });
132         test.accept(i -> {
133             if (i == 0) throw new IllegalArgumentException();
134             if (i == 2) throw new RuntimeException();
135         });
136         test.accept(i -> {
137             if (i == 3) throw new IllegalStateException();
138         });
139     }
140 
141 
142     public void testCatchThrowableF(IntConsumer c) {
143         try {
144             c.accept(0);
145             c.accept(-1);
146         } catch (IllegalStateException e) {
147             c.accept(1);
148             c.accept(-1);
149         } catch (Throwable e) {
150             c.accept(2);
151             c.accept(-1);
152         }
153         c.accept(3);
154         c.accept(-1);
155     }
156 
157     @Test
158     public void testCatchThrowable() {
159         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
160                 .body(fbody -> {
161                     var fblock = fbody.entryBlock();
162                     var catchER1ISE = fblock.block(type(IllegalStateException.class));
163                     var catchER1T = fblock.block(type(Throwable.class));
164                     var enterER1 = fblock.block();
165                     var end = fblock.block();
166 
167                     //
168                     var c = fblock.parameters().get(0);
169                     fblock.op(exceptionRegionEnter(
170                             enterER1.successor(),
171                             catchER1T.successor(), catchER1ISE.successor()));
172 
173                     // Start of exception region
174                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
175                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
176                     // End of exception region
177                     enterER1.op(exceptionRegionExit(end.successor(),
178                         catchER1ISE.successor(), catchER1T.successor()));
179 
180                     // First catch block for exception region
181                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, 1))));
182                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, -1))));
183                     catchER1ISE.op(branch(end.successor()));
184 
185                     // Second catch for exception region
186                     catchER1T.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1T.op(constant(INT, 2))));
187                     catchER1T.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1T.op(constant(INT, -1))));
188                     catchER1T.op(branch(end.successor()));
189 
190                     //
191                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
192                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
193                     end.op(return_());
194                 });
195 
196         System.out.println(f.toText());
197 
198         Consumer<IntConsumer> test = testConsumer(
199                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
200                 this::testCatchThrowableF);
201 
202         test.accept(i -> {});
203         test.accept(i -> {
204             if (i == 0) throw new IllegalStateException();
205         });
206         test.accept(i -> {
207             if (i == 0) throw new RuntimeException();
208         });
209         test.accept(i -> {
210             if (i == 0) throw new IllegalStateException();
211             if (i == 1) throw new RuntimeException();
212         });
213         test.accept(i -> {
214             if (i == 0) throw new RuntimeException();
215             if (i == 2) throw new RuntimeException();
216         });
217         test.accept(i -> {
218             if (i == 3) throw new IllegalStateException();
219         });
220     }
221 
222 
223     public void testNestedF(IntConsumer c) {
224         try {
225             c.accept(0);
226             c.accept(-1);
227             try {
228                 c.accept(1);
229                 c.accept(-1);
230             } catch (IllegalStateException e) {
231                 c.accept(2);
232                 c.accept(-1);
233             }
234             c.accept(3);
235             c.accept(-1);
236         } catch (IllegalArgumentException e) {
237             c.accept(4);
238             c.accept(-1);
239         }
240         c.accept(5);
241         c.accept(-1);
242     }
243 
244     @Test
245     public void testNested() {
246         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
247                 .body(fbody -> {
248                     var fblock = fbody.entryBlock();
249                     var catchER1 = fblock.block(type(IllegalArgumentException.class));
250                     var catchER2 = fblock.block(type(IllegalStateException.class));
251                     var enterER1 = fblock.block();
252                     var enterER2 = fblock.block();
253                     var b3 = fblock.block();
254                     var end = fblock.block();
255 
256                     //
257                     var c = fblock.parameters().get(0);
258                     fblock.op(exceptionRegionEnter(
259                             enterER1.successor(),
260                             catchER1.successor()));
261 
262                     // Start of first exception region
263                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
264                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
265                     enterER1.op(exceptionRegionEnter(
266                             enterER2.successor(),
267                             catchER2.successor()));
268 
269                     // Start of second exception region
270                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, 1))));
271                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, -1))));
272                     // End of second exception region
273                     enterER2.op(exceptionRegionExit(b3.successor(),
274                         catchER2.successor()));
275 
276                     // Catch block for second exception region
277                     catchER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER2.op(constant(INT, 2))));
278                     catchER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER2.op(constant(INT, -1))));
279                     catchER2.op(branch(b3.successor()));
280 
281                     b3.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b3.op(constant(INT, 3))));
282                     b3.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b3.op(constant(INT, -1))));
283                     // End of first exception region
284                     b3.op(exceptionRegionExit(end.successor(),
285                         catchER1.successor()));
286 
287                     // Catch block for first exception region
288                     catchER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1.op(constant(INT, 4))));
289                     catchER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1.op(constant(INT, -1))));
290                     catchER1.op(branch(end.successor()));
291 
292                     //
293                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 5))));
294                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
295                     end.op(CoreOp.return_());
296                 });
297 
298         System.out.println(f.toText());
299 
300         Consumer<IntConsumer> test = testConsumer(
301                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
302                 this::testNestedF);
303 
304         test.accept(i -> {});
305         test.accept(i -> {
306             if (i == 0) throw new IllegalStateException();
307         });
308         test.accept(i -> {
309             if (i == 0) throw new IllegalArgumentException();
310         });
311         test.accept(i -> {
312             if (i == 1) throw new IllegalStateException();
313         });
314         test.accept(i -> {
315             if (i == 1) throw new IllegalArgumentException();
316         });
317         test.accept(i -> {
318             if (i == 1) throw new IllegalStateException();
319             if (i == 2) throw new IllegalArgumentException();
320         });
321         test.accept(i -> {
322             if (i == 1) throw new IllegalStateException();
323             if (i == 2) throw new RuntimeException();
324         });
325         test.accept(i -> {
326             if (i == 3) throw new IllegalArgumentException();
327         });
328         test.accept(i -> {
329             if (i == 3) throw new RuntimeException();
330         });
331         test.accept(i -> {
332             if (i == 3) throw new IllegalArgumentException();
333             if (i == 4) throw new RuntimeException();
334         });
335         test.accept(i -> {
336             if (i == 5) throw new RuntimeException();
337         });
338     }
339 
340     public void testCatchFinallyF(IntConsumer c) {
341         try {
342             c.accept(0);
343             c.accept(-1);
344         } catch (IllegalStateException e) {
345             c.accept(1);
346             c.accept(-1);
347         } finally {
348             c.accept(2);
349             c.accept(-1);
350         }
351         c.accept(3);
352         c.accept(-1);
353     }
354 
355     @Test
356     public void testCatchFinally() {
357         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, JavaType.type(IntConsumer.class)))
358                 .body(fbody -> {
359                     var fblock = fbody.entryBlock();
360                     var catchRE = fblock.block(type(IllegalStateException.class));
361                     var catchAll = fblock.block(type(Throwable.class));
362                     var enterER1 = fblock.block();
363                     var exitER1 = fblock.block();
364                     var enterER2 = fblock.block();
365                     var exitER2 = fblock.block();
366                     var end = fblock.block();
367 
368                     //
369                     var c = fblock.parameters().get(0);
370                     fblock.op(exceptionRegionEnter(
371                             enterER1.successor(),
372                             catchAll.successor(), catchRE.successor()));
373 
374                     // Start of exception region
375                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
376                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
377                     // End of exception region
378                     enterER1.op(exceptionRegionExit(exitER1.successor(),
379                         catchRE.successor(), catchAll.successor()));
380                     // Inline finally
381                     exitER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER1.op(constant(INT, 2))));
382                     exitER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER1.op(constant(INT, -1))));
383                     exitER1.op(branch(end.successor()));
384 
385                     // Catch block for RuntimeException
386                     catchRE.op(exceptionRegionEnter(
387                             enterER2.successor(),
388                             catchAll.successor()));
389                     // Start of exception region
390                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, 1))));
391                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, -1))));
392                     // End of exception region
393                     enterER2.op(exceptionRegionExit(exitER2.successor(),
394                         catchAll.successor()));
395                     // Inline finally
396                     exitER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER2.op(constant(INT, 2))));
397                     exitER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER2.op(constant(INT, -1))));
398                     exitER2.op(branch(end.successor()));
399 
400                     // Catch all block for finally
401                     catchAll.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchAll.op(constant(INT, 2))));
402                     catchAll.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchAll.op(constant(INT, -1))));
403                     catchAll.op(throw_(catchAll.parameters().get(0)));
404 
405                     //
406                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
407                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
408                     end.op(CoreOp.return_());
409                 });
410 
411         System.out.println(f.toText());
412 
413         Consumer<IntConsumer> test = testConsumer(
414                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
415                 this::testCatchFinallyF
416                 );
417 
418         test.accept(i -> {});
419         test.accept(i -> {
420             if (i == 0) throw new IllegalStateException();
421         });
422         test.accept(i -> {
423             if (i == 0) throw new RuntimeException();
424         });
425         test.accept(i -> {
426             if (i == 2) throw new RuntimeException();
427         });
428         test.accept(i -> {
429             if (i == 0) throw new IllegalStateException();
430             if (i == 1) throw new RuntimeException();
431         });
432         test.accept(i -> {
433             if (i == 3) throw new RuntimeException();
434         });
435     }
436 
437     static final MethodRef INT_CONSUMER_ACCEPT_METHOD = MethodRef.method(type(IntConsumer.class), "accept",
438             VOID, INT);
439 
440     static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
441         return c -> {
442             List<Integer> actual = new ArrayList<>();
443             IntConsumer actualC = actual::add;
444             Throwable actualT = null;
445             try {
446                 actualR.accept(actualC.andThen(c));
447             } catch (Interpreter.InterpreterException e) {
448                 throw e;
449             } catch (Throwable t) {
450                 actualT = t;
451             }
452 
453             List<Integer> expected = new ArrayList<>();
454             IntConsumer expectedC = expected::add;
455             Throwable expectedT = null;
456             try {
457                 expectedR.accept(expectedC.andThen(c));
458             } catch (Throwable t) {
459                 expectedT = t;
460             }
461 
462             Assert.assertEquals(
463                     actualT != null ? actualT.getClass() : null,
464                     expectedT != null ? expectedT.getClass() : null);
465             Assert.assertEquals(actual, expected);
466         };
467     }
468 }