提交 8a5e8a3b 编写于 作者: D darcy

6998871: Support making the Throwable.stackTrace field immutable

Reviewed-by: dholmes, mchung, forax
上级 23a305fc
......@@ -32,7 +32,8 @@ package java.lang;
*
* {@code ArithmeticException} objects may be constructed by the
* virtual machine as if {@linkplain Throwable#Throwable(String,
* Throwable, boolean) suppression were disabled}.
* Throwable, boolean, boolean) suppression were disabled and/or the
* stack trace was not writable}.
*
* @author unascribed
* @since JDK1.0
......
/*
* 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 @@ public class Error extends Throwable {
* @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 <tt>null</tt> 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 @@ public class Error extends Throwable {
/**
* Constructs a new error with the specified cause and a detail
* message of <tt>(cause==null ? null : cause.toString())</tt> (which
* typically contains the class and detail message of <tt>cause</tt>).
* 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 <tt>null</tt> 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 class Error extends Throwable {
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);
}
}
/*
* 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 class Exception extends Throwable {
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);
}
}
......@@ -43,7 +43,8 @@ package java.lang;
*
* {@code NullPointerException} objects may be constructed by the
* virtual machine as if {@linkplain Throwable#Throwable(String,
* Throwable, boolean) suppression were disabled}.
* Throwable, boolean, boolean) suppression were disabled and/or the
* stack trace was not writable}.
*
* @author unascribed
* @since JDK1.0
......
......@@ -32,7 +32,8 @@ package java.lang;
*
* {@code OutOfMemoryError} objects may be constructed by the virtual
* machine as if {@linkplain Throwable#Throwable(String, Throwable,
* boolean) suppression were disabled}.
* boolean, boolean) suppression were disabled and/or the stack trace was not
* writable}.
*
* @author unascribed
* @since JDK1.0
......
/*
* 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 class RuntimeException extends Exception {
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);
}
}
......@@ -129,16 +129,41 @@ public class Throwable implements Serializable {
*/
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:<br>
* {@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 shared value for an empty stack.
*/
private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
/*
* 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 +199,15 @@ public class Throwable implements Serializable {
/**
* 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(StackTraceElement[])} and {@link
* #fillInStackTrace()} will be be no-ops.
*
* @serial
* @since 1.4
*/
private StackTraceElement[] stackTrace;
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
// Setting this static field introduces an acceptable
// initialization dependency on a few java.util classes.
......@@ -284,24 +314,36 @@ public class Throwable implements Serializable {
/**
* 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()}, a {@code null} will be written to the
* {@code stackTrace} field, 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.
*
* <p>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 and document
* conditions under which the stack trace is not writable.
* 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 +351,13 @@ public class Throwable implements Serializable {
* @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 +754,22 @@ public class Throwable implements Serializable {
* {@code Throwable} object information about the current state of
* the stack frames for the current thread.
*
* <p>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 = UNASSIGNED_STACK;
}
return this;
}
private native Throwable fillInStackTrace(int dummy);
/**
* Provides programmatic access to the stack trace information printed by
......@@ -740,12 +799,15 @@ public class Throwable implements Serializable {
}
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 == UNASSIGNED_STACK) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
......@@ -761,6 +823,11 @@ public class Throwable implements Serializable {
* when a throwable is constructed or deserialized when a throwable is
* read from a serialization stream.
*
* <p>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 +835,21 @@ public class Throwable implements Serializable {
* 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 +880,11 @@ public class Throwable implements Serializable {
* 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 null stack trace in the serial form results
* in a zero-length stack element array. 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 +913,63 @@ public class Throwable implements Serializable {
suppressedExceptions = suppressed;
} // else a null suppressedExceptions field remains null
/*
* For zero-length stack traces, use a clone of
* UNASSIGNED_STACK rather than UNASSIGNED_STACK itself to
* allow identity comparison against UNASSIGNED_STACK in
* getOurStackTrace. The identity of UNASSIGNED_STACK in
* stackTrace indicates to the getOurStackTrace method that
* the stackTrace needs to be constructed from the information
* in backtrace.
*/
if (stackTrace != null) {
for (StackTraceElement ste : stackTrace) {
if (ste == null)
throw new NullPointerException("null StackTraceElement in serial stream. ");
if (stackTrace.length == 0) {
stackTrace = UNASSIGNED_STACK.clone();
} else if (stackTrace.length == 1 &&
// Check for the marker of an immutable stack trace
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 in
// older JDK releases; treat such exceptions as having
// empty stack traces.
stackTrace = UNASSIGNED_STACK.clone();
}
}
/**
* 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();
}
/**
......@@ -866,8 +979,8 @@ public class Throwable implements Serializable {
* try}-with-resources statement.
*
* <p>The suppression behavior is enabled <em>unless</em> disabled
* {@linkplain #Throwable(String, Throwable, boolean) via a
* constructor}. When suppression is disabled, this method does
* {@linkplain #Throwable(String, Throwable, boolean, boolean) via
* a constructor}. When suppression is disabled, this method does
* nothing other than to validate its argument.
*
* <p>Note that when one exception {@linkplain
......@@ -933,8 +1046,8 @@ public class Throwable implements Serializable {
* statement, in order to deliver this exception.
*
* If no exceptions were suppressed or {@linkplain
* Throwable(String, Throwable, boolean) suppression is disabled},
* an empty array is returned.
* #Throwable(String, Throwable, boolean, boolean) suppression is
* disabled}, an empty array is returned.
*
* @return an array containing all of the exceptions that were
* suppressed to deliver this exception.
......
/*
* 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;
......
......@@ -13,28 +13,28 @@ public class ChainedExceptions {
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 @@ public class ChainedExceptions {
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);
}
}
......
/*
* 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 @@ import java.util.*;
/*
* @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 @@ public class StackTraceSerialization {
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 @@ public class StackTraceSerialization {
throw new AssertionError("Nonempty stacktrace.");
}
private static void testWithFillInStackTrace() throws Exception {
private static void testWithFillInStackTrace() {
Throwable original = null;
try {
a();
......@@ -61,16 +99,14 @@ public class StackTraceSerialization {
}
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 @@ public class StackTraceSerialization {
ObjectInputStream in = new ObjectInputStream(bin)) {
result = (Throwable) in.readObject();
}
} catch(IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
return result;
}
......
......@@ -193,6 +193,7 @@ public class SuppressedExceptions {
// Make sure addSuppressed(null) throws an NPE
try {
t.addSuppressed(null);
throw new RuntimeException("NPE not thrown!");
} catch(NullPointerException e) {
; // Expected
}
......@@ -204,7 +205,7 @@ public class SuppressedExceptions {
private static class NoSuppression extends Throwable {
public NoSuppression(boolean enableSuppression) {
super("The medium.", null, enableSuppression);
super("The medium.", null, enableSuppression, true);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册