--- old/src/java.base/share/classes/java/lang/ClassLoader.java 2018-07-06 15:39:25.413208011 +0000 +++ new/src/java.base/share/classes/java/lang/ClassLoader.java 2018-07-06 15:39:25.303207229 +0000 @@ -60,6 +60,7 @@ import java.util.stream.StreamSupport; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.ClassLoaderValue; import jdk.internal.perf.PerfCounter; import jdk.internal.loader.BootLoader; import jdk.internal.loader.ClassLoaders; @@ -2970,6 +2971,93 @@ private static native AssertionStatusDirectives retrieveDirectives(); + // A RuntimeException that acts as a substitute for the original exception + // (checked or unchecked) and mimics the original exception in every aspect + // except it's type. + private static class InitExceptionSubst extends RuntimeException { + static final ClassLoaderValue INIT_EXCEPTIONS = + new ClassLoaderValue<>(); + + private static final long serialVersionUID = 1; + + private final String threadName; + private final long time; + private final String originalExceptionClassName; + private final String localizedMessage; + + InitExceptionSubst(Throwable originalException, boolean top) { + super(originalException.getMessage()); + + this.threadName = top ? Thread.currentThread().getName() : null; + this.time = top ? System.currentTimeMillis() : 0L; + this.originalExceptionClassName = originalException.getClass().getName(); + this.localizedMessage = originalException.getLocalizedMessage(); + + // substitute originalException's cause + Throwable cause = originalException.getCause(); + initCause(cause == null ? null : new InitExceptionSubst(cause, false)); + + // substitute originalException's suppressed exceptions if any + for (Throwable suppressed : originalException.getSuppressed()) { + addSuppressed(new InitExceptionSubst(suppressed, false)); + } + + // inherit stack trace elements from originalException + setStackTrace(originalException.getStackTrace()); + } + + @Override + public Throwable fillInStackTrace() { + // don't need our backtrace - + // will inherit stack trace elements from originalException + return this; + } + + @Override + public String getLocalizedMessage() { + return localizedMessage; + } + + @Override + public String toString() { + // Emulate toString() method as if called upon originalException + String message = getLocalizedMessage(); + return time == 0L + ? (message != null + ? originalExceptionClassName + ": " + message + : originalExceptionClassName + ) + : (message != null + ? originalExceptionClassName + ": " + + (System.currentTimeMillis() - time) + " ms ago in thread " + + threadName + ": " + message + : originalExceptionClassName + ": " + + (System.currentTimeMillis() - time) + " ms ago in thread " + + threadName + ); + } + } + + // Called by the VM as notification just before exception + // (typically ExceptionInInitializerError) is thrown after unsuccessful + // attempt to initialize the class for the 1st time + private static void recordInitException(Class clazz, Throwable exc) { + InitExceptionSubst.INIT_EXCEPTIONS + .sub(clazz) + .putIfAbsent(clazz.getClassLoader0(), new InitExceptionSubst(exc, true)); + } + + // Called by the vm to throw NoClassDefFoundError as a consequence of + // class re-initialization attempt after the 1st class initialization + // attempt failed + private static void throwReinitException(Class clazz) { + InitExceptionSubst cause = InitExceptionSubst.INIT_EXCEPTIONS + .sub(clazz) + .get(clazz.getClassLoader0()); + throw (Error) new NoClassDefFoundError( + "Could not initialize " + clazz).initCause(cause); + } + // -- Misc -- /**