--- old/src/share/classes/java/lang/Throwable.java 2011-04-05 18:16:46.000000000 -0700 +++ new/src/share/classes/java/lang/Throwable.java 2011-04-05 18:16:46.000000000 -0700 @@ -129,6 +129,43 @@ */ private String detailMessage; + + /** + * Holder class to defer initializing sentinel objects only used + * for serialization. + */ + private static class SentinelHolder { + /** + * {@linkplain #setStackTrace(StackTraceElement[]) Setting the + * stack trace} to a one-element array containing this sentinel + * value indicates future attempts to set the stack trace will be + * ignored. The sentinal is equal to the result of calling:
+ * {@code new StackTraceElement("", "", null, Integer.MIN_VALUE)} + */ + public static final StackTraceElement STACK_TRACE_ELEMENT_SENTINEL = + new StackTraceElement("", "", null, Integer.MIN_VALUE); + + /** + * Sentinel value used in the serial form to indicate an immutable + * stack trace. + */ + public static final StackTraceElement[] STACK_TRACE_SENTINEL = + new StackTraceElement[] {STACK_TRACE_ELEMENT_SENTINEL}; + } + + /** + * A value indicating the stack trace field has not yet been initialized. + */ + private static final StackTraceElement[] UNINITIALIZED_STACK = + new StackTraceElement[] {new StackTraceElement("UNINITIALIZED", "STACK", null, -1)}; + + /** + * A value indicating that the logical stack trace has been + * populated into the backtrace field. + */ + private static final StackTraceElement[] FILLED_IN_STACK = + new StackTraceElement[] {new StackTraceElement("FILLED_IN", "STACK", null, -1)}; + /** * A shared value for an empty stack. */ @@ -137,8 +174,9 @@ /* * To allow Throwable objects to be made immutable and safely * reused by the JVM, such as OutOfMemoryErrors, fields of - * Throwable that are writable in response to user actions, cause - * and suppressedExceptions obey the following protocol: + * Throwable that are writable in response to user actions, cause, + * stackTrace, and suppressedExceptions obey the following + * protocol: * * 1) The fields are initialized to a non-null sentinel value * which indicates the value has logically not been set. @@ -174,10 +212,14 @@ /** * The stack trace, as returned by {@link #getStackTrace()}. * + * The field is initialized to a zero-length array. A {@code + * null} value of this field indicates subsequent calls to {@link + * #setStackTrace()} and {@link #fillInStackTrace} will be be no-ops. + * * @serial * @since 1.4 */ - private StackTraceElement[] stackTrace; + private StackTraceElement[] stackTrace = UNINITIALIZED_STACK; // Setting this static field introduces an acceptable // initialization dependency on a few java.util classes. @@ -707,10 +749,22 @@ * {@code Throwable} object information about the current state of * the stack frames for the current thread. * + *

