7005628: Clarify NPE behavior of Throwable.addSuppressed(null)
authordarcy
Thu, 31 Mar 2011 19:09:02 -0700
changeset 9020 13b639abc930
parent 9019 03920d69bb93
child 9021 5cf29386a520
7005628: Clarify NPE behavior of Throwable.addSuppressed(null) Reviewed-by: dholmes, mchung, jjb
jdk/src/share/classes/java/lang/ArithmeticException.java
jdk/src/share/classes/java/lang/NullPointerException.java
jdk/src/share/classes/java/lang/OutOfMemoryError.java
jdk/src/share/classes/java/lang/Throwable.java
jdk/test/java/lang/Throwable/SuppressedExceptions.java
--- a/jdk/src/share/classes/java/lang/ArithmeticException.java	Thu Mar 31 17:37:11 2011 +0100
+++ b/jdk/src/share/classes/java/lang/ArithmeticException.java	Thu Mar 31 19:09:02 2011 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2008, 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
@@ -30,15 +30,18 @@
  * example, an integer "divide by zero" throws an
  * instance of this class.
  *
+ * {@code ArithmeticException} objects may be constructed by the
+ * virtual machine as if {@linkplain Throwable#Throwable(String,
+ * Throwable, boolean) suppression were disabled}.
+ *
  * @author  unascribed
  * @since   JDK1.0
  */
