< prev index next >

test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/NoClassDefFoundErrorTest.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.

@@ -21,31 +21,39 @@
  * questions.
  */
 
 /*
  * @test
- * @bug 8056900
+ * @bug 8056900 8203826
  * @summary Verifies message returned with NoClassDefFoundError exception.
  * @library /test/lib
  * @modules java.base/jdk.internal.misc
  *          java.compiler
- * @run main/native NoClassDefFoundMsg
+ * @build sun.hotspot.WhiteBox
+ * @run driver ClassFileInstaller sun.hotspot.WhiteBox
+ *                                sun.hotspot.WhiteBox$WhiteBoxPermission
+ * @run main/native/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
+ *                          -XX:+WhiteBoxAPI NoClassDefFoundErrorTest
  */
 
+import java.net.URL;
+import java.net.URLClassLoader;
+import jdk.test.lib.Asserts;
 import jdk.test.lib.compiler.InMemoryJavaCompiler;
 import jdk.internal.misc.Unsafe;
+import sun.hotspot.WhiteBox;
 
-public class NoClassDefFoundMsg {
+public class NoClassDefFoundErrorTest {
 
     static native void callDefineClass(String className);
     static native void callFindClass(String className);
     static {
         System.loadLibrary("NoClassDefFoundMsg");
     }
 
-
-    public static void main(String args[]) throws Exception {
+    // Test illegal class names. Too long or null.
+    static void test_classNames() throws Exception {
         Unsafe unsafe = Unsafe.getUnsafe();
 
         byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", "class TestClass { }");
 
         // Create a class name of length 65536.

@@ -92,6 +100,130 @@
             if (!e.getMessage().contains("No class name given")) {
                 throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage());
             }
         }
     }
+
+    static final WhiteBox wb = WhiteBox.getWhiteBox();
+    static ClassLoader cl;
+
+    public static class A {
+        public static final int i = (new Object[1])[2].hashCode();
+    }
+    public static class B extends A {}
+    public static class C extends B {}
+
+    static class ClassWithFailedInitializer {
+        public static final int i;
+        static {
+            cl = new URLClassLoader(new URL[] {
+                    ClassWithFailedInitializer.class.getProtectionDomain().getCodeSource().getLocation() }, null);
+            try {
+                cl.loadClass("NoClassDefFoundErrorTest$C").newInstance();
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException("Class should be found", e);
+            } catch (InstantiationException | IllegalAccessException e) {
+                throw new RuntimeException("This should not happen", e);
+            }
+            i = 42;
+        }
+    }
+
+    // If static initialization of a class failed, later access to it
+    // throws a NoClassDefFoundError. This error chains the exeption
+    // thrown in the previous initialization. This test assures this is
+    // working properly.
+    static void test_chainStaticInitializerException() throws Exception {
+        wb.fullGC();
+
+        try {
+            if (ClassWithFailedInitializer.i == 42) {
+                throw new RuntimeException("Class initialization succeeded but is designed to fail.");
+            }
+        }
+        catch (ExceptionInInitializerError e) {
+            e.printStackTrace();
+            Asserts.assertNE(e.getCause(), null, "Expecting cause in ExceptionInInitializerError.");
+            Asserts.assertEQ(e.getCause().getClass(), ArrayIndexOutOfBoundsException.class, "sanity");
+        }
+
+        try {
+            // The second attempt to access a field in 'ClassWithFailedInitializer' should result in a
+            // 'NoClassDefFoundError' instead of an 'ExceptionInInitializerError' because the class is
+            // already in an erro state and its static intializer won't be evaluated one more time (see
+            // JVMS, 5.5 Initialization).
+            // Here we also check that after 8203826 this initial ExceptionInInitializerError is saved
+            // for class 'ClassWithFailedInitializer' and chained as cause into the NoClassDefFoundError
+            // thrown during the second attempt to access a field in 'ClassWithFailedInitializer'.
+            if (ClassWithFailedInitializer.i == 42) {
+                throw new RuntimeException("Class initialization succeeded but is designed to fail.");
+            }
+        } catch (NoClassDefFoundError e) {
+            e.printStackTrace();
+            Asserts.assertNE(e.getCause(), null,
+                             "Expecting chained ExceptionInInitializerError exception");
+            Asserts.assertTrue(e.getCause().toString()
+                                .startsWith(ExceptionInInitializerError.class.getName()),
+                               "Wrong exception chained: " + e.getCause());
+            Asserts.assertNE(e.getCause().getCause(), null,
+                             "Expecting chained ArrayIndexOutOfBoundsException exception");
+            Asserts.assertTrue(e.getCause().getCause().toString()
+                                .startsWith(ArrayIndexOutOfBoundsException.class.getName()),
+                               "sanity");
+        }
+
+        try {
+            // Instantiating class 'B' from the same class loader which was already used by the static
+            // initializer of 'ClassWithFailedInitializer' to load class 'C' (and thus transitively
+            // 'B' and 'A') should result in a NoClassDefFoundError instead of an
+            // ExceptionInInitializerError because class 'B' is already in an error state because of
+            // the failure in the static initializer of its base class 'A' in the previous loading attemt.
+            // Here we check that after 8203826 this initial ExceptionInInitializerError is saved
+            // for class 'B' and chained as cause into the NoClassDefFoundError thrown during the second
+            // loading attempt for class 'B'.
+            Object b = cl.loadClass("NoClassDefFoundErrorTest$B").newInstance();
+            throw new RuntimeException("Class initialization succeeded but is designed to fail.");
+        } catch (NoClassDefFoundError e) {
+            e.printStackTrace();
+            Asserts.assertNE(e.getCause(), null,
+                             "Expecting chained ExceptionInInitializerError exception");
+            Asserts.assertTrue(e.getCause().toString()
+                                .startsWith(ExceptionInInitializerError.class.getName()),
+                               "Wrong exception chained: " + e.getCause());
+            Asserts.assertNE(e.getCause().getCause(), null,
+                             "Expecting chained ArrayIndexOutOfBoundsException exception");
+            Asserts.assertTrue(e.getCause().getCause().toString()
+                                .startsWith(ArrayIndexOutOfBoundsException.class.getName()),
+                               "sanity");
+        }
+
+        // Accessing 'ClassWithFailedInitializer.i' above triggers the loading of it's base class 'A'.
+        // At this point however, class 'A' shouldn't be referenced from anywhere else except from the
+        // custom URLCLassLoader 'cl' which loaded 'A'.
+        // Notice that change 8203826 introduced the saving of the ExceptionInInitializerError which
+        // was thrown during the execution of A's static initializer (together with the recursively
+        // chained ArrayIndexOutOfBoundsException) inside a java.util.Hashtable object attached to the
+        // ClassLoaderData of A's class loader (see ClassLoaderData::record_init_exception() in
+        // hotspot/share/classfile/classLoaderData.cpp). This saved exception potentially references
+        // class 'A' in its native backtrace.
+        // Change 8203826 however, also takes care to remove all native backtraces from chained exceptions
+        // in order to prevent keeping the classes alive which are referenced from them. In order to do that
+        // all saved exceptions are converted to exceptions with a symbolic stack trace and a nulled-out
+        // backtrace (see java.lang.Throwable::removeNativeBacktrace()).
+
+        // At this point, class 'A' should be alive (but only referenced from its class loader).
+        Asserts.assertEQ(wb.isClassAlive("NoClassDefFoundErrorTest$A"), true,
+                         "NoClassDefFoundErrorTest.A should be alive.");
+
+        cl = null;
+        wb.fullGC();
+
+        // After freeing the last reference to the class loader and doing a full GC, class 'A' should be unloaded.
+        Asserts.assertEQ(wb.isClassAlive("NoClassDefFoundErrorTest$A"), false,
+                         "NoClassDefFoundErrorTest.A should be dead at this point.");
+    }
+
+    public static void main(String args[]) throws Exception {
+        test_classNames();
+        test_chainStaticInitializerException();
+    }
 }
< prev index next >