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 * @summary Test ForceEarlyReturnVoid when the target thread's top frame is a value class
27 * constructor, method or class initializer.
28 * @enablePreview
29 * @run junit/othervm/native --enable-native-access=ALL-UNNAMED -agentlib:ForceEarlyReturnValueClass ${test.main.class}
30 */
31
32 import org.junit.jupiter.api.Test;
33 import static org.junit.jupiter.api.Assertions.*;
34
35 class ForceEarlyReturnValueClass {
36
37 private static final int JVMTI_ERROR_NONE = 0;
38 private static final int JVMTI_ERROR_OPAQUE_FRAME = 32;
39
40 /**
41 * Test that ForceEarlyReturnVoid fails with JVMTI_ERROR_OPAQUE_FRAME when the
42 * target thread's top frame is a value class constructor before super.
43 */
44 @Test
45 void testForceEarlyReturnBeforeSuper() throws Exception {
46 class TestCase {
47 static volatile boolean initStarted;
48 static volatile boolean initFinished;
49 static volatile boolean stop;
50 static value class ValueClass {
51 ValueClass() {
52 TestCase.initStarted = true;
53 while (!TestCase.stop) {}
54 super();
55 TestCase.initFinished = true;
56 }
57 }
58 }
59 Thread thread = Thread.ofPlatform().start(TestCase.ValueClass::new);
60 boolean suspended = false;
61 try {
62 // wait for target thread to start executing constructor
63 while (!TestCase.initStarted) {
64 Thread.sleep(10);
65 }
66 assertEquals(JVMTI_ERROR_NONE, suspendThread(thread));
67 suspended = true;
68 assertTopFrame(thread, TestCase.ValueClass.class, "<init>");
69 assertEquals(JVMTI_ERROR_OPAQUE_FRAME, forceEarlyReturnVoid(thread));
70 } finally {
71 TestCase.stop = true;
72 if (suspended) resumeThread(thread);
73 thread.join();
74 }
75 assertTrue(TestCase.initFinished); // constructor should have run to end
76 }
77
78 /**
79 * Test that ForceEarlyReturnVoid fails with JVMTI_ERROR_OPAQUE_FRAME when the
80 * target thread's top frame is a value class constructor after super.
81 * (This case may be allowed to succeed in the future)
82 */
83 @Test
84 void testForceEarlyReturnAfterSuper() throws Exception {
85 class TestCase {
86 static volatile boolean afterSuperStarted;
87 static volatile boolean initFinished;
88 static volatile boolean stop;
89 static value class ValueClass {
90 ValueClass() {
91 super();
92 TestCase.afterSuperStarted = true;
93 while (!TestCase.stop) {}
94 TestCase.initFinished = true;
95 }
96 }
97 }
98 Thread thread = Thread.ofPlatform().start(TestCase.ValueClass::new);
99 boolean suspended = false;
100 try {
101 // wait for target thread to start executing code after super
102 while (!TestCase.afterSuperStarted) {
103 Thread.sleep(10);
104 }
105 assertEquals(JVMTI_ERROR_NONE, suspendThread(thread));
106 suspended = true;
107 assertTopFrame(thread, TestCase.ValueClass.class, "<init>");
108 assertEquals(JVMTI_ERROR_OPAQUE_FRAME, forceEarlyReturnVoid(thread));
109 } finally {
110 TestCase.stop = true;
111 if (suspended) resumeThread(thread);
112 thread.join();
113 }
114 assertTrue(TestCase.initFinished); // constructor should have run to end
115 }
116
117 /**
118 * Test that ForceEarlyReturnVoid succeeds with the target thread's top frame is a
119 * method invoked from the constructor after super.
120 */
121 @Test
122 void testForceEarlyReturnPostInit() throws Exception {
123 class TestCase {
124 static volatile boolean initFinished;
125 static volatile boolean postInitStarted;
126 static volatile boolean postInitFinished;
127 static volatile boolean stop;
128 static value class ValueClass {
129 ValueClass() {
130 super();
131 postInit();
132 TestCase.initFinished = true;
133 }
134 void postInit() {
135 TestCase.postInitStarted = true;
136 while (!TestCase.stop) {}
137 TestCase.postInitFinished = true;
138 }
139 }
140 }
141 Thread thread = Thread.ofPlatform().start(TestCase.ValueClass::new);
142 boolean suspended = false;
143 try {
144 // wait for target thread to start executing postInit method
145 while (!TestCase.postInitStarted) {
146 Thread.sleep(10);
147 }
148 assertEquals(JVMTI_ERROR_NONE, suspendThread(thread));
149 assertTopFrame(thread, TestCase.ValueClass.class, "postInit");
150 suspended = true;
151 assertEquals(JVMTI_ERROR_NONE, forceEarlyReturnVoid(thread));
152 } finally {
153 TestCase.stop = true;
154 if (suspended) resumeThread(thread);
155 thread.join();
156 }
157 assertFalse(TestCase.postInitFinished); // postInit should not have run to the end
158 assertTrue(TestCase.initFinished); // constructor should have run to end
159 }
160
161 /**
162 * Test that ForceEarlyReturnVoid succeeds with the target thread's top frame is a
163 * value class method.
164 */
165 @Test
166 void testForceEarlyReturnMethod() throws Exception {
167 class TestCase {
168 static volatile boolean runStarted;
169 static volatile boolean runFinished;
170 static volatile boolean stop;
171 static value class ValueClass {
172 void run() {
173 TestCase.runStarted = true;
174 while (!TestCase.stop) {}
175 TestCase.runFinished = true; // should not get there
176 }
177 }
178 }
179 var valueObj = new TestCase.ValueClass();
180 Thread thread = Thread.ofPlatform().start(valueObj::run);
181 boolean suspended = false;
182 try {
183 // wait for target thread to start executing method
184 while (!TestCase.runStarted) {
185 Thread.sleep(10);
186 }
187 assertEquals(JVMTI_ERROR_NONE, suspendThread(thread));
188 suspended = true;
189 assertTopFrame(thread, TestCase.ValueClass.class, "run");
190 assertEquals(JVMTI_ERROR_NONE, forceEarlyReturnVoid(thread));
191 } finally {
192 TestCase.stop = true;
193 if (suspended) resumeThread(thread);
194 thread.join();
195 }
196 assertFalse(TestCase.runFinished); // should not have run to the end
197 }
198
199 /**
200 * Test that ForceEarlyReturnVoid succeeds with the target thread's top frame is a
201 * value class clinit.
202 */
203 @Test
204 void testForceEarlyReturnClassInitializer() throws Exception {
205 class TestCase {
206 static volatile boolean clinitStarted;
207 static volatile boolean stop;
208 static value class ValueClass {
209 static final boolean initialized;
210 static {
211 TestCase.clinitStarted = true;
212 while (!TestCase.stop) {}
213 initialized = true; // should not get there
214 }
215 }
216 }
217 Thread thread = Thread.ofPlatform().start(TestCase.ValueClass::new);
218 boolean suspended = false;
219 try {
220 // wait for target thread to start executing class initializer
221 while (!TestCase.clinitStarted) {
222 Thread.sleep(10);
223 }
224 assertEquals(JVMTI_ERROR_NONE, suspendThread(thread));
225 suspended = true;
226 assertTopFrame(thread, TestCase.ValueClass.class, "<clinit>");
227 assertEquals(JVMTI_ERROR_NONE, forceEarlyReturnVoid(thread));
228 } finally {
229 TestCase.stop = true;
230 if (suspended) resumeThread(thread);
231 thread.join();
232 }
233 assertFalse(TestCase.ValueClass.initialized); // should not have run to the end
234 }
235
236 /**
237 * Asserts that the given thread's top frame is the expected class/method.
238 */
239 private void assertTopFrame(Thread thread, Class<?> clazz, String methodName) {
240 StackTraceElement[] stack = thread.getStackTrace();
241 assertTrue(stack.length > 0);
242 assertEquals(clazz.getName(), stack[0].getClassName());
243 assertEquals(methodName, stack[0].getMethodName());
244 }
245
246 private static native int suspendThread(Thread thread);
247 private static native int resumeThread(Thread thread);
248 private static native int forceEarlyReturnVoid(Thread thread);
249 private static native int forceEarlyReturnObject(Thread thread, Object retObject);
250 }