1 /*
  2  * Copyright (c) 2019, 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  * @library /test/lib
 27  *
 28  * @enablePreview




 29  *
 30  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -Xcheck:jni ClassInitBarrier
 31  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -Xcheck:jni ClassInitBarrier
 32  *
 33  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 34  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true  -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 35  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 36  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 37  *
 38  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 39  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true  -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 40  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 41  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 42  */
 43 
 44 import jdk.test.lib.Asserts;
 45 
 46 import java.util.*;
 47 import java.util.concurrent.atomic.AtomicBoolean;
 48 import java.util.concurrent.atomic.AtomicInteger;
 49 import java.util.function.Consumer;
 50 
 51 public class ClassInitBarrier {
 52     static {
 53         System.loadLibrary("ClassInitBarrier");
 54 
 55         if (!init()) {
 56             throw new Error("init failed");
 57         }
 58     }
 59 
 60     static native boolean init();
 61 
 62     static final boolean THROW = Boolean.getBoolean("THROW");
 63 
 64     static value class MyValue {
 65         int x = 42;
 66 
 67         void verify() {
 68             Asserts.assertEquals(x, 42);
 69         }
 70     }
 71 
 72     static class Test {
 73 
 74         static class A {
 75             static {
 76                 if (!init(B.class)) {
 77                     throw new Error("init failed");
 78                 }
 79 
 80                 changePhase(Phase.IN_PROGRESS);
 81                 runTests();      // interpreted mode
 82                 warmup();        // trigger compilation
 83                 runTests();      // compiled mode
 84 
 85                 ensureBlocked(); // ensure still blocked
 86                 maybeThrow();    // fail initialization if needed
 87 
 88                 changePhase(Phase.FINISHED);
 89             }
 90 
 91             static              void staticM(Runnable action, MyValue val) { action.run(); val.verify(); }
 92             static synchronized void staticS(Runnable action, MyValue val) { action.run(); val.verify(); }
 93             static native       void staticN(Runnable action, MyValue val);
 94 
 95             static int staticF;
 96 
 97             int f;
 98             void m() {}
 99 
100             static native boolean init(Class<B> cls);
101         }
102 
103         static class B extends A {}
104 
105         static void testInvokeStatic(Runnable action, MyValue val)       { A.staticM(action, val); }
106         static void testInvokeStaticSync(Runnable action, MyValue val)   { A.staticS(action, val); }
107         static void testInvokeStaticNative(Runnable action, MyValue val) { A.staticN(action, val); }
108 
109         static int  testGetStatic(Runnable action, MyValue val)    { int v = A.staticF; action.run(); val.verify(); return v;   }
110         static void testPutStatic(Runnable action, MyValue val)    { A.staticF = 1;     action.run(); val.verify(); }
111         static A    testNewInstanceA(Runnable action, MyValue val) { A obj = new A();   action.run(); val.verify(); return obj; }
112         static B    testNewInstanceB(Runnable action, MyValue val) { B obj = new B();   action.run(); val.verify(); return obj; }
113 
114         static int  testGetField(A recv, Runnable action, MyValue val)      { int v = recv.f; action.run(); val.verify(); return v; }
115         static void testPutField(A recv, Runnable action, MyValue val)      { recv.f = 1;     action.run(); val.verify(); }
116         static void testInvokeVirtual(A recv, Runnable action, MyValue val) { recv.m();       action.run(); val.verify(); }
117 
118         static native void testInvokeStaticJNI(Runnable action, MyValue val);
119         static native void testInvokeStaticSyncJNI(Runnable action, MyValue val);
120         static native void testInvokeStaticNativeJNI(Runnable action, MyValue val);
121 
122         static native int  testGetStaticJNI(Runnable action, MyValue val);
123         static native void testPutStaticJNI(Runnable action, MyValue val);
124         static native A    testNewInstanceAJNI(Runnable action, MyValue val);
125         static native B    testNewInstanceBJNI(Runnable action, MyValue val);
126 
127         static native int  testGetFieldJNI(A recv, Runnable action, MyValue val);
128         static native void testPutFieldJNI(A recv, Runnable action, MyValue val);
129         static native void testInvokeVirtualJNI(A recv, Runnable action, MyValue val);
130 
131         static void runTests() {
132             checkBlockingAction(Test::testInvokeStatic);       // invokestatic
133             checkBlockingAction(Test::testInvokeStaticSync);   // invokestatic
134             checkBlockingAction(Test::testInvokeStaticNative); // invokestatic
135             checkBlockingAction(Test::testGetStatic);          // getstatic
136             checkBlockingAction(Test::testPutStatic);          // putstatic
137             checkBlockingAction(Test::testNewInstanceA);       // new
138 
139             checkNonBlockingAction(Test::testInvokeStaticJNI);       // invokestatic
140             checkNonBlockingAction(Test::testInvokeStaticSyncJNI);   // invokestatic
141             checkNonBlockingAction(Test::testInvokeStaticNativeJNI); // invokestatic
142             checkNonBlockingAction(Test::testGetStaticJNI);          // getstatic
143             checkNonBlockingAction(Test::testPutStaticJNI);          // putstatic
144             checkBlockingAction(Test::testNewInstanceAJNI);          // new
145 
146             A recv = testNewInstanceB(NON_BLOCKING.get(), new MyValue());  // trigger B initialization
147             checkNonBlockingAction(Test::testNewInstanceB); // new: NO BLOCKING: same thread: A being initialized, B fully initialized
148 
149             checkNonBlockingAction(recv, Test::testGetField);      // getfield
150             checkNonBlockingAction(recv, Test::testPutField);      // putfield
151             checkNonBlockingAction(recv, Test::testInvokeVirtual); // invokevirtual
152 
153             checkNonBlockingAction(Test::testNewInstanceBJNI);        // new: NO BLOCKING: same thread: A being initialized, B fully initialized
154             checkNonBlockingAction(recv, Test::testGetFieldJNI);      // getfield
155             checkNonBlockingAction(recv, Test::testPutFieldJNI);      // putfield
156             checkNonBlockingAction(recv, Test::testInvokeVirtualJNI); // invokevirtual
157         }
158 
159         static void warmup() {
160             MyValue val = new MyValue();
161             for (int i = 0; i < 20_000; i++) {
162                 testInvokeStatic(      NON_BLOCKING_WARMUP, val);
163                 testInvokeStaticNative(NON_BLOCKING_WARMUP, val);
164                 testInvokeStaticSync(  NON_BLOCKING_WARMUP, val);
165                 testGetStatic(         NON_BLOCKING_WARMUP, val);
166                 testPutStatic(         NON_BLOCKING_WARMUP, val);
167                 testNewInstanceA(      NON_BLOCKING_WARMUP, val);
168                 testNewInstanceB(      NON_BLOCKING_WARMUP, val);
169 
170                 testGetField(new B(),      NON_BLOCKING_WARMUP, val);
171                 testPutField(new B(),      NON_BLOCKING_WARMUP, val);
172                 testInvokeVirtual(new B(), NON_BLOCKING_WARMUP, val);
173             }
174         }
175 
176         static void run() {
177             execute(ExceptionInInitializerError.class, () -> triggerInitialization(A.class));
178             ensureFinished();
179             runTests(); // after initialization is over
180         }
181     }
182 
183     // ============================================================================================================== //
184 
185     static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) {
186         try {
187             action.run();
188             if (THROW) throw failure("no exception thrown");
189         } catch (Throwable e) {
190             if (THROW) {
191                 if (e.getClass() == expectedExceptionClass) {
192                     // expected
193                 } else {
194                     String msg = String.format("unexpected exception thrown: expected %s, caught %s",
195                             expectedExceptionClass.getName(), e);
196                     throw failure(msg, e);
197                 }
198             } else {
199                 throw failure("no exception expected", e);
200             }
201         }
202     }
203 
204     private static AssertionError failure(String msg) {
205         return new AssertionError(phase + ": " + msg);
206     }
207 
208     private static AssertionError failure(String msg, Throwable e) {
209         return new AssertionError(phase + ": " + msg, e);
210     }
211 
212     static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>());
213     static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add;
214 
215     static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>());
216     static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put;
217 
218     private static void ensureBlocked() {
219         for (Thread thr : BLOCKED_THREADS) {
220             try {
221                 thr.join(100);
222                 if (!thr.isAlive()) {
223                     dump(thr);
224                     throw new AssertionError("not blocked");
225                 }
226             } catch (InterruptedException e) {
227                 throw new Error(e);
228             }
229         }
230     }
231 
232 
233     private static void ensureFinished() {
234         for (Thread thr : BLOCKED_THREADS) {
235             try {
236                 thr.join(15_000);
237             } catch (InterruptedException e) {
238                 throw new Error(e);
239             }
240             if (thr.isAlive()) {
241                 dump(thr);
242                 throw new AssertionError(thr + ": still blocked");
243             }
244         }
245         for (Thread thr : BLOCKED_THREADS) {
246             if (THROW) {
247                 if (!FAILED_THREADS.containsKey(thr)) {
248                     throw new AssertionError(thr + ": exception not thrown");
249                 }
250 
251                 Throwable ex = FAILED_THREADS.get(thr);
252                 if (ex.getClass() != NoClassDefFoundError.class) {
253                     throw new AssertionError(thr + ": wrong exception thrown", ex);
254                 }
255             } else {
256                 if (FAILED_THREADS.containsKey(thr)) {
257                     Throwable ex = FAILED_THREADS.get(thr);
258                     throw new AssertionError(thr + ": exception thrown", ex);
259                 }
260             }
261         }
262         if (THROW) {
263             Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
264         } else {
265             Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get());
266         }
267 
268         dumpInfo();
269     }
270 
271     interface TestCase0 {
272         void run(Runnable runnable, MyValue val);
273     }
274 
275     interface TestCase1<T> {
276         void run(T arg, Runnable runnable, MyValue val);
277     }
278 
279     enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE }
280 
281     static volatile Phase phase = Phase.BEFORE_INIT;
282 
283     static void changePhase(Phase newPhase) {
284         dumpInfo();
285 
286         Phase oldPhase = phase;
287         switch (oldPhase) {
288             case BEFORE_INIT:
289                 Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0);
290                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0);
291 
292                 Asserts.assertEquals(BLOCKING_ACTIONS.get(),     0);
293                 Asserts.assertEquals(BLOCKING_COUNTER.get(),     0);
294                 break;
295             case IN_PROGRESS:
296                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get());
297 
298                 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
299                 break;
300             default: throw new Error("wrong phase transition " + oldPhase);
301         }
302         phase = newPhase;
303     }
304 
305     static void dumpInfo() {
306         System.out.println("Phase: " + phase);
307         System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get());
308         System.out.println("Blocking actions:     " + BLOCKING_COUNTER.get()     + " / " + BLOCKING_ACTIONS.get());
309     }
310 
311     static final Runnable NON_BLOCKING_WARMUP = () -> {
312         if (phase != Phase.IN_PROGRESS) {
313             throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
314         }
315     };
316 
317     static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) {
318         actionCounter.incrementAndGet();
319 
320         final AtomicBoolean cnt = new AtomicBoolean(false);
321         return () -> {
322             if (cnt.getAndSet(true)) {
323                 throw new Error("repeated invocation");
324             }
325             invocationCounter.incrementAndGet();
326             if (phase != validPhase) {
327                 throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
328             }
329         };
330     }
331 
332     @FunctionalInterface
333     interface Factory<V> {
334         V get();
335     }
336 
337     static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0);
338     static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0);
339     static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(phase, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS);
340 
341     static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0);
342     static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0);
343     static final Factory<Runnable> BLOCKING     = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS);
344 
345     static void checkBlockingAction(TestCase0 r) {
346         MyValue val = new MyValue();
347         switch (phase) {
348             case IN_PROGRESS: {
349                 // Barrier during class initalization.
350                 r.run(NON_BLOCKING.get(), val);             // initializing thread
351                 checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread
352                 break;
353             }
354             case FINISHED: {
355                 // No barrier after class initalization is over.
356                 r.run(NON_BLOCKING.get(), val); // initializing thread
357                 checkNotBlocked(r);        // different thread
358                 break;
359             }
360             case INIT_FAILURE: {
361                 // Exception is thrown after class initialization failed.
362                 TestCase0 test = (action, valarg) -> execute(NoClassDefFoundError.class, () -> r.run(action, valarg));
363 
364                 test.run(NON_BLOCKING.get(), val); // initializing thread
365                 checkNotBlocked(test);        // different thread
366                 break;
367             }
368             default: throw new Error("wrong phase: " + phase);
369         }
370     }
371 
372     static void checkNonBlockingAction(TestCase0 r) {
373         r.run(NON_BLOCKING.get(), new MyValue()); // initializing thread
374         checkNotBlocked(r);        // different thread
375     }
376 
377     static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) {
378         r.run(recv, NON_BLOCKING.get(), new MyValue());                  // initializing thread
379         checkNotBlocked((action, val) -> r.run(recv, action, val)); // different thread
380     }
381 
382     static void checkFailingAction(TestCase0 r) {
383         r.run(NON_BLOCKING.get(), new MyValue()); // initializing thread
384         checkNotBlocked(r);        // different thread
385     }
386 
387     static void triggerInitialization(Class<?> cls) {
388         try {
389             Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader());
390             if (loadedClass != cls) {
391                 throw new Error("wrong class");
392             }
393         } catch (ClassNotFoundException e) {
394             throw new Error(e);
395         }
396     }
397 
398     static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) {
399         Thread thr = new Thread(() -> {
400             try {
401                 r.run(BLOCKING.get(), new MyValue());
402                 System.out.println("Thread " + Thread.currentThread() + ": Finished successfully");
403             } catch(Throwable e) {
404                 System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e);
405                 if (!THROW) {
406                     e.printStackTrace();
407                 }
408                 throw e;
409             }
410         } );
411         thr.setUncaughtExceptionHandler(onException);
412 
413         thr.start();
414         try {
415             thr.join(100);
416 
417             dump(thr);
418             if (thr.isAlive()) {
419                 onBlockHandler.accept(thr); // blocked
420             } else {
421                 throw new AssertionError("not blocked");
422             }
423         } catch (InterruptedException e) {
424             throw new Error(e);
425         }
426     }
427 
428     static void checkNotBlocked(TestCase0 r) {
429         final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get(), new MyValue()));
430         final Throwable[] ex = new Throwable[1];
431         thr.setUncaughtExceptionHandler((t, e) -> {
432             if (thr != t) {
433                 ex[0] = new Error("wrong thread: " + thr + " vs " + t);
434             } else {
435                 ex[0] = e;
436             }
437         });
438 
439         thr.start();
440         try {
441             thr.join(15_000);
442             if (thr.isAlive()) {
443                 dump(thr);
444                 throw new AssertionError("blocked");
445             }
446         } catch (InterruptedException e) {
447             throw new Error(e);
448         }
449 
450         if (ex[0] != null) {
451             throw new AssertionError("no exception expected", ex[0]);
452         }
453     }
454 
455     static void maybeThrow() {
456         if (THROW) {
457             changePhase(Phase.INIT_FAILURE);
458             throw new RuntimeException("failed class initialization");
459         }
460     }
461 
462     private static void dump(Thread thr) {
463         System.out.println("Thread: " + thr);
464         System.out.println("Thread state: " + thr.getState());
465         if (thr.isAlive()) {
466             for (StackTraceElement frame : thr.getStackTrace()) {
467                 System.out.println(frame);
468             }
469         } else {
470             if (FAILED_THREADS.containsKey(thr)) {
471                 System.out.println("Failed with an exception: ");
472                 FAILED_THREADS.get(thr).toString();
473             } else {
474                 System.out.println("Finished successfully");
475             }
476         }
477     }
478 
479     public static void main(String[] args) throws Exception {
480         Test.run();
481         System.out.println("TEST PASSED");
482     }
483 }
--- EOF ---