1 /*
  2  * Copyright (c) 2026, 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  * @library lib
 28  * @run junit TestTryWithResources
 29  * @run junit/othervm -Dbabylon.tryFinally=sharedDispatch TestTryWithResources
 30  * @run main Unreflect TestTryWithResources
 31  * @run junit TestTryWithResources
 32  * @run junit/othervm -Dbabylon.tryFinally=sharedDispatch TestTryWithResources
 33  */
 34 
 35 import java.io.Closeable;
 36 import java.io.IOException;
 37 import java.lang.invoke.MethodHandle;
 38 import java.lang.invoke.MethodHandles;
 39 import java.lang.invoke.MethodType;
 40 import java.lang.reflect.Method;
 41 import java.util.ArrayList;
 42 import java.util.function.Consumer;
 43 import java.util.stream.Stream;
 44 
 45 import jdk.incubator.code.Op;
 46 import jdk.incubator.code.Reflect;
 47 import jdk.incubator.code.bytecode.BytecodeGenerator;
 48 import jdk.incubator.code.dialect.core.CoreOp;
 49 import org.junit.jupiter.params.ParameterizedTest;
 50 import org.junit.jupiter.params.provider.MethodSource;
 51 
 52 import static org.junit.jupiter.api.Assertions.*;
 53 
 54 public class TestTryWithResources {
 55 
 56     record Resource(Consumer<String> log, String suffix, boolean throwOnClose, boolean throwOnCreate) implements Closeable {
 57 
 58         Resource {
 59             log.accept("open" + suffix);
 60             if (throwOnCreate) {
 61                 log.accept("throwCreate" + suffix);
 62                 throw new RuntimeException();
 63             }
 64         }
 65 
 66         @Override
 67         public void close() throws IOException {
 68             log.accept("close" + suffix);
 69             if (throwOnClose) {
 70                 log.accept("throwClose" + suffix);
 71                 throw new IOException();
 72             }
 73         }
 74     }
 75 
 76     @Reflect
 77     public static void tryWithResources(Consumer<String> log, boolean throwInBody, boolean throwOnClose1,
 78                                         boolean throwOnClose2, boolean throwOnClose3, boolean throwOnCreate1,
 79                                         boolean throwOnCreate2, boolean throwOnCreate3) throws IOException {
 80         var r2 = new Resource(log, "2", throwOnClose2, throwOnCreate2);
 81         try {
 82             try (var _ = new Resource(log, "1", throwOnClose1, throwOnCreate1)) {
 83                 log.accept("outerBody");
 84                 try (var _ = r2;
 85                      var _ = new Resource(log, "3", throwOnClose3, throwOnCreate3)) {
 86                     log.accept("innerBody");
 87                     if (throwInBody) {
 88                         log.accept("throwBody");
 89                         throw new IllegalStateException("body");
 90                     }
 91                 } finally {
 92                     log.accept("innerFinally");
 93                 }
 94             } finally {
 95                 log.accept("outerFinally");
 96             }
 97         } finally {
 98             log.accept("end");
 99         }
100     }
101 
102     static Stream<MethodHandle> mhs() throws NoSuchMethodException, IllegalAccessException {
103         Method m = TestTryWithResources.class.getDeclaredMethod("tryWithResources", Consumer.class, boolean.class,
104                 boolean.class, boolean.class, boolean.class, boolean.class, boolean.class, boolean.class);
105         CoreOp.FuncOp fop = Op.ofMethod(m).orElseThrow();
106         return Stream.of(
107                 BytecodeGenerator.generate(MethodHandles.lookup(), fop),
108                 MethodHandles.lookup().findStatic(Interpreter.class, "invoke",
109                                 MethodType.methodType(Object.class, MethodHandles.Lookup.class, Op.class, Object[].class))
110                         .bindTo(MethodHandles.lookup())
111                         .bindTo(fop)
112                         .asVarargsCollector(Object[].class)
113         );
114     }
115 
116     @ParameterizedTest
117     @MethodSource("mhs")
118     public void testTryWithResources(MethodHandle mh) {
119         for (int i = 0; i < 128; i++) {
120             boolean throwInBody = (i & 1) != 0;
121             boolean throwOnClose1 = (i & 2) != 0;
122             boolean throwOnClose2 = (i & 4) != 0;
123             boolean throwOnClose3 = (i & 8) != 0;
124             boolean throwOnCreate1 = (i & 16) != 0;
125             boolean throwOnCreate2 = (i & 32) != 0;
126             boolean throwOnCreate3 = (i & 64) != 0;
127             var expected = new ArrayList<String>();
128             var actual = new ArrayList<String>();
129             try {
130                 tryWithResources(expected::add, throwInBody, throwOnClose1, throwOnClose2, throwOnClose3,
131                         throwOnCreate1, throwOnCreate2, throwOnCreate3);
132                 mh.invoke((Consumer<String>)actual::add, throwInBody, throwOnClose1, throwOnClose2, throwOnClose3,
133                         throwOnCreate1, throwOnCreate2, throwOnCreate3);
134             } catch (Throwable t) {
135                 assertEquals(t.getSuppressed().length,
136                         assertThrowsExactly(t.getClass(), () -> mh.invoke((Consumer<String>)actual::add, throwInBody,
137                                 throwOnClose1, throwOnClose2, throwOnClose3, throwOnCreate1, throwOnCreate2,
138                                 throwOnCreate3)).getSuppressed().length);
139             }
140             assertIterableEquals(expected, actual);
141         }
142     }
143 
144 }