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