OpenJDK / jdk / jdk12
changeset 31421:d6ddd21f6df8
8042377: BufferedWriter and FilteredOutputStream.close throw IAE if flush and close throw equal exceptions
Summary: Explcitly handle IOExceptions in FilteredOutputStream.close() instead of using try-with-resources approach.
Reviewed-by: chegar, alanb
Contributed-by: Peter Levart <peter.levart@gmail.com>
author | bpb |
---|---|
date | Mon, 29 Jun 2015 08:39:29 -0700 |
parents | 7ee930c87f08 |
children | 67cd03bc9cce |
files | jdk/src/java.base/share/classes/java/io/BufferedOutputStream.java jdk/src/java.base/share/classes/java/io/FilterOutputStream.java jdk/test/java/io/FilterOutputStream/SuppressedException.java |
diffstat | 3 files changed, 239 insertions(+), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/jdk/src/java.base/share/classes/java/io/BufferedOutputStream.java Mon Jun 29 17:11:17 2015 +0800 +++ b/jdk/src/java.base/share/classes/java/io/BufferedOutputStream.java Mon Jun 29 08:39:29 2015 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2015, 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 @@ -34,8 +34,7 @@ * @author Arthur van Hoff * @since 1.0 */ -public -class BufferedOutputStream extends FilterOutputStream { +public class BufferedOutputStream extends FilterOutputStream { /** * The internal buffer where data is stored. */ @@ -90,6 +89,7 @@ * @param b the byte to be written. * @exception IOException if an I/O error occurs. */ + @Override public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); @@ -113,6 +113,7 @@ * @param len the number of bytes to write. * @exception IOException if an I/O error occurs. */ + @Override public synchronized void write(byte b[], int off, int len) throws IOException { if (len >= buf.length) { /* If the request length exceeds the size of the output buffer, @@ -136,6 +137,7 @@ * @exception IOException if an I/O error occurs. * @see java.io.FilterOutputStream#out */ + @Override public synchronized void flush() throws IOException { flushBuffer(); out.flush();
--- a/jdk/src/java.base/share/classes/java/io/FilterOutputStream.java Mon Jun 29 17:11:17 2015 +0800 +++ b/jdk/src/java.base/share/classes/java/io/FilterOutputStream.java Mon Jun 29 08:39:29 2015 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2015, 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 @@ -41,13 +41,15 @@ * @author Jonathan Payne * @since 1.0 */ -public -class FilterOutputStream extends OutputStream { +public class FilterOutputStream extends OutputStream { /** * The underlying output stream to be filtered. */ protected OutputStream out; + /** + * Whether the stream is closed; implicitly initialized to false. + */ private boolean closed; /** @@ -75,6 +77,7 @@ * @param b the <code>byte</code>. * @exception IOException if an I/O error occurs. */ + @Override public void write(int b) throws IOException { out.write(b); } @@ -95,6 +98,7 @@ * @exception IOException if an I/O error occurs. * @see java.io.FilterOutputStream#write(byte[], int, int) */ + @Override public void write(byte b[]) throws IOException { write(b, 0, b.length); } @@ -119,6 +123,7 @@ * @exception IOException if an I/O error occurs. * @see java.io.FilterOutputStream#write(int) */ + @Override public void write(byte b[], int off, int len) throws IOException { if ((off | len | (b.length - (len + off)) | (off + len)) < 0) throw new IndexOutOfBoundsException(); @@ -138,6 +143,7 @@ * @exception IOException if an I/O error occurs. * @see java.io.FilterOutputStream#out */ + @Override public void flush() throws IOException { out.flush(); } @@ -154,13 +160,40 @@ * @see java.io.FilterOutputStream#flush() * @see java.io.FilterOutputStream#out */ - @SuppressWarnings("try") + @Override public void close() throws IOException { - if (closed) + if (closed) { return; + } closed = true; - try (OutputStream ostream = out) { + + Throwable flushException = null; + try { flush(); + } catch (Throwable e) { + flushException = e; + throw e; + } finally { + if (flushException == null) { + out.close(); + } else { + try { + out.close(); + } catch (Throwable closeException) { + // evaluate possible precedence of flushException over closeException + if ((flushException instanceof ThreadDeath) && + !(closeException instanceof ThreadDeath)) { + flushException.addSuppressed(closeException); + throw (ThreadDeath) flushException; + } + + if (flushException != closeException) { + closeException.addSuppressed(flushException); + } + + throw closeException; + } + } } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/io/FilterOutputStream/SuppressedException.java Mon Jun 29 08:39:29 2015 -0700 @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2015, 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.BufferedOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/* + * @test + * @bug 8042377 + * @summary Ensure suppressed exceptions are properly handled in close() + */ +public class SuppressedException { + private static final String CLOSE_MESSAGE = "Close exception"; + private static final String FLUSH_MESSAGE = "Flush exception"; + private static final String SAME_MESSAGE = "Same exception"; + + public static void main(String[] args) throws java.io.IOException { + SuppressedException test = new SuppressedException(); + test.test(); + } + + private FilterOutputStream createOutputStream(OutputStream out, + boolean isBuffered) { + return isBuffered ? new BufferedOutputStream(out) : + new FilterOutputStream(out); + } + + private void test() { + int failures = 0; + FilterOutputStream buf; + + boolean[] isBuffered = new boolean[] {false, true}; + for (boolean buffered : isBuffered) { + System.err.println("\n>>> Buffered: " + buffered + " <<<"); + System.err.flush(); + + try { + buf = createOutputStream(new OutputStreamFailsWithException(), + buffered); + buf.close(); + System.err.println("\nNo IOException thrown for same exception"); + failures++; + } catch (IOException expected) { + if (!expected.getMessage().equals(SAME_MESSAGE)) { + System.err.println("\nIOException with unexpected message thrown"); + expected.printStackTrace(); + failures++; + } + } catch (IllegalArgumentException unexpected) { + System.err.println("\nUnexpected IllegalArgumentException thrown"); + unexpected.printStackTrace(); + failures++; + } + + try { + buf = createOutputStream( + new OutputStreamFailsWithException(false, false), + buffered); + buf.close(); + } catch (IOException e) { + System.err.println("\nUnexpected IOException thrown"); + e.printStackTrace(); + failures++; + } + + try { + buf = createOutputStream( + new OutputStreamFailsWithException(true, false), + buffered); + buf.close(); + } catch (IOException e) { + if (!e.getMessage().equals(CLOSE_MESSAGE)) { + System.err.println("\nIOException with unexpected message thrown"); + e.printStackTrace(); + failures++; + } + } + + try { + buf = createOutputStream( + new OutputStreamFailsWithException(false, true), + buffered); + buf.close(); + } catch (IOException e) { + if (!e.getMessage().equals(FLUSH_MESSAGE)) { + System.err.println("\nIOException with unexpected message thrown"); + e.printStackTrace(); + failures++; + } + } + + try { + buf = createOutputStream( + new OutputStreamFailsWithException(true, true), + buffered); + buf.close(); + } catch (IOException e) { + if (!e.getMessage().equals(CLOSE_MESSAGE)) { + System.err.println("\nIOException with unexpected message thrown"); + e.printStackTrace(); + failures++; + } + + Throwable[] suppressed = e.getSuppressed(); + if (suppressed == null) { + System.err.println("\nExpected suppressed exception not present"); + e.printStackTrace(); + failures++; + } else if (suppressed.length != 1) { + System.err.println("\nUnexpected number of suppressed exceptions"); + e.printStackTrace(); + failures++; + } else if (!(suppressed[0] instanceof IOException)) { + System.err.println("\nSuppressed exception is not an IOException"); + e.printStackTrace(); + failures++; + } else if (!suppressed[0].getMessage().equals(FLUSH_MESSAGE)) { + System.err.println("\nIOException with unexpected message thrown"); + e.printStackTrace(); + failures++; + } + } + } + + if (failures > 0) { + throw new RuntimeException("Test failed with " + failures + " errors"); + } else { + System.out.println("Test succeeded."); + } + } + + class OutputStreamFailsWithException extends OutputStream { + private final IOException sameException = new IOException(SAME_MESSAGE); + + private final Boolean throwSeparateCloseException; + private final Boolean throwSeparateFlushException; + + OutputStreamFailsWithException() { + throwSeparateCloseException = null; + throwSeparateFlushException = null; + } + + OutputStreamFailsWithException(boolean throwCloseException, + boolean throwFlushException) { + throwSeparateCloseException = throwCloseException; + throwSeparateFlushException = throwFlushException; + } + + @Override + public void write(int i) throws IOException { + throw new UnsupportedOperationException(""); + } + + @Override + public void flush() throws IOException { + System.out.println("flush()"); + if (throwSeparateFlushException == null) { + throw sameException; + } else if (throwSeparateFlushException) { + throw new IOException(FLUSH_MESSAGE); + } + } + + @Override + public void close() throws IOException { + System.out.println("close()"); + if (throwSeparateCloseException == null) { + throw sameException; + } else if (throwSeparateCloseException) { + throw new IOException(CLOSE_MESSAGE); + } + } + } +}