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 }