8042377: BufferedWriter and FilteredOutputStream.close throw IAE if flush and close throw equal exceptions

Explcitly handle IOExceptions in FilteredOutputStream.close() instead of using try-with-resources approach.

Reviewed-by: chegar, alanb
This commit is contained in:
Peter Levart 2015-06-29 08:39:29 -07:00 committed by Brian Burkhalter
parent 0a6597b64e
commit 24d0d5af7c
3 changed files with 239 additions and 9 deletions

View File

@ -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 @@ package java.io;
* @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 @@ class BufferedOutputStream extends FilterOutputStream {
* @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 @@ class BufferedOutputStream extends FilterOutputStream {
* @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 @@ class BufferedOutputStream extends FilterOutputStream {
* @exception IOException if an I/O error occurs.
* @see java.io.FilterOutputStream#out
*/
@Override
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();

View File

@ -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 @@ package java.io;
* @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 @@ class FilterOutputStream extends OutputStream {
* @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 @@ class FilterOutputStream extends OutputStream {
* @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 @@ class FilterOutputStream extends OutputStream {
* @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 @@ class FilterOutputStream extends OutputStream {
* @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 @@ class FilterOutputStream extends OutputStream {
* @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;
}
}
}
}
}

View File

@ -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);
}
}
}
}