If the stack trace of this {@code Throwable} {@linkplain + * Throwable#setStackTrace(StackTraceElement[]) has been set} to + * {@code null}, calling this method has no effect. + * * @return a reference to this {@code Throwable} instance. * @see java.lang.Throwable#printStackTrace() */ - public synchronized native Throwable fillInStackTrace(); + public synchronized Throwable fillInStackTrace() { + if (stackTrace != null) { + fillInStackTrace(0); + stackTrace = FILLED_IN_STACK; + } + return this; + } + + private native Throwable fillInStackTrace(int dummy); /** * Provides programmatic access to the stack trace information printed by @@ -740,12 +794,15 @@ } private synchronized StackTraceElement[] getOurStackTrace() { - // Initialize stack trace if this is the first call to this method - if (stackTrace == null) { + // Initialize stack trace field with information from + // backtrace if this is the first call to this method + if (stackTrace == FILLED_IN_STACK) { int depth = getStackTraceDepth(); stackTrace = new StackTraceElement[depth]; for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i); + } else if (stackTrace == null) { + return EMPTY_STACK; } return stackTrace; } @@ -761,23 +818,36 @@ * when a throwable is constructed or deserialized when a throwable is * read from a serialization stream. * + *

If the stack trace is set to {@code null}, then future calls + * to this method have no effect on this {@code Throwable}. + * * @param stackTrace the stack trace elements to be associated with * this {@code Throwable}. The specified array is copied by this * call; changes in the specified array after the method invocation * returns will have no affect on this {@code Throwable}'s stack * trace. * - * @throws NullPointerException if {@code stackTrace} is - * {@code null}, or if any of the elements of + * @throws NullPointerException if any of the elements of * {@code stackTrace} are {@code null} * * @since 1.4 */ public void setStackTrace(StackTraceElement[] stackTrace) { - StackTraceElement[] defensiveCopy = stackTrace.clone(); - for (int i = 0; i < defensiveCopy.length; i++) - if (defensiveCopy[i] == null) - throw new NullPointerException("stackTrace[" + i + "]"); + if (this.stackTrace == null) // Immutable stack + return; + + StackTraceElement[] defensiveCopy; + + if (stackTrace == null) { + defensiveCopy = stackTrace; + } else { + defensiveCopy = stackTrace.clone(); + + for (int i = 0; i < defensiveCopy.length; i++) { + if (defensiveCopy[i] == null) + throw new NullPointerException("stackTrace[" + i + "]"); + } + } synchronized (this) { this.stackTrace = defensiveCopy; @@ -808,7 +878,10 @@ * well-formedness constraints on fields. Null entries and * self-pointers are not allowed in the list of {@code * suppressedExceptions}. Null entries are not allowed for stack - * trace elements. + * trace elements. A single-element stack trace whose entry is + * equal to {@code new StackTraceElement("", "", null, + * Integer.MIN_VALUE)} results in a {@code null} {@code + * stackTrace} field. * * Note that there are no constraints on the value the {@code * cause} field can hold; both {@code null} and {@code this} are @@ -837,26 +910,52 @@ suppressedExceptions = suppressed; } // else a null suppressedExceptions field remains null + // Check for the marker of an immutable stack trace if (stackTrace != null) { - for (StackTraceElement ste : stackTrace) { - if (ste == null) - throw new NullPointerException("null StackTraceElement in serial stream. "); + // Share zero-length stack traces + if (stackTrace.length == 0) { + stackTrace = EMPTY_STACK; + } else if (stackTrace.length == 1 && + SentinelHolder.STACK_TRACE_ELEMENT_SENTINEL.equals(stackTrace[0])) { + stackTrace = null; + } else { // Verify stack trace elements are non-null. + for(StackTraceElement ste : stackTrace) { + if (ste == null) + throw new NullPointerException("null StackTraceElement in serial stream. "); + } } - } else { - // A null stackTrace field in the serial form can result from - // an exception serialized without that field in older JDK releases. - stackTrace = EMPTY_STACK; } - + // A null stackTrace field in the serial form can result from + // an exception serialized without that field. Such exceptions + // are now treated as having immutable stack traces. } /** * Write a {@code Throwable} object to a stream. + * + * A {@code null} stack trace field is represented in the serial + * form as a one-element array whose element is equal to {@code + * new StackTraceElement("", "", null, Integer.MIN_VALUE)}. */ private synchronized void writeObject(ObjectOutputStream s) throws IOException { - getOurStackTrace(); // Ensure that stackTrace field is initialized. - s.defaultWriteObject(); + // Ensure that the stackTrace field is initialized to a + // non-null value, if appropriate. As of JDK 7, a null stack + // trace field is a valid value indicating the stack trace + // should not be set. + getOurStackTrace(); + ObjectOutputStream.PutField fields = s.putFields(); + + fields.put("detailMessage", detailMessage); + fields.put("cause", cause); + // Serialize a null stacktrace using the stack trace sentinel. + if (stackTrace == null) + fields.put("stackTrace", SentinelHolder.STACK_TRACE_SENTINEL); + else + fields.put("stackTrace", stackTrace); + fields.put("suppressedExceptions", suppressedExceptions); + + s.writeFields(); } /** @@ -933,7 +1032,7 @@ * statement, in order to deliver this exception. * * If no exceptions were suppressed or {@linkplain - * Throwable(String, Throwable, boolean) suppression is disabled}, + * #Throwable(String, Throwable, boolean) suppression is disabled}, * an empty array is returned. * * @return an array containing all of the exceptions that were --- old/src/share/native/java/lang/Throwable.c 2011-04-05 18:16:47.000000000 -0700 +++ new/src/share/native/java/lang/Throwable.c 2011-04-05 18:16:47.000000000 -0700 @@ -44,7 +44,7 @@ * `this' so you can write 'throw e.fillInStackTrace();' */ JNIEXPORT jobject JNICALL -Java_java_lang_Throwable_fillInStackTrace(JNIEnv *env, jobject throwable) +Java_java_lang_Throwable_fillInStackTrace(JNIEnv *env, jobject throwable, int dummy) { JVM_FillInStackTrace(env, throwable); return throwable; --- old/test/java/lang/Throwable/ChainedExceptions.java 2011-04-05 18:16:48.000000000 -0700 +++ new/test/java/lang/Throwable/ChainedExceptions.java 2011-04-05 18:16:47.000000000 -0700 @@ -13,28 +13,28 @@ StackTraceElement[] highTrace = e.getStackTrace(); int depthTrim = highTrace.length - 2; - check(highTrace[0], "a", 48); - check(highTrace[1], "main", 11); + check(e, highTrace[0], "a", 48); + check(e, highTrace[1], "main", 11); Throwable mid = e.getCause(); StackTraceElement[] midTrace = mid.getStackTrace(); if (midTrace.length - depthTrim != 4) throw new RuntimeException("Mid depth"); - check(midTrace[0], "c", 58); - check(midTrace[1], "b", 52); - check(midTrace[2], "a", 46); - check(midTrace[3], "main", 11); + check(mid, midTrace[0], "c", 58); + check(mid, midTrace[1], "b", 52); + check(mid, midTrace[2], "a", 46); + check(mid, midTrace[3], "main", 11); Throwable low = mid.getCause(); StackTraceElement[] lowTrace = low.getStackTrace(); if (lowTrace.length - depthTrim != 6) throw new RuntimeException("Low depth"); - check(lowTrace[0], "e", 65); - check(lowTrace[1], "d", 62); - check(lowTrace[2], "c", 56); - check(lowTrace[3], "b", 52); - check(lowTrace[4], "a", 46); - check(lowTrace[5], "main", 11); + check(low, lowTrace[0], "e", 65); + check(low, lowTrace[1], "d", 62); + check(low, lowTrace[2], "c", 56); + check(low, lowTrace[3], "b", 52); + check(low, lowTrace[4], "a", 46); + check(low, lowTrace[5], "main", 11); if (low.getCause() != null) throw new RuntimeException("Low cause != null"); @@ -68,15 +68,15 @@ private static final String OUR_CLASS = ChainedExceptions.class.getName(); private static final String OUR_FILE_NAME = "ChainedExceptions.java"; - private static void check(StackTraceElement e, String methodName, int n) { + private static void check(Throwable t, StackTraceElement e, String methodName, int n) { if (!e.getClassName().equals(OUR_CLASS)) - throw new RuntimeException("Class: " + e); + throw new RuntimeException("Class: " + e, t); if (!e.getMethodName().equals(methodName)) - throw new RuntimeException("Method name: " + e); + throw new RuntimeException("Method name: " + e, t); if (!e.getFileName().equals(OUR_FILE_NAME)) - throw new RuntimeException("File name: " + e); + throw new RuntimeException("File name: " + e, t); if (e.getLineNumber() != n) - throw new RuntimeException("Line number: " + e); + throw new RuntimeException("Line number: " + e, t); } } --- old/test/java/lang/Throwable/StackTraceSerialization.java 2011-04-05 18:16:48.000000000 -0700 +++ new/test/java/lang/Throwable/StackTraceSerialization.java 2011-04-05 18:16:48.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -26,7 +26,7 @@ /* * @test - * @bug 4202914 4363318 6991528 + * @bug 4202914 4363318 6991528 6998871 * @summary Basic test of serialization of stack trace information * @author Josh Bloch */ @@ -37,14 +37,28 @@ testWithFillInStackTrace(); } - private static void testWithSetStackTrace() throws Exception { + private static void testWithSetStackTrace() { + StackTraceElement[] stackTrace = {new StackTraceElement("foo", "bar", "baz", -1)}; + Throwable t = new Throwable(); + t.setStackTrace(null); // Immutable and empty stack + assertEmptyStackTrace(t); + + // Verify fillInStackTrace is now a no-op. + t.fillInStackTrace(); + assertEmptyStackTrace(t); - t.setStackTrace(new StackTraceElement[] - {new StackTraceElement("foo", "bar", "baz", -1)}); + t.setStackTrace(stackTrace); + assertEmptyStackTrace(t); if (!equal(t, reconstitute(t))) - throw new Exception("Unequal Throwables with set stacktrace"); + throw new RuntimeException("Unequal Throwables with set stacktrace"); + + Throwable t2 = new Throwable(); + t2.setStackTrace(stackTrace); + if (!equal(t2, reconstitute(t2))) + throw new RuntimeException("Unequal Throwables with set stacktrace"); + } private static void assertEmptyStackTrace(Throwable t) { @@ -52,7 +66,7 @@ throw new AssertionError("Nonempty stacktrace."); } - private static void testWithFillInStackTrace() throws Exception { + private static void testWithFillInStackTrace() { Throwable original = null; try { a(); @@ -61,16 +75,14 @@ } if (!equal(original, reconstitute(original))) - throw new Exception("Unequal Throwables with filled-in stacktrace"); + throw new RuntimeException("Unequal Throwables with filled-in stacktrace"); } - - + /** * Serialize the argument and return the deserialized result. */ - private static Throwable reconstitute(Throwable t) throws Exception { + private static Throwable reconstitute(Throwable t) { Throwable result = null; - try(ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout)) { out.writeObject(t); @@ -80,8 +92,9 @@ ObjectInputStream in = new ObjectInputStream(bin)) { result = (Throwable) in.readObject(); } + } catch(IOException | ClassNotFoundException e) { + throw new RuntimeException(e); } - return result; }