6991528: Support making Throwable.suppressedExceptions immutable
authordarcy
Sun, 14 Nov 2010 07:22:39 -0800
changeset 7186 7f5fe8985263
parent 7185 0ca9d322c703
child 7187 1ad8f3107d3b
child 7271 17d3fc18872d
6991528: Support making Throwable.suppressedExceptions immutable Reviewed-by: mchung, dholmes
jdk/src/share/classes/java/lang/StackTraceElement.java
jdk/src/share/classes/java/lang/Throwable.java
jdk/test/java/lang/Throwable/StackTraceSerialization.java
jdk/test/java/lang/Throwable/SuppressedExceptions.java
--- 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>&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 @@
      * 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.");
+    }
 }