jdk/src/share/classes/java/lang/Throwable.java

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 1994, 2006, 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 168,177 **** --- 168,230 ---- * @serial */ private String detailMessage; /** + * {@linkplain #setStackTrace(StackTraceElement[]) Setting the + * stack trace} to a one-element array containing this sentinel + * value indicates future attempts to set the stack trace will be + * ignored. The sentinal is equal to the result of calling:<br> + * {@code new StackTraceElement("", "", null, Integer.MIN_VALUE)} + */ + private static final StackTraceElement STACK_TRACE_ELEMENT_SENTINEL = + new StackTraceElement("", "", null, Integer.MIN_VALUE); + + /** + * Sentinel value used in the serial form to indicate an immutable + * stack trace. + */ + private static final StackTraceElement[] STACK_TRACE_SENTINEL = initStackTraceSentinel(); + + private static StackTraceElement[] initStackTraceSentinel() { + StackTraceElement[] ste = new StackTraceElement[1]; + ste[0] = STACK_TRACE_ELEMENT_SENTINEL; + return ste; + } + + /** + * A value indicating the stack trace field has not yet been initialized. + */ + 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, the three fields + * of Throwable that are writable in response to user actions, + * cause, stackTrace, and suppressedExceptions obey the following + * protocol: + * + * 1) The fields are initialized to a non-null sentinel value + * which indicates the value has logically not been set. + * + * 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, * it indicates that the cause of this throwable has not yet been * initialized.
*** 182,221 **** private Throwable cause = this; /** * The stack trace, as returned by {@link #getStackTrace()}. * * @serial * @since 1.4 */ ! private StackTraceElement[] stackTrace; ! /* ! * This field is lazily initialized on first use or serialization and ! * nulled out when fillInStackTrace is called. ! */ /** ! * The list of suppressed exceptions, as returned by ! * {@link #getSuppressedExceptions()}. * * @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. ! */ /** Message for trying to suppress a null exception. */ private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception."; /** Caption for labeling causative exception stack traces */ private static final String CAUSE_CAPTION = "Caused by: "; /** Caption for labeling suppressed exception stack traces */ private static final String SUPPRESSED_CAPTION = "Suppressed: "; --- 235,276 ---- private Throwable cause = this; /** * The stack trace, as returned by {@link #getStackTrace()}. * + * The field is initialized to a zero-length array. A {@code + * null} value of this field indicates subsequent calls to {@link + * #setStackTrace()} will be be no-ops. + * * @serial * @since 1.4 */ ! private StackTraceElement[] stackTrace = EMPTY_STACK; ! ! // Setting this static field introduces an acceptable ! // initialization dependency on a few java.util classes. ! private static final List<Throwable> suppressedSentinel = ! Collections.unmodifiableList(new ArrayList<Throwable>(0)); /** ! * 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 = suppressedSentinel; /** 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: "; /** Caption for labeling suppressed exception stack traces */ private static final String SUPPRESSED_CAPTION = "Suppressed: ";
*** 570,580 **** StackTraceElement[] trace = getOurStackTrace(); for (StackTraceElement traceElement : trace) s.println("\tat " + traceElement); // Print suppressed exceptions, if any ! for (Throwable se : getSuppressedExceptions()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu); // Print cause, if any Throwable ourCause = getCause(); if (ourCause != null) --- 625,635 ---- StackTraceElement[] trace = getOurStackTrace(); for (StackTraceElement traceElement : trace) s.println("\tat " + traceElement); // Print suppressed exceptions, if any ! for (Throwable se : getSuppressed()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu); // Print cause, if any Throwable ourCause = getCause(); if (ourCause != null)
*** 611,621 **** s.println(prefix + "\tat " + trace[i]); if (framesInCommon != 0) s.println(prefix + "\t... " + framesInCommon + " more"); // Print suppressed exceptions, if any ! for (Throwable se : getSuppressedExceptions()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, prefix +"\t", dejaVu); // Print cause, if any Throwable ourCause = getCause(); --- 666,676 ---- s.println(prefix + "\tat " + trace[i]); if (framesInCommon != 0) s.println(prefix + "\t... " + framesInCommon + " more"); // Print suppressed exceptions, if any ! for (Throwable se : getSuppressed()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, prefix +"\t", dejaVu); // Print cause, if any Throwable ourCause = getCause();
*** 716,731 **** return getOurStackTrace().clone(); } private synchronized StackTraceElement[] getOurStackTrace() { // Initialize stack trace if this is the first call to this method ! if (stackTrace == null) { int depth = getStackTraceDepth(); stackTrace = new StackTraceElement[depth]; for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i); } return stackTrace; } /** * Sets the stack trace elements that will be returned by --- 771,789 ---- return getOurStackTrace().clone(); } private synchronized StackTraceElement[] getOurStackTrace() { // Initialize stack trace if this is the first call to this method ! if (stackTrace == EMPTY_STACK) { int depth = getStackTraceDepth(); stackTrace = new StackTraceElement[depth]; for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i); + } else if (stackTrace == null) { + return EMPTY_STACK; } + return stackTrace; } /** * Sets the stack trace elements that will be returned by
*** 736,762 **** * advanced systems, allows the client to override the default * stack trace that is either generated by {@link #fillInStackTrace()} * when a throwable is constructed or deserialized when a throwable is * read from a serialization stream. * * @param stackTrace the stack trace elements to be associated with * this {@code Throwable}. The specified array is copied by this * call; changes in the specified array after the method invocation * returns will have no affect on this {@code Throwable}'s stack * trace. * ! * @throws NullPointerException if {@code stackTrace} is ! * {@code null}, or if any of the elements of * {@code stackTrace} are {@code null} * * @since 1.4 */ public void setStackTrace(StackTraceElement[] stackTrace) { ! StackTraceElement[] defensiveCopy = stackTrace.clone(); ! for (int i = 0; i < defensiveCopy.length; i++) if (defensiveCopy[i] == null) throw new NullPointerException("stackTrace[" + i + "]"); synchronized (this) { this.stackTrace = defensiveCopy; } } --- 794,833 ---- * advanced systems, allows the client to override the default * stack trace that is either generated by {@link #fillInStackTrace()} * when a throwable is constructed or deserialized when a throwable is * read from a serialization stream. * + * <p>If the stack trace is set to {@code null}, then future calls + * to this method have no effect on this {@code Throwable}. + * * @param stackTrace the stack trace elements to be associated with * this {@code Throwable}. The specified array is copied by this * call; changes in the specified array after the method invocation * returns will have no affect on this {@code Throwable}'s stack * trace. * ! * @throws NullPointerException if any of the elements of * {@code stackTrace} are {@code null} * * @since 1.4 */ public void setStackTrace(StackTraceElement[] stackTrace) { ! if (this.stackTrace == null) // Immutable stack ! return; ! ! StackTraceElement[] defensiveCopy; ! ! if (stackTrace == null) { ! defensiveCopy = stackTrace; ! } else { ! defensiveCopy = stackTrace.clone(); ! ! for (int i = 0; i < defensiveCopy.length; i++) { if (defensiveCopy[i] == null) throw new NullPointerException("stackTrace[" + i + "]"); + } + } synchronized (this) { this.stackTrace = defensiveCopy; } }
*** 778,815 **** * @throws IndexOutOfBoundsException if {@code index < 0 || * index >= getStackTraceDepth() } */ native StackTraceElement getStackTraceElement(int index); 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); } } suppressedExceptions = suppressed; } private synchronized void writeObject(ObjectOutputStream s) ! throws IOException ! { ! getOurStackTrace(); // Ensure that stackTrace field is initialized. ! s.defaultWriteObject(); } /** * Adds the specified exception to the list of exceptions that * were suppressed, typically by the {@code try}-with-resources * statement, in order to deliver this exception. * * <p>Note that when one exception {@linkplain * #initCause(Throwable) causes} another exception, the first * exception is usually caught and then the second exception is * thrown in response. In contrast, when one exception suppresses * another, two exceptions are thrown in sibling code blocks, such --- 849,958 ---- * @throws IndexOutOfBoundsException if {@code index < 0 || * index >= getStackTraceDepth() } */ 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. A single-element stack trace whose entry is + * equal to {@code new StackTraceElement("", "", null, + * Integer.MIN_VALUE)} results in a {@code null} {@code + * stackTrace} field. + * + * Note that there are no constraints on the value the {@code + * cause} field can hold; both {@code null} and 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>(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); } } + + // If suppressed is a zero-length list, use the sentinel + // value. + if (suppressed != null && suppressed.isEmpty()) + suppressedExceptions = suppressedSentinel; + else suppressedExceptions = suppressed; + + // Check for the marker of an immutable stack trace + if (stackTrace != null) { + // Share zero-length stack traces + if (stackTrace.length == 0) { + stackTrace = EMPTY_STACK; + } else if (stackTrace.length == 1 && + STACK_TRACE_ELEMENT_SENTINEL.equals(stackTrace[0])) { + stackTrace = null; + } else { // Verify stack trace elements are non-null. + for(StackTraceElement ste : stackTrace) { + if (ste == null) + throw new NullPointerException("null StackTraceElement in serial stream. "); + } + } + } + + // A null stackTrace field in the serial form can result from + // an exception serialied without that field. Such exceptions + // are now treated as having immutable stack traces. } + /** + * Write a {@code Throwable} object to a stream. A {@code null} + * stack trace field is represented in the serial form as a + * one-element array whose element is equal to {@code new + * StackTraceElement("", "", null, Integer.MIN_VALUE)}. + */ private synchronized void writeObject(ObjectOutputStream s) ! throws IOException { ! // Ensure that the stackTrace field is initialized to a ! // non-null value, if appropriate. As of JDK 7, a null stack ! // trace field is a valid value indicating the stack trace ! // should not be set. ! getOurStackTrace(); ! ObjectOutputStream.PutField fields = s.putFields(); ! ! fields.put("detailMessage", detailMessage); ! fields.put("cause", cause); ! // Serialize a null stacktrace using the stack trace sentinel. ! if (stackTrace == null) ! fields.put("stackTrace", STACK_TRACE_SENTINEL); ! else ! fields.put("stackTrace", stackTrace); ! fields.put("suppressedExceptions", suppressedExceptions); ! ! s.writeFields(); } /** * Adds the specified exception to the list of exceptions that * 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 * thrown in response. In contrast, when one exception suppresses * another, two exceptions are thrown in sibling code blocks, such
*** 817,855 **** * control flow can only continue with one exception so the second * is recorded as a suppressed exception of the first. * * @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. * @since 1.7 */ ! public synchronized void addSuppressedException(Throwable exception) { ! if (exception == null) ! throw new NullPointerException(NULL_CAUSE_MESSAGE); if (exception == this) ! throw new IllegalArgumentException("Self-suppression not permitted"); - if (suppressedExceptions == null) - suppressedExceptions = new ArrayList<Throwable>(); suppressedExceptions.add(exception); } private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0]; /** * Returns an array containing all of the exceptions that were * suppressed, typically by the {@code try}-with-resources * statement, in order to deliver this exception. * * @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) return EMPTY_THROWABLE_ARRAY; else return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY); } } --- 960,1016 ---- * control flow can only continue with one exception so the second * is recorded as a suppressed exception of the first. * * @param exception the exception to be added to the list of * 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 * @since 1.7 */ ! public synchronized void addSuppressed(Throwable exception) { if (exception == this) ! throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE); ! ! if (exception == null) { ! if (suppressedExceptions == suppressedSentinel) { ! 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 == suppressedSentinel) ! suppressedExceptions = new ArrayList<Throwable>(1); ! ! assert suppressedExceptions != suppressedSentinel; suppressedExceptions.add(exception); } + } private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0]; /** * Returns an array containing all of the exceptions that were * 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[] getSuppressed() { ! if (suppressedExceptions == suppressedSentinel || ! suppressedExceptions == null) return EMPTY_THROWABLE_ARRAY; else return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY); } }