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