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             ensureFinished();
 144             runTests(); // after initialization is over
 145         }
 146     }
 147 
 148     // ============================================================================================================== //
 149 
 150     static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) {
 151         try {
 152             action.run();
 153             if (THROW) throw failure("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 failure(msg, e);
 162                 }
 163             } else {
 164                 throw failure("no exception expected", e);
 165             }
 166         }
 167     }
 168 
 169     private static AssertionError failure(String msg) {
 170         return new AssertionError(phase + ": " + msg);
 171     }
 172 
 173     private static AssertionError failure(String msg, Throwable e) {
 174         return new AssertionError(phase + ": " + msg, e);
 175     }
 176 
 177     static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>());
 178     static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add;
 179 
 180     static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>());
 181     static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put;
 182 
 183     private static void ensureBlocked() {
 184         for (Thread thr : BLOCKED_THREADS) {
 185             try {
 186                 thr.join(100);
 187                 if (!thr.isAlive()) {
 188                     dump(thr);
 189                     throw new AssertionError("not blocked");
 190                 }
 191             } catch (InterruptedException e) {
 192                 throw new Error(e);
 193             }
 194         }
 195     }
 196 
 197 
 198     private static void ensureFinished() {
 199         for (Thread thr : BLOCKED_THREADS) {
 200             try {
 201                 thr.join(15_000);
 202             } catch (InterruptedException e) {
 203                 throw new Error(e);
 204             }
 205             if (thr.isAlive()) {
 206                 dump(thr);
 207                 throw new AssertionError(thr + ": still blocked");
 208             }
 209         }
 210         for (Thread thr : BLOCKED_THREADS) {
 211             if (THROW) {
 212                 if (!FAILED_THREADS.containsKey(thr)) {
 213                     throw new AssertionError(thr + ": exception not thrown");
 214                 }
 215 
 216                 Throwable ex = FAILED_THREADS.get(thr);
 217                 if (ex.getClass() != NoClassDefFoundError.class) {
 218                     throw new AssertionError(thr + ": wrong exception thrown", ex);
 219                 }
 220             } else {
 221                 if (FAILED_THREADS.containsKey(thr)) {
 222                     Throwable ex = FAILED_THREADS.get(thr);
 223                     throw new AssertionError(thr + ": exception thrown", ex);
 224                 }
 225             }
 226         }
 227         if (THROW) {
 228             Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
 229         } else {
 230             Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get());
 231         }
 232 
 233         dumpInfo();
 234     }
 235 
 236     interface TestCase0 {
 237         void run(Runnable runnable);
 238     }
 239 
 240     interface TestCase1<T> {
 241         void run(T arg, Runnable runnable);
 242     }
 243 
 244     enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE }
 245 
 246     static volatile Phase phase = Phase.BEFORE_INIT;
 247 
 248     static void changePhase(Phase newPhase) {
 249         dumpInfo();
 250 
 251         Phase oldPhase = phase;
 252         switch (oldPhase) {
 253             case BEFORE_INIT:
 254                 Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0);
 255                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0);
 256 
 257                 Asserts.assertEquals(BLOCKING_ACTIONS.get(),     0);
 258                 Asserts.assertEquals(BLOCKING_COUNTER.get(),     0);
 259                 break;
 260             case IN_PROGRESS:
 261                 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get());
 262 
 263                 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
 264                 break;
 265             default: throw new Error("wrong phase transition " + oldPhase);
 266         }
 267         phase = newPhase;
 268     }
 269 
 270     static void dumpInfo() {
 271         System.out.println("Phase: " + phase);
 272         System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get());
 273         System.out.println("Blocking actions:     " + BLOCKING_COUNTER.get()     + " / " + BLOCKING_ACTIONS.get());
 274     }
 275 
 276     static final Runnable NON_BLOCKING_WARMUP = () -> {
 277         if (phase != Phase.IN_PROGRESS) {
 278             throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
 279         }
 280     };
 281 
 282     static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) {
 283         actionCounter.incrementAndGet();
 284 
 285         final AtomicBoolean cnt = new AtomicBoolean(false);
 286         return () -> {
 287             if (cnt.getAndSet(true)) {
 288                 throw new Error("repeated invocation");
 289             }
 290             invocationCounter.incrementAndGet();
 291             if (phase != validPhase) {
 292                 throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
 293             }
 294         };
 295     }
 296 
 297     @FunctionalInterface
 298     interface Factory<V> {
 299         V get();
 300     }
 301 
 302     static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0);
 303     static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0);
 304     static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(phase, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS);
 305 
 306     static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0);
 307     static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0);
 308     static final Factory<Runnable> BLOCKING     = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS);
 309 
 310     static void checkBlockingAction(TestCase0 r) {
 311         switch (phase) {
 312             case IN_PROGRESS: {
 313                 // Barrier during class initalization.
 314                 r.run(NON_BLOCKING.get());             // initializing thread
 315                 checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread
 316                 break;
 317             }
 318             case FINISHED: {
 319                 // No barrier after class initalization is over.
 320                 r.run(NON_BLOCKING.get()); // initializing thread
 321                 checkNotBlocked(r);        // different thread
 322                 break;
 323             }
 324             case INIT_FAILURE: {
 325                 // Exception is thrown after class initialization failed.
 326                 TestCase0 test = action -> execute(NoClassDefFoundError.class, () -> r.run(action));
 327 
 328                 test.run(NON_BLOCKING.get()); // initializing thread
 329                 checkNotBlocked(test);        // different thread
 330                 break;
 331             }
 332             default: throw new Error("wrong phase: " + phase);
 333         }
 334     }
 335 
 336     static void checkNonBlockingAction(TestCase0 r) {
 337         r.run(NON_BLOCKING.get()); // initializing thread
 338         checkNotBlocked(r);        // different thread
 339     }
 340 
 341     static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) {
 342         r.run(recv, NON_BLOCKING.get());                  // initializing thread
 343         checkNotBlocked((action) -> r.run(recv, action)); // different thread
 344     }
 345 
 346     static void checkFailingAction(TestCase0 r) {
 347         r.run(NON_BLOCKING.get()); // initializing thread
 348         checkNotBlocked(r);        // different thread
 349     }
 350 
 351     static void triggerInitialization(Class<?> cls) {
 352         try {
 353             Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader());
 354             if (loadedClass != cls) {
 355                 throw new Error("wrong class");
 356             }
 357         } catch (ClassNotFoundException e) {
 358             throw new Error(e);
 359         }
 360     }
 361 
 362     static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) {
 363         Thread thr = new Thread(() -> {
 364             try {
 365                 r.run(BLOCKING.get());
 366                 System.out.println("Thread " + Thread.currentThread() + ": Finished successfully");
 367             } catch(Throwable e) {
 368                 System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e);
 369                 if (!THROW) {
 370                     e.printStackTrace();
 371                 }
 372                 throw e;
 373             }
 374         } );
 375         thr.setUncaughtExceptionHandler(onException);
 376 
 377         thr.start();
 378         try {
 379             thr.join(100);
 380 
 381             dump(thr);
 382             if (thr.isAlive()) {
 383                 onBlockHandler.accept(thr); // blocked
 384             } else {
 385                 throw new AssertionError("not blocked");
 386             }
 387         } catch (InterruptedException e) {
 388             throw new Error(e);
 389         }
 390     }
 391 
 392     static void checkNotBlocked(TestCase0 r) {
 393         final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get()));
 394         final Throwable[] ex = new Throwable[1];
 395         thr.setUncaughtExceptionHandler((t, e) -> {
 396             if (thr != t) {
 397                 ex[0] = new Error("wrong thread: " + thr + " vs " + t);
 398             } else {
 399                 ex[0] = e;
 400             }
 401         });
 402 
 403         thr.start();
 404         try {
 405             thr.join(15_000);
 406             if (thr.isAlive()) {
 407                 dump(thr);
 408                 throw new AssertionError("blocked");
 409             }
 410         } catch (InterruptedException e) {
 411             throw new Error(e);
 412         }
 413 
 414         if (ex[0] != null) {
 415             throw new AssertionError("no exception expected", ex[0]);
 416         }
 417     }
 418 
 419     static void maybeThrow() {
 420         if (THROW) {
 421             changePhase(Phase.INIT_FAILURE);
 422             throw new RuntimeException("failed class initialization");
 423         }
 424     }
 425 
 426     private static void dump(Thread thr) {
 427         System.out.println("Thread: " + thr);
 428         System.out.println("Thread state: " + thr.getState());
 429         if (thr.isAlive()) {
 430             for (StackTraceElement frame : thr.getStackTrace()) {
 431                 System.out.println(frame);
 432             }
 433         } else {
 434             if (FAILED_THREADS.containsKey(thr)) {
 435                 System.out.println("Failed with an exception: ");
 436                 FAILED_THREADS.get(thr).toString();
 437             } else {
 438                 System.out.println("Finished successfully");
 439             }
 440         }
 441     }
 442 
 443     public static void main(String[] args) throws Exception {
 444         Test.run();
 445         System.out.println("TEST PASSED");
 446     }
 447 }