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 }