--- old/src/share/classes/java/lang/Error.java 2011-04-07 16:25:58.000000000 -0700 +++ new/src/share/classes/java/lang/Error.java 2011-04-07 16:25:57.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -79,7 +79,7 @@ * @param message the detail message (which is saved for later retrieval * by the {@link #getMessage()} method). * @param cause the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A null value is + * {@link #getCause()} method). (A {@code null} value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 @@ -90,13 +90,13 @@ /** * Constructs a new error with the specified cause and a detail - * message of (cause==null ? null : cause.toString()) (which - * typically contains the class and detail message of cause). + * message of {@code (cause==null ? null : cause.toString())} (which + * typically contains the class and detail message of {@code cause}). * This constructor is useful for errors that are little more than * wrappers for other throwables. * * @param cause the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A null value is + * {@link #getCause()} method). (A {@code null} value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 @@ -104,4 +104,25 @@ public Error(Throwable cause) { super(cause); } + + /** + * Constructs a new error with the specified detail message, + * cause, suppression enabled or disabled, and writable stack + * trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * + * @since 1.7 + */ + protected Error(String message, Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } --- old/src/share/classes/java/lang/Exception.java 2011-04-07 16:25:58.000000000 -0700 +++ new/src/share/classes/java/lang/Exception.java 2011-04-07 16:25:58.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -101,4 +101,24 @@ public Exception(Throwable cause) { super(cause); } + + /** + * Constructs a new exception with the specified detail message, + * cause, suppression enabled or disabled, and writable stack + * trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * @since 1.7 + */ + protected Exception(String message, Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } --- old/src/share/classes/java/lang/RuntimeException.java 2011-04-07 16:25:59.000000000 -0700 +++ new/src/share/classes/java/lang/RuntimeException.java 2011-04-07 16:25:59.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -95,4 +95,25 @@ public RuntimeException(Throwable cause) { super(cause); } + + /** + * Constructs a new runtime exception with the specified detail + * message, cause, suppression enabled or disabled, and writable + * stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * + * @since 1.7 + */ + protected RuntimeException(String message, Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } --- old/src/share/classes/java/lang/Throwable.java 2011-04-07 16:25:59.000000000 -0700 +++ new/src/share/classes/java/lang/Throwable.java 2011-04-07 16:25:59.000000000 -0700 @@ -1,4 +1,4 @@ -/* +* * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -129,6 +129,37 @@ */ 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 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 +168,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 +206,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 = EMPTY_STACK; // Setting this static field introduces an acceptable // initialization dependency on a few java.util classes. @@ -284,24 +320,33 @@ /** * Constructs a new throwable with the specified detail message, - * cause, and {@linkplain #addSuppressed suppression} enabled or - * disabled. If suppression is disabled, {@link #getSuppressed} - * for this object will return a zero-length array and calls to - * {@link #addSuppressed} that would otherwise append an exception - * to the suppressed list will have no effect. + * cause, {@linkplain #addSuppressed suppression} enabled or + * disabled, and writable stack trace enabled or disabled. If + * suppression is disabled, {@link #getSuppressed} for this object + * will return a zero-length array and calls to {@link + * #addSuppressed} that would otherwise append an exception to the + * suppressed list will have no effect. If the writable stack + * trace is false, this constructor will not call {@link + * #fillInStackTrace()} and subsequent calls to {@code + * fillInStackTrace} and {@link + * #setStackTrace(StackTraceElement[])} will not set the stack + * trace. If the writable stack trace is false, {@link + * #getStackTrace} will return a zero length array. * *

Note that the other constructors of {@code Throwable} treat - * suppression as being enabled. Subclasses of {@code Throwable} - * should document any conditions under which suppression is - * disabled. Disabling of suppression should only occur in - * exceptional circumstances where special requirements exist, - * such as a virtual machine reusing exception objects under - * low-memory situations. + * suppression as being enabled and the stack trace as being + * writable. Subclasses of {@code Throwable} should document any + * conditions under which suppression is disabled. Disabling of + * suppression should only occur in exceptional circumstances + * where special requirements exist, such as a virtual machine + * reusing exception objects under low-memory situations. * * @param message the detail message. * @param cause the cause. (A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown.) * @param enableSuppression whether or not suppression is enabled or disabled + * @param writableStackTrace whether or not the stack trace should be + * writable * * @see OutOfMemoryError * @see NullPointerException @@ -309,8 +354,13 @@ * @since 1.7 */ protected Throwable(String message, Throwable cause, - boolean enableSuppression) { - fillInStackTrace(); + boolean enableSuppression, + boolean writableStackTrace) { + if (writableStackTrace) { + fillInStackTrace(); + } else { + stackTrace = null; + } detailMessage = message; this.cause = cause; if (!enableSuppression) @@ -707,10 +757,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#Throwable(String, Throwable, boolean, boolean) is not + * writable}, 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 +802,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,6 +826,11 @@ * when a throwable is constructed or deserialized when a throwable is * read from a serialization stream. * + *

If the stack trace of this {@code Throwable} {@linkplain + * Throwable#Throwable(String, Throwable, boolean, boolean) is not + * writable}, calling this method has no effect other than + * validating its argument. + * * @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 @@ -768,16 +838,21 @@ * trace. * * @throws NullPointerException if {@code stackTrace} is - * {@code null}, or if any of the elements of + * {@code null} or if any of the elements of * {@code stackTrace} are {@code null} * * @since 1.4 */ public void setStackTrace(StackTraceElement[] stackTrace) { + // Validate argument StackTraceElement[] defensiveCopy = stackTrace.clone(); - for (int i = 0; i < defensiveCopy.length; i++) + for (int i = 0; i < defensiveCopy.length; i++) { if (defensiveCopy[i] == null) throw new NullPointerException("stackTrace[" + i + "]"); + } + + if (this.stackTrace == null) // Immutable stack + return; synchronized (this) { this.stackTrace = defensiveCopy; @@ -808,7 +883,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 +915,55 @@ 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. + // A null stackTrace field in the serial form can result + // from an exception serialized without that field in + // older JDK releases; treat such exceptions as having + // empty stack traces. stackTrace = EMPTY_STACK; } - } /** * 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 +1040,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-07 16:26:00.000000000 -0700 +++ new/src/share/native/java/lang/Throwable.c 2011-04-07 16:26:00.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2000, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -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-07 16:26:01.000000000 -0700 +++ new/test/java/lang/Throwable/ChainedExceptions.java 2011-04-07 16:26:00.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-07 16:26:01.000000000 -0700 +++ new/test/java/lang/Throwable/StackTraceSerialization.java 2011-04-07 16:26:01.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,52 @@ testWithFillInStackTrace(); } - private static void testWithSetStackTrace() throws Exception { - Throwable t = new Throwable(); + private static void testWithSetStackTrace() { + StackTraceElement[] stackTrace = {new StackTraceElement("foo", "bar", "baz", -1)}; - t.setStackTrace(new StackTraceElement[] - {new StackTraceElement("foo", "bar", "baz", -1)}); + Throwable t = new TestThrowable(true, false); // Immutable and empty stack + assertEmptyStackTrace(t); + + // Verify fillInStackTrace is now a no-op. + t.fillInStackTrace(); + assertEmptyStackTrace(t); + + // Verify setStackTrace is now a no-op. + t.setStackTrace(stackTrace); + assertEmptyStackTrace(t); + + // Verify null-handling + try { + t.setStackTrace(null); + throw new RuntimeException("No NPE on a null stack trace."); + } catch(NullPointerException npe) { + assertEmptyStackTrace(t); + } + + try { + t.setStackTrace(new StackTraceElement[]{null}); + throw new RuntimeException("No NPE on a null stack trace element."); + } catch(NullPointerException npe) { + 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 class TestThrowable extends Throwable { + public TestThrowable(boolean enableSuppression, + boolean writableStackTrace) { + super("the medium", null, + enableSuppression, + writableStackTrace); + } } private static void assertEmptyStackTrace(Throwable t) { @@ -52,7 +90,7 @@ throw new AssertionError("Nonempty stacktrace."); } - private static void testWithFillInStackTrace() throws Exception { + private static void testWithFillInStackTrace() { Throwable original = null; try { a(); @@ -61,16 +99,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 +116,9 @@ ObjectInputStream in = new ObjectInputStream(bin)) { result = (Throwable) in.readObject(); } + } catch(IOException | ClassNotFoundException e) { + throw new RuntimeException(e); } - return result; } --- old/test/java/lang/Throwable/SuppressedExceptions.java 2011-04-07 16:26:02.000000000 -0700 +++ new/test/java/lang/Throwable/SuppressedExceptions.java 2011-04-07 16:26:02.000000000 -0700 @@ -204,7 +204,7 @@ private static class NoSuppression extends Throwable { public NoSuppression(boolean enableSuppression) { - super("The medium.", null, enableSuppression); + super("The medium.", null, enableSuppression, true); } } }