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