< prev index next >

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

Print this page
rev 50866 : 8203826: Chain exception from initialization in later NoClassDefFoundErrors.

*** 1,7 **** /* ! * Copyright (c) 2017, 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. --- 1,7 ---- /* ! * 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,51 **** * questions. */ /* * @test ! * @bug 8056900 * @summary Verifies message returned with NoClassDefFoundError exception. * @library /test/lib * @modules java.base/jdk.internal.misc * java.compiler ! * @run main/native NoClassDefFoundMsg */ import jdk.test.lib.compiler.InMemoryJavaCompiler; import jdk.internal.misc.Unsafe; ! public class NoClassDefFoundMsg { static native void callDefineClass(String className); static native void callFindClass(String className); static { System.loadLibrary("NoClassDefFoundMsg"); } ! ! public static void main(String args[]) throws Exception { Unsafe unsafe = Unsafe.getUnsafe(); byte klassbuf[] = InMemoryJavaCompiler.compile("TestClass", "class TestClass { }"); // Create a class name of length 65536. --- 21,59 ---- * 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.
*** 92,97 **** --- 100,223 ---- 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 | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Class should be found", 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 == 0) { + throw new RuntimeException("Class initialization succeeded but is designed to fail."); + } + throw new Exception("Expected exception was not thrown."); + } + 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 == 0) { + throw new RuntimeException("Class initialization succeeded but is designed to fail."); + } + throw new Exception("Expected exception was not thrown."); + } catch (NoClassDefFoundError e) { + e.printStackTrace(); + Asserts.assertNE(e.getCause(), null, + "Expecting chained ExceptionInInitializerError exception"); + Asserts.assertEQ(e.getCause().getClass(), ExceptionInInitializerError.class, + "Wrong exception chained: " + e.getCause()); + Asserts.assertNE(e.getCause().getCause(), null, + "Expecting chained ArrayIndexOutOfBoundsException exception"); + Asserts.assertEQ(e.getCause().getCause().getClass(), ArrayIndexOutOfBoundsException.class, "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.assertEQ(e.getCause().getClass(), ExceptionInInitializerError.class, + "Wrong exception chained: " + e.getCause()); + Asserts.assertNE(e.getCause().getCause(), null, + "Expecting chained ArrayIndexOutOfBoundsException exception"); + Asserts.assertEQ(e.getCause().getCause().getClass(), ArrayIndexOutOfBoundsException.class, "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 >