/* * 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. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8056900 8203826 * @summary Verifies message returned with NoClassDefFoundError exception. * @library /test/lib * @modules java.base/jdk.internal.misc * java.compiler * @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 NoClassDefFoundErrorTest { static native void callDefineClass(String className); static native void callFindClass(String className); static { System.loadLibrary("NoClassDefFoundMsg"); } // 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. StringBuilder tooBigClassName = new StringBuilder("z"); for (int x = 0; x < 16; x++) { tooBigClassName = tooBigClassName.append(tooBigClassName); } // Test JVM_DefineClass() with long name. try { unsafe.defineClass(tooBigClassName.toString(), klassbuf, 4, klassbuf.length - 4, null, null); throw new RuntimeException("defineClass did not throw expected NoClassDefFoundError"); } catch (NoClassDefFoundError e) { if (!e.getMessage().contains("Class name exceeds maximum length of ")) { throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); } } // Test JNI_DefineClass() with long name. try { callDefineClass(tooBigClassName.toString()); throw new RuntimeException("DefineClass did not throw expected NoClassDefFoundError"); } catch (NoClassDefFoundError e) { if (!e.getMessage().contains("Class name exceeds maximum length of ")) { throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); } } // Test JNI_FindClass() with long name. try { callFindClass(tooBigClassName.toString()); throw new RuntimeException("DefineClass did not throw expected NoClassDefFoundError"); } catch (NoClassDefFoundError e) { if (!e.getMessage().contains("Class name exceeds maximum length of ")) { throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); } } // Test JNI_FindClass() with null name. try { callFindClass(null); throw new RuntimeException("FindClass did not throw expected NoClassDefFoundError"); } catch (NoClassDefFoundError e) { 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(); } }