6991528: Support making Throwable.suppressedExceptions immutable
Reviewed-by: mchung, dholmes
--- a/jdk/src/share/classes/java/lang/StackTraceElement.java Sat Nov 13 18:56:50 2010 -0800
+++ b/jdk/src/share/classes/java/lang/StackTraceElement.java Sun Nov 14 07:22:39 2010 -0800
@@ -1,5 +1,5 @@
/*
- * 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 @@
* @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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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><init></tt> or
- * <tt><clinit></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 @@
* 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 @@
* 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 @@
/**
* 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 @@
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 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;
}
--- a/jdk/src/share/classes/java/lang/Throwable.java Sat Nov 13 18:56:50 2010 -0800
+++ b/jdk/src/share/classes/java/lang/Throwable.java Sun Nov 14 07:22:39 2010 -0800
@@ -1,5 +1,5 @@
/*
- * 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
@@ -170,6 +170,36 @@
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
* throwable is unknown. If this field is equal to this throwable itself,
@@ -188,32 +218,30 @@
* @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 @@
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 @@
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 @@
*/
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 @@
* 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 @@
*
* @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)
- suppressedExceptions = new ArrayList<Throwable>();
- suppressedExceptions.add(exception);
+ if (suppressedExceptions == null) // Suppressed exceptions not recorded
+ return;
+
+ if (suppressedExceptions == SUPPRESSED_SENTINEL)
+ suppressedExceptions = new ArrayList<Throwable>(1);
+
+ assert suppressedExceptions != SUPPRESSED_SENTINEL;
+
+ suppressedExceptions.add(exception);
+ }
}
private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];
@@ -842,12 +926,15 @@
* 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);
--- a/jdk/test/java/lang/Throwable/StackTraceSerialization.java Sat Nov 13 18:56:50 2010 -0800
+++ b/jdk/test/java/lang/Throwable/StackTraceSerialization.java Sun Nov 14 07:22:39 2010 -0800
@@ -1,5 +1,5 @@
/*
- * 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 @@
/*
* @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 @@
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, 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;
- if (!equal(original, clone))
- throw new Exception();
+ 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 {
--- a/jdk/test/java/lang/Throwable/SuppressedExceptions.java Sat Nov 13 18:56:50 2010 -0800
+++ b/jdk/test/java/lang/Throwable/SuppressedExceptions.java Sun Nov 14 07:22:39 2010 -0800
@@ -26,7 +26,7 @@
/*
* @test
- * @bug 6911258 6962571 6963622
+ * @bug 6911258 6962571 6963622 6991528
* @summary Basic tests of suppressed exceptions
* @author Joseph D. Darcy
*/
@@ -39,12 +39,21 @@
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 @@
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 @@
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 @@
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.");
+ }
}