-public
-class ArithmeticException extends RuntimeException {
+public class ArithmeticException extends RuntimeException {
     private static final long serialVersionUID = 2256477558314496007L;
 
     /**
-     * Constructs an <code>ArithmeticException</code> with no detail
+     * Constructs an {@code ArithmeticException} with no detail
      * message.
      */
     public ArithmeticException() {
@@ -46,7 +49,7 @@
     }
 
     /**
-     * Constructs an <code>ArithmeticException</code> with the specified
+     * Constructs an {@code ArithmeticException} with the specified
      * detail message.
      *
      * @param   s   the detail message.
--- a/jdk/src/share/classes/java/lang/NullPointerException.java	Thu Mar 31 17:37:11 2011 +0100
+++ b/jdk/src/share/classes/java/lang/NullPointerException.java	Thu Mar 31 19:09:02 2011 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2008, 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
@@ -26,20 +26,24 @@
 package java.lang;
 
 /**
- * Thrown when an application attempts to use <code>null</code> in a
+ * Thrown when an application attempts to use {@code null} in a
  * case where an object is required. These include:
  * <ul>
- * <li>Calling the instance method of a <code>null</code> object.
- * <li>Accessing or modifying the field of a <code>null</code> object.
- * <li>Taking the length of <code>null</code> as if it were an array.
- * <li>Accessing or modifying the slots of <code>null</code> as if it
+ * <li>Calling the instance method of a {@code null} object.
+ * <li>Accessing or modifying the field of a {@code null} object.
+ * <li>Taking the length of {@code null} as if it were an array.
+ * <li>Accessing or modifying the slots of {@code null} as if it
  *     were an array.
- * <li>Throwing <code>null</code> as if it were a <code>Throwable</code>
+ * <li>Throwing {@code null} as if it were a {@code Throwable}
  *     value.
  * </ul>
  * <p>
  * Applications should throw instances of this class to indicate
- * other illegal uses of the <code>null</code> object.
+ * other illegal uses of the {@code null} object.
+ *
+ * {@code NullPointerException} objects may be constructed by the
+ * virtual machine as if {@linkplain Throwable#Throwable(String,
+ * Throwable, boolean) suppression were disabled}.
  *
  * @author  unascribed
  * @since   JDK1.0
@@ -49,14 +53,14 @@
     private static final long serialVersionUID = 5162710183389028792L;
 
     /**
-     * Constructs a <code>NullPointerException</code> with no detail message.
+     * Constructs a {@code NullPointerException} with no detail message.
      */
     public NullPointerException() {
         super();
     }
 
     /**
-     * Constructs a <code>NullPointerException</code> with the specified
+     * Constructs a {@code NullPointerException} with the specified
      * detail message.
      *
      * @param   s   the detail message.
--- a/jdk/src/share/classes/java/lang/OutOfMemoryError.java	Thu Mar 31 17:37:11 2011 +0100
+++ b/jdk/src/share/classes/java/lang/OutOfMemoryError.java	Thu Mar 31 19:09:02 2011 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2008, 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
@@ -30,22 +30,25 @@
  * because it is out of memory, and no more memory could be made
  * available by the garbage collector.
  *
+ * {@code OutOfMemoryError} objects may be constructed by the virtual
+ * machine as if {@linkplain Throwable#Throwable(String, Throwable,
+ * boolean) suppression were disabled}.
+ *
  * @author  unascribed
  * @since   JDK1.0
  */
-public
-class OutOfMemoryError extends VirtualMachineError {
+public class OutOfMemoryError extends VirtualMachineError {
     private static final long serialVersionUID = 8228564086184010517L;
 
     /**
-     * Constructs an <code>OutOfMemoryError</code> with no detail message.
+     * Constructs an {@code OutOfMemoryError} with no detail message.
      */
     public OutOfMemoryError() {
         super();
     }
 
     /**
-     * Constructs an <code>OutOfMemoryError</code> with the specified
+     * Constructs an {@code OutOfMemoryError} with the specified
      * detail message.
      *
      * @param   s   the detail message.
--- a/jdk/src/share/classes/java/lang/Throwable.java	Thu Mar 31 17:37:11 2011 +0100
+++ b/jdk/src/share/classes/java/lang/Throwable.java	Thu Mar 31 19:09:02 2011 -0700
@@ -52,7 +52,7 @@
  * throwable can {@linkplain Throwable#addSuppressed suppress} other
  * throwables from being propagated.  Finally, the throwable can also
  * contain a <i>cause</i>: another throwable that caused this
- * throwable to get thrown.  The recording of this causal information
+ * throwable to be constructed.  The recording of this causal information
  * is referred to as the <i>chained exception</i> facility, as the
  * cause can, itself, have a cause, and so on, leading to a "chain" of
  * exceptions, each caused by another.
@@ -283,6 +283,41 @@
     }
 
     /**
+     * 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.
+     *
+     * <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.
+     *
+     * @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
+     *
+     * @see OutOfMemoryError
+     * @see NullPointerException
+     * @see ArithmeticException
+     * @since 1.7
+     */
+    protected Throwable(String message, Throwable cause,
+                        boolean enableSuppression) {
+        fillInStackTrace();
+        detailMessage = message;
+        this.cause = cause;
+        if (!enableSuppression)
+            suppressedExceptions = null;
+    }
+
+    /**
      * Returns the detail message string of this throwable.
      *
      * @return  the detail message string of this {@code Throwable} instance
@@ -830,13 +865,10 @@
      * typically called (automatically and implicitly) by the {@code
      * try}-with-resources statement.
      *
-     * 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>The suppression behavior is enabled <em>unless</em> disabled
+     * {@linkplain #Throwable(String, Throwable, 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
      * #initCause(Throwable) causes} another exception, the first
@@ -874,33 +906,23 @@
      *        suppressed exceptions
      * @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
+     * @throws NullPointerException if {@code exception} is {@code null}
      * @since 1.7
      */
     public final synchronized void addSuppressed(Throwable exception) {
         if (exception == this)
             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 (exception == null)
+            throw new NullPointerException(NULL_CAUSE_MESSAGE);
 
-            if (suppressedExceptions == null) // Suppressed exceptions not recorded
-                return;
+        if (suppressedExceptions == null) // Suppressed exceptions not recorded
+            return;
 
-            if (suppressedExceptions == SUPPRESSED_SENTINEL)
-                suppressedExceptions = new ArrayList<>(1);
+        if (suppressedExceptions == SUPPRESSED_SENTINEL)
+            suppressedExceptions = new ArrayList<>(1);
 
-            assert suppressedExceptions != SUPPRESSED_SENTINEL;
-
-            suppressedExceptions.add(exception);
-        }
+        suppressedExceptions.add(exception);
     }
 
     private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];
@@ -910,7 +932,9 @@
      * 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.
+     * If no exceptions were suppressed or {@linkplain
+     * Throwable(String, Throwable, boolean) suppression is disabled},
+     * an empty array is returned.
      *
      * @return an array containing all of the exceptions that were
      *         suppressed to deliver this exception.
--- a/jdk/test/java/lang/Throwable/SuppressedExceptions.java	Thu Mar 31 17:37:11 2011 +0100
+++ b/jdk/test/java/lang/Throwable/SuppressedExceptions.java	Thu Mar 31 19:09:02 2011 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,7 @@
 
 /*
  * @test
- * @bug     6911258 6962571 6963622 6991528
+ * @bug     6911258 6962571 6963622 6991528 7005628
  * @summary Basic tests of suppressed exceptions
  * @author  Joseph D. Darcy
  */
@@ -50,14 +50,6 @@
         } 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
-        }
     }
 
     private static void basicSupressionTest() {
@@ -153,19 +145,19 @@
             (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
         };
 
-        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
-        ObjectInputStream ois = new ObjectInputStream(bais);
+        try(ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            ObjectInputStream ois = new ObjectInputStream(bais)) {
+            Object o = ois.readObject();
+            Throwable throwable = (Throwable) o;
 
-        Object o = ois.readObject();
-        Throwable throwable = (Throwable) o;
+            System.err.println("TESTING SERIALIZED EXCEPTION");
 
-        System.err.println("TESTING SERIALIZED EXCEPTION");
-
-        Throwable[] t0 = throwable.getSuppressed();
-        if (t0.length != 0) { // Will fail if t0 is null.
-            throw new RuntimeException(message);
+            Throwable[] t0 = throwable.getSuppressed();
+            if (t0.length != 0) { // Will fail if t0 is null.
+                throw new RuntimeException(message);
+            }
+            throwable.printStackTrace();
         }
-        throwable.printStackTrace();
     }
 
     private static void selfReference() {
@@ -183,8 +175,7 @@
     }
 
     private static void noModification() {
-        Throwable t = new Throwable();
-        t.addSuppressed(null);
+        Throwable t = new NoSuppression(false);
 
         Throwable[] t0 = t.getSuppressed();
         if (t0.length != 0)
@@ -196,5 +187,24 @@
         t0 = t.getSuppressed();
         if (t0.length != 0)
             throw new RuntimeException("Bad nonzero length of suppressed exceptions.");
+
+        Throwable suppressed = new ArithmeticException();
+        t = new NoSuppression(true); // Suppression enabled
+        // Make sure addSuppressed(null) throws an NPE
+        try {
+            t.addSuppressed(null);
+        } catch(NullPointerException e) {
+            ; // Expected
+        }
+        t.addSuppressed(suppressed);
+        t0 = t.getSuppressed();
+        if (t0.length != 1 || t0[0] != suppressed)
+            throw new RuntimeException("Expected suppression did not occur.");
+    }
+
+    private static class NoSuppression extends Throwable {
+        public NoSuppression(boolean enableSuppression) {
+            super("The medium.", null, enableSuppression);
+        }
     }
 }