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  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint                   -DTHROW=false -Xcheck:jni ClassInitBarrier
 29  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint                   -DTHROW=true  -Xcheck:jni ClassInitBarrier
 30  *
 31  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -Xcheck:jni ClassInitBarrier
 32  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true  -Xcheck:jni ClassInitBarrier
 33  *
 34  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -Xcheck:jni ClassInitBarrier
 35  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -Xcheck:jni ClassInitBarrier
 36  *
 37  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 38  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true  -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 39  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 40  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
 41  *
 42  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 43  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true  -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 44  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 45  * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation  -DTHROW=true  -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
 46  */
 47 
 48 import jdk.test.lib.Asserts;
 49 
 50 import java.util.*;
 51 import java.util.concurrent.atomic.AtomicBoolean;
 52 import java.util.concurrent.atomic.AtomicInteger;
 53 import java.util.function.Consumer;
 54 
 55 public class ClassInitBarrier {
 56     static {
 57         System.loadLibrary("ClassInitBarrier");
 58 
 59         if (!init()) {
 60             throw new Error("init failed");
 61         }
 62     }
 63 
 64     static native boolean init();
 65 
 66     static final boolean THROW = Boolean.getBoolean("THROW");
 67 








 68     static class Test {

 69         static class A {
 70             static {
 71                 if (!init(B.class)) {
 72                     throw new Error("init failed");
 73                 }
 74 
 75                 changePhase(Phase.IN_PROGRESS);
 76                 runTests();      // interpreted mode
 77                 warmup();        // trigger compilation
 78                 runTests();      // compiled mode
 79 
 80                 ensureBlocked(); // ensure still blocked
 81                 maybeThrow();    // fail initialization if needed
 82 
 83                 changePhase(Phase.FINISHED);
 84             }
 85 
 86             static              void staticM(Runnable action) { action.run(); }
 87             static synchronized void staticS(Runnable action) { action.run(); }
 88             static native       void staticN(Runnable action);
 89 
 90             static int staticF;
 91 
 92             int f;
 93             void m() {}
 94 
 95             static native boolean init(Class<B> cls);
 96         }
 97 
 98         static class B extends A {}
 99 
100         static void testInvokeStatic(Runnable action)       { A.staticM(action); }
101         static void testInvokeStaticSync(Runnable action)   { A.staticS(action); }
102         static void testInvokeStaticNative(Runnable action) { A.staticN(action); }
103 
104         static int  testGetStatic(Runnable action)    { int v = A.staticF; action.run(); return v;   }
105         static void testPutStatic(Runnable action)    { A.staticF = 1;     action.run(); }
106         static A    testNewInstanceA(Runnable action) { A obj = new A();   action.run(); return obj; }
107         static B    testNewInstanceB(Runnable action) { B obj = new B();   action.run(); return obj; }
108 
109         static int  testGetField(A recv, Runnable action)      { int v = recv.f; action.run(); return v; }
110         static void testPutField(A recv, Runnable action)      { recv.f = 1;     action.run(); }
111         static void testInvokeVirtual(A recv, Runnable action) { recv.m();       action.run(); }
112 
113         static native void testInvokeStaticJNI(Runnable action);
114         static native void testInvokeStaticSyncJNI(Runnable action);
115         static native void testInvokeStaticNativeJNI(Runnable action);
116 
117         static native int  testGetStaticJNI(Runnable action);
118         static native void testPutStaticJNI(Runnable action);
119         static native A    testNewInstanceAJNI(Runnable action);
120         static native B    testNewInstanceBJNI(Runnable action);
121 
122         static native int  testGetFieldJNI(A recv, Runnable action);
123         static native void testPutFieldJNI(A recv, Runnable action);
124         static native void testInvokeVirtualJNI(A recv, Runnable action);
125 
126         static void runTests() {
127             checkBlockingAction(Test::testInvokeStatic);       // invokestatic
128             checkBlockingAction(Test::testInvokeStaticSync);   // invokestatic
129             checkBlockingAction(Test::testInvokeStaticNative); // invokestatic
130             checkBlockingAction(Test::testGetStatic);          // getstatic
131             checkBlockingAction(Test::testPutStatic);          // putstatic
132             checkBlockingAction(Test::testNewInstanceA);       // new
133 
134             checkNonBlockingAction(Test::testInvokeStaticJNI);       // invokestatic
135             checkNonBlockingAction(Test::testInvokeStaticSyncJNI);   // invokestatic
136             checkNonBlockingAction(Test::testInvokeStaticNativeJNI); // invokestatic
137             checkNonBlockingAction(Test::testGetStaticJNI);          // getstatic
138             checkNonBlockingAction(Test::testPutStaticJNI);          // putstatic
139             checkBlockingAction(Test::testNewInstanceAJNI);          // new
140 
141             A recv = testNewInstanceB(NON_BLOCKING.get());  // trigger B initialization
142             checkNonBlockingAction(Test::testNewInstanceB); // new: NO BLOCKING: same thread: A being initialized, B fully initialized
143 
144             checkNonBlockingAction(recv, Test::testGetField);      // getfield
145             checkNonBlockingAction(recv, Test::testPutField);      // putfield
146             checkNonBlockingAction(recv, Test::testInvokeVirtual); // invokevirtual
147 
148             checkNonBlockingAction(Test::testNewInstanceBJNI);        // new: NO BLOCKING: same thread: A being initialized, B fully initialized
149             checkNonBlockingAction(recv, Test::testGetFieldJNI);      // getfield
150             checkNonBlockingAction(recv, Test::testPutFieldJNI);      // putfield
151             checkNonBlockingAction(recv, Test::testInvokeVirtualJNI); // invokevirtual
152         }
153 
154         static void warmup() {

155             for (int i = 0; i < 20_000; i++) {
156                 testInvokeStatic(      NON_BLOCKING_WARMUP);
157                 testInvokeStaticNative(NON_BLOCKING_WARMUP);
158                 testInvokeStaticSync(  NON_BLOCKING_WARMUP);
159                 testGetStatic(         NON_BLOCKING_WARMUP);
160                 testPutStatic(         NON_BLOCKING_WARMUP);
161                 testNewInstanceA(      NON_BLOCKING_WARMUP);
162                 testNewInstanceB(      NON_BLOCKING_WARMUP);
163 
164                 testGetField(new B(),      NON_BLOCKING_WARMUP);
165                 testPutField(new B(),      NON_BLOCKING_WARMUP);
166                 testInvokeVirtual(new B(), NON_BLOCKING_WARMUP);
167             }
168         }
169 
170         static void run() {
171             execute(ExceptionInInitializerError.class, () -> triggerInitialization(A.class));
172             ensureFinished();
173             runTests(); // after initialization is over
174         }
175     }
176 
177     // ============================================================================================================== //
178 
179     static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) {
180         try {
181             action.run();
182             if (THROW) throw failure("no exception thrown");
183         } catch (Throwable e) {
184             if (THROW) {
185                 if (e.getClass() == expectedExceptionClass) {
186                     // expected
187                 } else {
188                     String msg = String.format("unexpected exception thrown: expected %s, caught %s",
189                             expectedExceptionClass.getName(), e);
190                     throw failure(msg, e);
191                 }
192             } else {
193                 throw failure("no exception expected", e);
194             }
195         }
196     }
197 
198     private static AssertionError failure(String msg) {
199         return new AssertionError(phase + ": " + msg);
200     }
201 
202     private static AssertionError failure(String msg, Throwable e) {
203         return new AssertionError(phase + ": " + msg, e);
204     }
205 
206     static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>());
207     static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add;
208 
209     static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>());
210     static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put;
211 
212     private static void ensureBlocked() {
213         for (Thread thr : BLOCKED_THREADS) {
214             try {
215                 thr.join(100);
216                 if (!thr.isAlive()) {
217                     dump(thr);
218                     throw new AssertionError("not blocked");
219                 }
220             } catch (InterruptedException e) {
221                 throw new Error(e);
222             }
223         }
224     }
225 
226 
227     private static void ensureFinished() {
228         for (Thread thr : BLOCKED_THREADS) {
229             try {
230                 thr.join(15_000);
231             } catch (InterruptedException e) {
232                 throw new Error(e);
233             }
234             if (thr.isAlive()) {
235                 dump(thr);
236                 throw new AssertionError(thr + ": still blocked");
237             }
238         }
239         for (Thread thr : BLOCKED_THREADS) {
240             if (THROW) {
241                 if (!FAILED_THREADS.containsKey(thr)) {
242                     throw new AssertionError(thr + ": exception not thrown");
243                 }
244 
245                 Throwable ex = FAILED_THREADS.get(thr);
246                 if (ex.getClass() != NoClassDefFoundError.class) {
247                     throw new AssertionError(thr + ": wrong exception thrown", ex);
248                 }
249             } else {
250                 if (FAILED_THREADS.containsKey(thr)) {
251                     Throwable ex = FAILED_THREADS.get(thr);
252                     throw new AssertionError(thr + ": exception thrown", ex);
253                 }
254             }
255         }
256         if (THROW) {
257             Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
258         } else {
259             Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get());
260         }
261 
262         dumpInfo();
263     }
264 
265     interface TestCase0 {
266         void run(Runnable runnable);
267     }
268 
269     interface TestCase1<T> {
270         void run(T arg, Runnable runnable);
271     }
272 
273     enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE }
274 
275     static volatile Phase phase = Phase.BEFORE_INIT;
276 
277     static void changePhase(Phase newPhase) {
278         dumpInfo();
279 
280         Phase oldPhase = phase;
281         switch (oldPhase) {
282             case BEFORE_INIT:
283                 Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0);
284                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0);
285 
286                 Asserts.assertEquals(BLOCKING_ACTIONS.get(),     0);
287                 Asserts.assertEquals(BLOCKING_COUNTER.get(),     0);
288                 break;
289             case IN_PROGRESS:
290                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get());
291 
292                 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
293                 break;
294             default: throw new Error("wrong phase transition " + oldPhase);
295         }
296         phase = newPhase;
297     }
298 
299     static void dumpInfo() {
300         System.out.println("Phase: " + phase);
301         System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get());
302         System.out.println("Blocking actions:     " + BLOCKING_COUNTER.get()     + " / " + BLOCKING_ACTIONS.get());
303     }
304 
305     static final Runnable NON_BLOCKING_WARMUP = () -> {
306         if (phase != Phase.IN_PROGRESS) {
307             throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
308         }
309     };
310 
311     static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) {
312         actionCounter.incrementAndGet();
313 
314         final AtomicBoolean cnt = new AtomicBoolean(false);
315         return () -> {
316             if (cnt.getAndSet(true)) {
317                 throw new Error("repeated invocation");
318             }
319             invocationCounter.incrementAndGet();
320             if (phase != validPhase) {
321                 throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
322             }
323         };
324     }
325 
326     @FunctionalInterface
327     interface Factory<V> {
328         V get();
329     }
330 
331     static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0);
332     static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0);
333     static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(phase, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS);
334 
335     static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0);
336     static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0);
337     static final Factory<Runnable> BLOCKING     = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS);
338 
339     static void checkBlockingAction(TestCase0 r) {

340         switch (phase) {
341             case IN_PROGRESS: {
342                 // Barrier during class initalization.
343                 r.run(NON_BLOCKING.get());             // initializing thread
344                 checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread
345                 break;
346             }
347             case FINISHED: {
348                 // No barrier after class initalization is over.
349                 r.run(NON_BLOCKING.get()); // initializing thread
350                 checkNotBlocked(r);        // different thread
351                 break;
352             }
353             case INIT_FAILURE: {
354                 // Exception is thrown after class initialization failed.
355                 TestCase0 test = action -> execute(NoClassDefFoundError.class, () -> r.run(action));
356 
357                 test.run(NON_BLOCKING.get()); // initializing thread
358                 checkNotBlocked(test);        // different thread
359                 break;
360             }
361             default: throw new Error("wrong phase: " + phase);
362         }
363     }
364 
365     static void checkNonBlockingAction(TestCase0 r) {
366         r.run(NON_BLOCKING.get()); // initializing thread
367         checkNotBlocked(r);        // different thread
368     }
369 
370     static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) {
371         r.run(recv, NON_BLOCKING.get());                  // initializing thread
372         checkNotBlocked((action) -> r.run(recv, action)); // different thread
373     }
374 
375     static void checkFailingAction(TestCase0 r) {
376         r.run(NON_BLOCKING.get()); // initializing thread
377         checkNotBlocked(r);        // different thread
378     }
379 
380     static void triggerInitialization(Class<?> cls) {
381         try {
382             Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader());
383             if (loadedClass != cls) {
384                 throw new Error("wrong class");
385             }
386         } catch (ClassNotFoundException e) {
387             throw new Error(e);
388         }
389     }
390 
391     static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) {
392         Thread thr = new Thread(() -> {
393             try {
394                 r.run(BLOCKING.get());
395                 System.out.println("Thread " + Thread.currentThread() + ": Finished successfully");
396             } catch(Throwable e) {
397                 System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e);
398                 if (!THROW) {
399                     e.printStackTrace();
400                 }
401                 throw e;
402             }
403         } );
404         thr.setUncaughtExceptionHandler(onException);
405 
406         thr.start();
407         try {
408             thr.join(100);
409 
410             dump(thr);
411             if (thr.isAlive()) {
412                 onBlockHandler.accept(thr); // blocked
413             } else {
414                 throw new AssertionError("not blocked");
415             }
416         } catch (InterruptedException e) {
417             throw new Error(e);
418         }
419     }
420 
421     static void checkNotBlocked(TestCase0 r) {
422         final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get()));
423         final Throwable[] ex = new Throwable[1];
424         thr.setUncaughtExceptionHandler((t, e) -> {
425             if (thr != t) {
426                 ex[0] = new Error("wrong thread: " + thr + " vs " + t);
427             } else {
428                 ex[0] = e;
429             }
430         });
431 
432         thr.start();
433         try {
434             thr.join(15_000);
435             if (thr.isAlive()) {
436                 dump(thr);
437                 throw new AssertionError("blocked");
438             }
439         } catch (InterruptedException e) {
440             throw new Error(e);
441         }
442 
443         if (ex[0] != null) {
444             throw new AssertionError("no exception expected", ex[0]);
445         }
446     }
447 
448     static void maybeThrow() {
449         if (THROW) {
450             changePhase(Phase.INIT_FAILURE);
451             throw new RuntimeException("failed class initialization");
452         }
453     }
454 
455     private static void dump(Thread thr) {
456         System.out.println("Thread: " + thr);
457         System.out.println("Thread state: " + thr.getState());
458         if (thr.isAlive()) {
459             for (StackTraceElement frame : thr.getStackTrace()) {
460                 System.out.println(frame);
461             }
462         } else {
463             if (FAILED_THREADS.containsKey(thr)) {
464                 System.out.println("Failed with an exception: ");
465                 FAILED_THREADS.get(thr).toString();
466             } else {
467                 System.out.println("Finished successfully");
468             }
469         }
470     }
471 
472     public static void main(String[] args) throws Exception {
473         Test.run();
474         System.out.println("TEST PASSED");
475     }
476 }
--- EOF ---