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