提交 24acdd3f 编写于 作者: D darcy

6991528: Support making Throwable.suppressedExceptions immutable

Reviewed-by: mchung, dholmes
上级 644b712e
/*
* Copyright (c) 2000, 2004, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2010, 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
......@@ -25,6 +25,8 @@
package java.lang;
import java.util.Objects;
/**
* An element in a stack trace, as returned by {@link
* Throwable#getStackTrace()}. Each element represents a single stack frame.
......@@ -53,26 +55,21 @@ public final class StackTraceElement implements java.io.Serializable {
* @param methodName the name of the method containing the execution point
* represented by the stack trace element
* @param fileName the name of the file containing the execution point
* represented by the stack trace element, or <tt>null</tt> if
* represented by the stack trace element, or {@code null} if
* this information is unavailable
* @param lineNumber the line number of the source line containing the
* execution point represented by this stack trace element, or
* a negative number if this information is unavailable. A value
* of -2 indicates that the method containing the execution point
* is a native method
* @throws NullPointerException if <tt>declaringClass</tt> or
* <tt>methodName</tt> is null
* @throws NullPointerException if {@code declaringClass} or
* {@code methodName} is null
* @since 1.5
*/
public StackTraceElement(String declaringClass, String methodName,
String fileName, int lineNumber) {
if (declaringClass == null)
throw new NullPointerException("Declaring class is null");
if (methodName == null)
throw new NullPointerException("Method name is null");
this.declaringClass = declaringClass;
this.methodName = methodName;
this.declaringClass = Objects.nonNull(declaringClass, "Declaring class is null");
this.methodName = Objects.nonNull(methodName, "Method name is null");
this.fileName = fileName;
this.lineNumber = lineNumber;
}
......@@ -80,13 +77,13 @@ public final class StackTraceElement implements java.io.Serializable {
/**
* Returns the name of the source file containing the execution point
* represented by this stack trace element. Generally, this corresponds
* to the <tt>SourceFile</tt> attribute of the relevant <tt>class</tt>
* to the {@code SourceFile} attribute of the relevant {@code class}
* file (as per <i>The Java Virtual Machine Specification</i>, Section
* 4.7.7). In some systems, the name may refer to some source code unit
* other than a file, such as an entry in source repository.
*
* @return the name of the file containing the execution point
* represented by this stack trace element, or <tt>null</tt> if
* represented by this stack trace element, or {@code null} if
* this information is unavailable.
*/
public String getFileName() {
......@@ -96,8 +93,8 @@ public final class StackTraceElement implements java.io.Serializable {
/**
* Returns the line number of the source line containing the execution
* point represented by this stack trace element. Generally, this is
* derived from the <tt>LineNumberTable</tt> attribute of the relevant
* <tt>class</tt> file (as per <i>The Java Virtual Machine
* derived from the {@code LineNumberTable} attribute of the relevant
* {@code class} file (as per <i>The Java Virtual Machine
* Specification</i>, Section 4.7.8).
*
* @return the line number of the source line containing the execution
......@@ -112,7 +109,7 @@ public final class StackTraceElement implements java.io.Serializable {
* Returns the fully qualified name of the class containing the
* execution point represented by this stack trace element.
*
* @return the fully qualified name of the <tt>Class</tt> containing
* @return the fully qualified name of the {@code Class} containing
* the execution point represented by this stack trace element.
*/
public String getClassName() {
......@@ -123,8 +120,8 @@ public final class StackTraceElement implements java.io.Serializable {
* Returns the name of the method containing the execution point
* represented by this stack trace element. If the execution point is
* contained in an instance or class initializer, this method will return
* the appropriate <i>special method name</i>, <tt>&lt;init&gt;</tt> or
* <tt>&lt;clinit&gt;</tt>, as per Section 3.9 of <i>The Java Virtual
* the appropriate <i>special method name</i>, {@code <init>} or
* {@code <clinit>}, as per Section 3.9 of <i>The Java Virtual
* Machine Specification</i>.
*
* @return the name of the method containing the execution point
......@@ -138,7 +135,7 @@ public final class StackTraceElement implements java.io.Serializable {
* Returns true if the method containing the execution point
* represented by this stack trace element is a native method.
*
* @return <tt>true</tt> if the method containing the execution point
* @return {@code true} if the method containing the execution point
* represented by this stack trace element is a native method.
*/
public boolean isNativeMethod() {
......@@ -151,21 +148,21 @@ public final class StackTraceElement implements java.io.Serializable {
* examples may be regarded as typical:
* <ul>
* <li>
* <tt>"MyClass.mash(MyClass.java:9)"</tt> - Here, <tt>"MyClass"</tt>
* {@code "MyClass.mash(MyClass.java:9)"} - Here, {@code "MyClass"}
* is the <i>fully-qualified name</i> of the class containing the
* execution point represented by this stack trace element,
* <tt>"mash"</tt> is the name of the method containing the execution
* point, <tt>"MyClass.java"</tt> is the source file containing the
* execution point, and <tt>"9"</tt> is the line number of the source
* {@code "mash"} is the name of the method containing the execution
* point, {@code "MyClass.java"} is the source file containing the
* execution point, and {@code "9"} is the line number of the source
* line containing the execution point.
* <li>
* <tt>"MyClass.mash(MyClass.java)"</tt> - As above, but the line
* {@code "MyClass.mash(MyClass.java)"} - As above, but the line
* number is unavailable.
* <li>
* <tt>"MyClass.mash(Unknown Source)"</tt> - As above, but neither
* {@code "MyClass.mash(Unknown Source)"} - As above, but neither
* the file name nor the line number are available.
* <li>
* <tt>"MyClass.mash(Native Method)"</tt> - As above, but neither
* {@code "MyClass.mash(Native Method)"} - As above, but neither
* the file name nor the line number are available, and the method
* containing the execution point is known to be a native method.
* </ul>
......@@ -181,25 +178,21 @@ public final class StackTraceElement implements java.io.Serializable {
/**
* Returns true if the specified object is another
* <tt>StackTraceElement</tt> instance representing the same execution
* point as this instance. Two stack trace elements <tt>a</tt> and
* <tt>b</tt> are equal if and only if:
* {@code StackTraceElement} instance representing the same execution
* point as this instance. Two stack trace elements {@code a} and
* {@code b} are equal if and only if:
* <pre>
* equals(a.getFileName(), b.getFileName()) &&
* a.getLineNumber() == b.getLineNumber()) &&
* equals(a.getClassName(), b.getClassName()) &&
* equals(a.getMethodName(), b.getMethodName())
* </pre>
* where <tt>equals</tt> is defined as:
* <pre>
* static boolean equals(Object a, Object b) {
* return a==b || (a != null && a.equals(b));
* }
* </pre>
* where {@code equals} has the semantics of {@link
* java.util.Objects#equals(Object, Object) Objects.equals}.
*
* @param obj the object to be compared with this stack trace element.
* @return true if the specified object is another
* <tt>StackTraceElement</tt> instance representing the same
* {@code StackTraceElement} instance representing the same
* execution point as this instance.
*/
public boolean equals(Object obj) {
......@@ -208,12 +201,10 @@ public final class StackTraceElement implements java.io.Serializable {
if (!(obj instanceof StackTraceElement))
return false;
StackTraceElement e = (StackTraceElement)obj;
return e.declaringClass.equals(declaringClass) && e.lineNumber == lineNumber
&& eq(methodName, e.methodName) && eq(fileName, e.fileName);
}
private static boolean eq(Object a, Object b) {
return a==b || (a != null && a.equals(b));
return e.declaringClass.equals(declaringClass) &&
e.lineNumber == lineNumber &&
Objects.equals(methodName, e.methodName) &&
Objects.equals(fileName, e.fileName);
}
/**
......@@ -221,7 +212,7 @@ public final class StackTraceElement implements java.io.Serializable {
*/
public int hashCode() {
int result = 31*declaringClass.hashCode() + methodName.hashCode();
result = 31*result + (fileName == null ? 0 : fileName.hashCode());
result = 31*result + Objects.hashCode(fileName);
result = 31*result + lineNumber;
return result;
}
......
/*
* Copyright (c) 1994, 2006, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2010, 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
......@@ -169,6 +169,36 @@ public class Throwable implements Serializable {
*/
private String detailMessage;
/**
* A shared value for an empty stack.
*/
private static final StackTraceElement[] EMPTY_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:
*
* 1) The fields are initialized to a non-null sentinel value
* which indicates the value has logically not been set.
*
* 2) Writing a null to the field indicates further writes
* are forbidden
*
* 3) The sentinel value may be replaced with another non-null
* value.
*
* For example, implementations of the HotSpot JVM have
* preallocated OutOfMemoryError objects to provide for better
* diagnosability of that situation. These objects are created
* without calling the constructor for that class and the fields
* in question are initialized to null. To support this
* capability, any new fields added to Throwable that require
* being initialized to a non-null value require a coordinated JVM
* change.
*/
/**
* The throwable that caused this throwable to get thrown, or null if this
* throwable was not caused by another throwable, or if the causative
......@@ -188,32 +218,30 @@ public class Throwable implements Serializable {
* @since 1.4
*/
private StackTraceElement[] stackTrace;
/*
* This field is lazily initialized on first use or serialization and
* nulled out when fillInStackTrace is called.
*/
// Setting this static field introduces an acceptable
// initialization dependency on a few java.util classes.
private static final List<Throwable> SUPPRESSED_SENTINEL =
Collections.unmodifiableList(new ArrayList<Throwable>(0));
/**
* The list of suppressed exceptions, as returned by
* {@link #getSuppressedExceptions()}.
* The list of suppressed exceptions, as returned by {@link
* #getSuppressed()}. The list is initialized to a zero-element
* unmodifiable sentinel list. When a serialized Throwable is
* read in, if the {@code suppressedExceptions} field points to a
* zero-element list, the field is reset to the sentinel value.
*
* @serial
* @since 1.7
*/
private List<Throwable> suppressedExceptions = null;
/*
* This field is lazily initialized when the first suppressed
* exception is added.
*
* OutOfMemoryError is preallocated in the VM for better OOM
* diagnosability during VM initialization. Constructor can't
* be not invoked. If a new field to be added in the future must
* be initialized to non-null, it requires a synchronized VM change.
*/
private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;
/** Message for trying to suppress a null exception. */
private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";
/** Message for trying to suppress oneself. */
private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";
/** Caption for labeling causative exception stack traces */
private static final String CAUSE_CAPTION = "Caused by: ";
......@@ -572,7 +600,7 @@ public class Throwable implements Serializable {
s.println("\tat " + traceElement);
// Print suppressed exceptions, if any
for (Throwable se : getSuppressedExceptions())
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
// Print cause, if any
......@@ -613,7 +641,7 @@ public class Throwable implements Serializable {
s.println(prefix + "\t... " + framesInCommon + " more");
// Print suppressed exceptions, if any
for (Throwable se : getSuppressedExceptions())
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION,
prefix +"\t", dejaVu);
......@@ -780,25 +808,58 @@ public class Throwable implements Serializable {
*/
native StackTraceElement getStackTraceElement(int index);
/**
* Read a {@code Throwable} from a stream, enforcing
* 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.
*
* Note that there are no constraints on the value the {@code
* cause} field can hold; both {@code null} and {@code this} are
* valid values for the field.
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject(); // read in all fields
List<Throwable> suppressed = null;
if (suppressedExceptions != null &&
!suppressedExceptions.isEmpty()) { // Copy Throwables to new list
suppressed = new ArrayList<Throwable>();
for (Throwable t : suppressedExceptions) {
if (t == null)
throw new NullPointerException(NULL_CAUSE_MESSAGE);
suppressed.add(t);
if (suppressedExceptions != null) {
List<Throwable> suppressed = null;
if (suppressedExceptions.isEmpty()) {
// Use the sentinel for a zero-length list
suppressed = SUPPRESSED_SENTINEL;
} else { // Copy Throwables to new list
suppressed = new ArrayList<Throwable>(1);
for (Throwable t : suppressedExceptions) {
// Enforce constraints on suppressed exceptions in
// case of corrupt or malicious stream.
if (t == null)
throw new NullPointerException(NULL_CAUSE_MESSAGE);
if (t == this)
throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE);
suppressed.add(t);
}
}
suppressedExceptions = suppressed;
} // else a null suppressedExceptions field remains null
if (stackTrace != 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;
}
suppressedExceptions = suppressed;
}
/**
* Write a {@code Throwable} object to a stream.
*/
private synchronized void writeObject(ObjectOutputStream s)
throws IOException
{
throws IOException {
getOurStackTrace(); // Ensure that stackTrace field is initialized.
s.defaultWriteObject();
}
......@@ -808,6 +869,14 @@ public class Throwable implements Serializable {
* were suppressed, typically by the {@code try}-with-resources
* statement, in order to deliver this exception.
*
* If the first exception to be suppressed is {@code null}, that
* indicates suppressed exception information will <em>not</em> be
* recorded for this exception. Subsequent calls to this method
* will not record any suppressed exceptions. Otherwise,
* attempting to suppress {@code null} after an exception has
* already been successfully suppressed results in a {@code
* NullPointerException}.
*
* <p>Note that when one exception {@linkplain
* #initCause(Throwable) causes} another exception, the first
* exception is usually caught and then the second exception is
......@@ -819,20 +888,35 @@ public class Throwable implements Serializable {
*
* @param exception the exception to be added to the list of
* suppressed exceptions
* @throws NullPointerException if {@code exception} is null
* @throws IllegalArgumentException if {@code exception} is this
* throwable; a throwable cannot suppress itself.
* @throws NullPointerException if {@code exception} is null and
* an exception has already been suppressed by this exception
* @since 1.7
*/
public synchronized void addSuppressedException(Throwable exception) {
if (exception == null)
throw new NullPointerException(NULL_CAUSE_MESSAGE);
public final synchronized void addSuppressed(Throwable exception) {
if (exception == this)
throw new IllegalArgumentException("Self-suppression not permitted");
throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE);
if (exception == null) {
if (suppressedExceptions == SUPPRESSED_SENTINEL) {
suppressedExceptions = null; // No suppression information recorded
return;
} else
throw new NullPointerException(NULL_CAUSE_MESSAGE);
} else {
assert exception != null && exception != this;
if (suppressedExceptions == null) // Suppressed exceptions not recorded
return;
if (suppressedExceptions == SUPPRESSED_SENTINEL)
suppressedExceptions = new ArrayList<Throwable>(1);
if (suppressedExceptions == null)
suppressedExceptions = new ArrayList<Throwable>();
suppressedExceptions.add(exception);
assert suppressedExceptions != SUPPRESSED_SENTINEL;
suppressedExceptions.add(exception);
}
}
private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];
......@@ -842,12 +926,15 @@ public class Throwable implements Serializable {
* suppressed, typically by the {@code try}-with-resources
* statement, in order to deliver this exception.
*
* If no exceptions were suppressed, an empty array is returned.
*
* @return an array containing all of the exceptions that were
* suppressed to deliver this exception.
* @since 1.7
*/
public synchronized Throwable[] getSuppressedExceptions() {
if (suppressedExceptions == null)
public final synchronized Throwable[] getSuppressed() {
if (suppressedExceptions == SUPPRESSED_SENTINEL ||
suppressedExceptions == null)
return EMPTY_THROWABLE_ARRAY;
else
return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
......
/*
* Copyright (c) 2000, 2001, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2010, 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,13 +26,33 @@ import java.util.*;
/*
* @test
* @bug 4202914 4363318
* @bug 4202914 4363318 6991528
* @summary Basic test of serialization of stack trace information
* @author Josh Bloch
*/
public class StackTraceSerialization {
public static void main(String args[]) throws Exception {
testWithSetStackTrace();
testWithFillInStackTrace();
}
private static void testWithSetStackTrace() throws Exception {
Throwable t = new Throwable();
t.setStackTrace(new StackTraceElement[]
{new StackTraceElement("foo", "bar", "baz", -1)});
if (!equal(t, reconstitute(t)))
throw new Exception("Unequal Throwables with set stacktrace");
}
private static void assertEmptyStackTrace(Throwable t) {
if (t.getStackTrace().length != 0)
throw new AssertionError("Nonempty stacktrace.");
}
private static void testWithFillInStackTrace() throws Exception {
Throwable original = null;
try {
a();
......@@ -40,27 +60,42 @@ public class StackTraceSerialization {
original = e;
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(original);
out.flush();
ByteArrayInputStream bin =
new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Throwable clone = (Throwable) in.readObject();
if (!equal(original, clone))
throw new Exception();
if (!equal(original, reconstitute(original)))
throw new Exception("Unequal Throwables with filled-in stacktrace");
}
/**
* Serialize the argument and return the deserialized result.
*/
private static Throwable reconstitute(Throwable t) throws Exception {
Throwable result = null;
try(ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout)) {
out.writeObject(t);
out.flush();
try(ByteArrayInputStream bin =
new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin)) {
result = (Throwable) in.readObject();
}
}
return result;
}
/**
* Returns true if e1 and e2 have equal stack traces and their causes
* are recursively equal (by the same definition). Returns false
* or throws NullPointerExeption otherwise.
* Returns true if e1 and e2 have equal stack traces and their
* causes are recursively equal (by the same definition) and their
* suppressed exception information is equals. Returns false or
* throws NullPointerExeption otherwise.
*/
private static boolean equal(Throwable t1, Throwable t2) {
return t1==t2 || (Arrays.equals(t1.getStackTrace(), t2.getStackTrace())
&& equal(t1.getCause(), t2.getCause()));
return t1==t2 ||
(Arrays.equals(t1.getStackTrace(), t2.getStackTrace()) &&
equal(t1.getCause(), t2.getCause()) &&
Objects.equals(t1.getSuppressed(), t2.getSuppressed()));
}
static void a() throws HighLevelException {
......
......@@ -26,7 +26,7 @@ import java.util.*;
/*
* @test
* @bug 6911258 6962571 6963622
* @bug 6911258 6962571 6963622 6991528
* @summary Basic tests of suppressed exceptions
* @author Joseph D. Darcy
*/
......@@ -39,12 +39,21 @@ public class SuppressedExceptions {
basicSupressionTest();
serializationTest();
selfReference();
noModification();
}
private static void noSelfSuppression() {
Throwable throwable = new Throwable();
try {
throwable.addSuppressedException(throwable);
throwable.addSuppressed(throwable);
throw new RuntimeException("IllegalArgumentException for self-suppresion not thrown.");
} catch (IllegalArgumentException iae) {
; // Expected
}
throwable.addSuppressed(null); // Immutable suppression list
try {
throwable.addSuppressed(throwable);
throw new RuntimeException("IllegalArgumentException for self-suppresion not thrown.");
} catch (IllegalArgumentException iae) {
; // Expected
......@@ -56,21 +65,21 @@ public class SuppressedExceptions {
RuntimeException suppressed = new RuntimeException("A suppressed exception.");
AssertionError repressed = new AssertionError("A repressed error.");
Throwable[] t0 = throwable.getSuppressedExceptions();
Throwable[] t0 = throwable.getSuppressed();
if (t0.length != 0) {
throw new RuntimeException(message);
}
throwable.printStackTrace();
throwable.addSuppressedException(suppressed);
Throwable[] t1 = throwable.getSuppressedExceptions();
throwable.addSuppressed(suppressed);
Throwable[] t1 = throwable.getSuppressed();
if (t1.length != 1 ||
t1[0] != suppressed) {throw new RuntimeException(message);
}
throwable.printStackTrace();
throwable.addSuppressedException(repressed);
Throwable[] t2 = throwable.getSuppressedExceptions();
throwable.addSuppressed(repressed);
Throwable[] t2 = throwable.getSuppressed();
if (t2.length != 2 ||
t2[0] != suppressed ||
t2[1] != repressed) {
......@@ -152,7 +161,7 @@ public class SuppressedExceptions {
System.err.println("TESTING SERIALIZED EXCEPTION");
Throwable[] t0 = throwable.getSuppressedExceptions();
Throwable[] t0 = throwable.getSuppressed();
if (t0.length != 0) { // Will fail if t0 is null.
throw new RuntimeException(message);
}
......@@ -167,9 +176,25 @@ public class SuppressedExceptions {
throwable1.printStackTrace();
throwable1.addSuppressedException(throwable2);
throwable2.addSuppressedException(throwable1);
throwable1.addSuppressed(throwable2);
throwable2.addSuppressed(throwable1);
throwable1.printStackTrace();
}
private static void noModification() {
Throwable t = new Throwable();
t.addSuppressed(null);
Throwable[] t0 = t.getSuppressed();
if (t0.length != 0)
throw new RuntimeException("Bad nonzero length of suppressed exceptions.");
t.addSuppressed(new ArithmeticException());
// Make sure a suppressed exception did *not* get added.
t0 = t.getSuppressed();
if (t0.length != 0)
throw new RuntimeException("Bad nonzero length of suppressed exceptions.");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册