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 junit TestExceptionRegionOps
 28  */
 29 
 30 import jdk.incubator.code.dialect.core.CoreOp;
 31 import jdk.incubator.code.dialect.core.CoreType;
 32 import jdk.incubator.code.dialect.java.JavaOp;
 33 import jdk.incubator.code.dialect.java.JavaType;
 34 import jdk.incubator.code.dialect.java.MethodRef;
 35 import jdk.incubator.code.interpreter.Interpreter;
 36 import org.junit.jupiter.api.Assertions;
 37 import org.junit.jupiter.api.Test;
 38 
 39 import java.lang.invoke.MethodHandles;
 40 import java.util.ArrayList;
 41 import java.util.List;
 42 import java.util.function.Consumer;
 43 import java.util.function.IntConsumer;
 44 
 45 import static jdk.incubator.code.dialect.core.CoreOp.*;
 46 import static jdk.incubator.code.dialect.java.JavaOp.*;
 47 import static jdk.incubator.code.dialect.java.JavaType.*;
 48 
 49 public class TestExceptionRegionOps {
 50 
 51     public void testF(IntConsumer c) {
 52         try {
 53             c.accept(0);
 54             c.accept(-1);
 55         } catch (IllegalStateException e) {
 56             c.accept(1);
 57             c.accept(-1);
 58         } catch (IllegalArgumentException e) {
 59             c.accept(2);
 60             c.accept(-1);
 61         }
 62         c.accept(3);
 63         c.accept(-1);
 64     }
 65 
 66     @Test
 67     public void test() {
 68         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
 69                 .body(fbody -> {
 70                     var fblock = fbody.entryBlock();
 71                     var catchER1ISE = fblock.block(type(IllegalStateException.class));
 72                     var catchER1IAE = fblock.block(type(IllegalArgumentException.class));
 73                     var enterER1 = fblock.block();
 74                     var end = fblock.block();
 75 
 76                     //
 77                     var c = fblock.parameters().get(0);
 78                     fblock.op(exceptionRegionEnter(
 79                             enterER1.successor(),
 80                             catchER1IAE.successor(), catchER1ISE.successor()));
 81 
 82                     // Start of exception region
 83                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
 84                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
 85                     // End of exception region
 86                     enterER1.op(exceptionRegionExit(end.successor(),
 87                         catchER1ISE.successor(), catchER1IAE.successor()));
 88 
 89                     // First catch block for exception region
 90                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, 1))));
 91                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, -1))));
 92                     catchER1ISE.op(branch(end.successor()));
 93 
 94                     // Second catch for exception region
 95                     catchER1IAE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1IAE.op(constant(INT, 2))));
 96                     catchER1IAE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1IAE.op(constant(INT, -1))));
 97                     catchER1IAE.op(branch(end.successor()));
 98 
 99                     //
