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