100                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
101                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
102                     end.op(CoreOp.return_());
103                 });
104 
105         System.out.println(f.toText());
106 
107         Consumer<IntConsumer> test = testConsumer(
108                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
109                 this::testF);
110 
111         test.accept(i -> {});
112         test.accept(i -> {
113             if (i == 0) throw new IllegalStateException();
114         });
115         test.accept(i -> {
116             if (i == 0) throw new IllegalArgumentException();
117         });
118         test.accept(i -> {
119             if (i == 0) throw new NullPointerException();
120         });
121         test.accept(i -> {
122             if (i == 0) throw new IllegalStateException();
123             if (i == 1) throw new RuntimeException();
124         });
125         test.accept(i -> {
126             if (i == 0) throw new IllegalArgumentException();
127             if (i == 2) throw new RuntimeException();
128         });
129         test.accept(i -> {
130             if (i == 3) throw new IllegalStateException();
131         });
132     }
133 
134 
135     public void testCatchThrowableF(IntConsumer c) {
136         try {
137             c.accept(0);
138             c.accept(-1);
139         } catch (IllegalStateException e) {
140             c.accept(1);
141             c.accept(-1);
142         } catch (Throwable e) {
143             c.accept(2);
144             c.accept(-1);
145         }
146         c.accept(3);
147         c.accept(-1);
148     }
149 
150     @Test
151     public void testCatchThrowable() {
152         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
153                 .body(fbody -> {
154                     var fblock = fbody.entryBlock();
155                     var catchER1ISE = fblock.block(type(IllegalStateException.class));
156                     var catchER1T = fblock.block(type(Throwable.class));
157                     var enterER1 = fblock.block();
158                     var end = fblock.block();
159 
160                     //
161                     var c = fblock.parameters().get(0);
162                     fblock.op(exceptionRegionEnter(
163                             enterER1.successor(),
164                             catchER1T.successor(), catchER1ISE.successor()));
165 
166                     // Start of exception region
167                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
168                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
169                     // End of exception region
170                     enterER1.op(exceptionRegionExit(end.successor(),
171                         catchER1ISE.successor(), catchER1T.successor()));
172 
173                     // First catch block for exception region
174                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, 1))));
175                     catchER1ISE.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1ISE.op(constant(INT, -1))));
176                     catchER1ISE.op(branch(end.successor()));
177 
178                     // Second catch for exception region
179                     catchER1T.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1T.op(constant(INT, 2))));
180                     catchER1T.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1T.op(constant(INT, -1))));
181                     catchER1T.op(branch(end.successor()));
182 
183                     //
184                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
185                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
186                     end.op(return_());
187                 });
188 
189         System.out.println(f.toText());
190 
191         Consumer<IntConsumer> test = testConsumer(
192                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
193                 this::testCatchThrowableF);
194 
195         test.accept(i -> {});
196         test.accept(i -> {
197             if (i == 0) throw new IllegalStateException();
198         });
199         test.accept(i -> {
200             if (i == 0) throw new RuntimeException();
201         });
202         test.accept(i -> {
203             if (i == 0) throw new IllegalStateException();
204             if (i == 1) throw new RuntimeException();
205         });
206         test.accept(i -> {
207             if (i == 0) throw new RuntimeException();
208             if (i == 2) throw new RuntimeException();
209         });
210         test.accept(i -> {
211             if (i == 3) throw new IllegalStateException();
212         });
213     }
214 
215 
216     public void testNestedF(IntConsumer c) {
217         try {
218             c.accept(0);
219             c.accept(-1);
220             try {
221                 c.accept(1);
222                 c.accept(-1);
223             } catch (IllegalStateException e) {
224                 c.accept(2);
225                 c.accept(-1);
226             }
227             c.accept(3);
228             c.accept(-1);
229         } catch (IllegalArgumentException e) {
230             c.accept(4);
231             c.accept(-1);
232         }
233         c.accept(5);
234         c.accept(-1);
235     }
236 
237     @Test
238     public void testNested() {
239         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, type(IntConsumer.class)))
240                 .body(fbody -> {
241                     var fblock = fbody.entryBlock();
242                     var catchER1 = fblock.block(type(IllegalArgumentException.class));
243                     var catchER2 = fblock.block(type(IllegalStateException.class));
244                     var enterER1 = fblock.block();
245                     var enterER2 = fblock.block();
246                     var b3 = fblock.block();
247                     var end = fblock.block();
248 
249                     //
250                     var c = fblock.parameters().get(0);
251                     fblock.op(exceptionRegionEnter(
252                             enterER1.successor(),
253                             catchER1.successor()));
254 
255                     // Start of first exception region
256                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
257                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
258                     enterER1.op(exceptionRegionEnter(
259                             enterER2.successor(),
260                             catchER2.successor()));
261 
262                     // Start of second exception region
263                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, 1))));
264                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, -1))));
265                     // End of second exception region
266                     enterER2.op(exceptionRegionExit(b3.successor(),
267                         catchER2.successor()));
268 
269                     // Catch block for second exception region
270                     catchER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER2.op(constant(INT, 2))));
271                     catchER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER2.op(constant(INT, -1))));
272                     catchER2.op(branch(b3.successor()));
273 
274                     b3.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b3.op(constant(INT, 3))));
275                     b3.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, b3.op(constant(INT, -1))));
276                     // End of first exception region
277                     b3.op(exceptionRegionExit(end.successor(),
278                         catchER1.successor()));
279 
280                     // Catch block for first exception region
281                     catchER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1.op(constant(INT, 4))));
282                     catchER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchER1.op(constant(INT, -1))));
283                     catchER1.op(branch(end.successor()));
284 
285                     //
286                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 5))));
287                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
288                     end.op(CoreOp.return_());
289                 });
290 
291         System.out.println(f.toText());
292 
293         Consumer<IntConsumer> test = testConsumer(
294                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
295                 this::testNestedF);
296 
297         test.accept(i -> {});
298         test.accept(i -> {
299             if (i == 0) throw new IllegalStateException();
300         });
301         test.accept(i -> {
302             if (i == 0) throw new IllegalArgumentException();
303         });
304         test.accept(i -> {
305             if (i == 1) throw new IllegalStateException();
306         });
307         test.accept(i -> {
308             if (i == 1) throw new IllegalArgumentException();
309         });
310         test.accept(i -> {
311             if (i == 1) throw new IllegalStateException();
312             if (i == 2) throw new IllegalArgumentException();
313         });
314         test.accept(i -> {
315             if (i == 1) throw new IllegalStateException();
316             if (i == 2) throw new RuntimeException();
317         });
318         test.accept(i -> {
319             if (i == 3) throw new IllegalArgumentException();
320         });
321         test.accept(i -> {
322             if (i == 3) throw new RuntimeException();
323         });
324         test.accept(i -> {
325             if (i == 3) throw new IllegalArgumentException();
326             if (i == 4) throw new RuntimeException();
327         });
328         test.accept(i -> {
329             if (i == 5) throw new RuntimeException();
330         });
331     }
332 
333     public void testCatchFinallyF(IntConsumer c) {
334         try {
335             c.accept(0);
336             c.accept(-1);
337         } catch (IllegalStateException e) {
338             c.accept(1);
339             c.accept(-1);
340         } finally {
341             c.accept(2);
342             c.accept(-1);
343         }
344         c.accept(3);
345         c.accept(-1);
346     }
347 
348     @Test
349     public void testCatchFinally() {
350         CoreOp.FuncOp f = func("f", CoreType.functionType(VOID, JavaType.type(IntConsumer.class)))
351                 .body(fbody -> {
352                     var fblock = fbody.entryBlock();
353                     var catchRE = fblock.block(type(IllegalStateException.class));
354                     var catchAll = fblock.block(type(Throwable.class));
355                     var enterER1 = fblock.block();
356                     var exitER1 = fblock.block();
357                     var enterER2 = fblock.block();
358                     var exitER2 = fblock.block();
359                     var end = fblock.block();
360 
361                     //
362                     var c = fblock.parameters().get(0);
363                     fblock.op(exceptionRegionEnter(
364                             enterER1.successor(),
365                             catchAll.successor(), catchRE.successor()));
366 
367                     // Start of exception region
368                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, 0))));
369                     enterER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER1.op(constant(INT, -1))));
370                     // End of exception region
371                     enterER1.op(exceptionRegionExit(exitER1.successor(),
372                         catchRE.successor(), catchAll.successor()));
373                     // Inline finally
374                     exitER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER1.op(constant(INT, 2))));
375                     exitER1.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER1.op(constant(INT, -1))));
376                     exitER1.op(branch(end.successor()));
377 
378                     // Catch block for RuntimeException
379                     catchRE.op(exceptionRegionEnter(
380                             enterER2.successor(),
381                             catchAll.successor()));
382                     // Start of exception region
383                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, 1))));
384                     enterER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, enterER2.op(constant(INT, -1))));
385                     // End of exception region
386                     enterER2.op(exceptionRegionExit(exitER2.successor(),
387                         catchAll.successor()));
388                     // Inline finally
389                     exitER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER2.op(constant(INT, 2))));
390                     exitER2.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, exitER2.op(constant(INT, -1))));
391                     exitER2.op(branch(end.successor()));
392 
393                     // Catch all block for finally
394                     catchAll.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchAll.op(constant(INT, 2))));
395                     catchAll.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, catchAll.op(constant(INT, -1))));
396                     catchAll.op(throw_(catchAll.parameters().get(0)));
397 
398                     //
399                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, 3))));
400                     end.op(JavaOp.invoke(INT_CONSUMER_ACCEPT_METHOD, c, end.op(constant(INT, -1))));
401                     end.op(CoreOp.return_());
402                 });
403 
404         System.out.println(f.toText());
405 
406         Consumer<IntConsumer> test = testConsumer(
407                 c -> Interpreter.invoke(MethodHandles.lookup(), f, c),
408                 this::testCatchFinallyF
409                 );
410 
411         test.accept(i -> {});
412         test.accept(i -> {
413             if (i == 0) throw new IllegalStateException();
414         });
415         test.accept(i -> {
416             if (i == 0) throw new RuntimeException();
417         });
418         test.accept(i -> {
419             if (i == 2) throw new RuntimeException();
420         });
421         test.accept(i -> {
422             if (i == 0) throw new IllegalStateException();
423             if (i == 1) throw new RuntimeException();
424         });
425         test.accept(i -> {
426             if (i == 3) throw new RuntimeException();
427         });
428     }
429 
430     static final MethodRef INT_CONSUMER_ACCEPT_METHOD = MethodRef.method(type(IntConsumer.class), "accept",
431             VOID, INT);
432 
433     static Consumer<IntConsumer> testConsumer(Consumer<IntConsumer> actualR, Consumer<IntConsumer> expectedR) {
434         return c -> {
435             List<Integer> actual = new ArrayList<>();
436             IntConsumer actualC = actual::add;
437             Throwable actualT = null;
438             try {
439                 actualR.accept(actualC.andThen(c));
440             } catch (Interpreter.InterpreterException e) {
441                 throw e;
442             } catch (Throwable t) {
443                 actualT = t;
444             }
445 
446             List<Integer> expected = new ArrayList<>();
447             IntConsumer expectedC = expected::add;
448             Throwable expectedT = null;
449             try {
450                 expectedR.accept(expectedC.andThen(c));
451             } catch (Throwable t) {
452                 expectedT = t;
453             }
454 
455             Assertions.assertEquals(
456                     expectedT != null ? expectedT.getClass() : null, actualT != null ? actualT.getClass() : null
457             );
458             Assertions.assertEquals(expected, actual);
459         };
460     }
461 }