8288589: Files.readString ignores encoding errors for UTF-16

Reviewed-by: rriggs, iris, alanb
This commit is contained in:
Naoto Sato 2022-06-23 15:49:43 +00:00
parent ef17ee4dea
commit 2728770e3d
4 changed files with 163 additions and 88 deletions
src/java.base/share/classes/java/lang
test/jdk/java

@ -658,6 +658,8 @@ public final class String
// decode using CharsetDecoder
int en = scale(length, cd.maxCharsPerByte());
cd.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
char[] ca = new char[en];
if (charset.getClass().getClassLoader0() != null &&
System.getSecurityManager() != null) {
@ -665,7 +667,13 @@ public final class String
offset = 0;
}
int caLen = decodeWithDecoder(cd, ca, bytes, offset, length);
int caLen;
try {
caLen = decodeWithDecoder(cd, ca, bytes, offset, length);
} catch (CharacterCodingException x) {
// Substitution is enabled, so this shouldn't happen
throw new Error(x);
}
if (COMPACT_STRINGS) {
byte[] bs = StringUTF16.compress(ca, 0, caLen);
if (bs != null) {
@ -791,7 +799,13 @@ public final class String
System.getSecurityManager() != null) {
src = Arrays.copyOf(src, len);
}
int caLen = decodeWithDecoder(cd, ca, src, 0, src.length);
int caLen;
try {
caLen = decodeWithDecoder(cd, ca, src, 0, src.length);
} catch (CharacterCodingException x) {
// throw via IAE
throw new IllegalArgumentException(x);
}
if (COMPACT_STRINGS) {
byte[] bs = StringUTF16.compress(ca, 0, caLen);
if (bs != null) {
@ -1199,23 +1213,16 @@ public final class String
return dp;
}
private static int decodeWithDecoder(CharsetDecoder cd, char[] dst, byte[] src, int offset, int length) {
private static int decodeWithDecoder(CharsetDecoder cd, char[] dst, byte[] src, int offset, int length)
throws CharacterCodingException {
ByteBuffer bb = ByteBuffer.wrap(src, offset, length);
CharBuffer cb = CharBuffer.wrap(dst, 0, dst.length);
cd.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
try {
CoderResult cr = cd.decode(bb, cb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = cd.flush(cb);
if (!cr.isUnderflow())
cr.throwException();
} catch (CharacterCodingException x) {
// Substitution is always enabled,
// so this shouldn't happen
throw new Error(x);
}
CoderResult cr = cd.decode(bb, cb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = cd.flush(cb);
if (!cr.isUnderflow())
cr.throwException();
return cb.position();
}

@ -1,50 +0,0 @@
/*
* Copyright (c) 2022, 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.
*/
/*
* @test
* @bug 8286287
* @summary Verifies newStringNoRepl() does not throw an Error.
*/
import java.io.IOException;
import java.nio.file.Files;
import java.util.HexFormat;
import static java.nio.charset.StandardCharsets.UTF_16;
public class NewStringNoRepl {
private final static byte[] MALFORMED_UTF16 = {(byte)0x00, (byte)0x20, (byte)0x00};
public static void main(String... args) throws IOException {
var f = Files.createTempFile(null, null);
try (var fos = Files.newOutputStream(f)) {
fos.write(MALFORMED_UTF16);
}
System.out.println("Returned bytes: " +
HexFormat.of()
.withPrefix("x")
.withUpperCase()
.formatHex(Files.readString(f, UTF_16).getBytes(UTF_16)));
Files.delete(f);
}
}

@ -0,0 +1,85 @@
/*
* Copyright (c) 2022, 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.
*/
/*
* @test
* @bug 8286287 8288589
* @summary Tests for *NoRepl() shared secret methods.
* @run testng NoReplTest
* @modules jdk.charsets
*/
import java.io.IOException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HexFormat;
import static java.nio.charset.StandardCharsets.UTF_16;
import org.testng.annotations.Test;
@Test
public class NoReplTest {
private final static byte[] MALFORMED_UTF16 = {(byte)0x00, (byte)0x20, (byte)0x00};
private final static String MALFORMED_WINDOWS_1252 = "\u0080\u041e";
private final static Charset WINDOWS_1252 = Charset.forName("windows-1252");
/**
* Verifies newStringNoRepl() throws a CharacterCodingException.
* The method is invoked by `Files.readString()` method.
*/
@Test
public void newStringNoReplTest() throws IOException {
var f = Files.createTempFile(null, null);
try (var fos = Files.newOutputStream(f)) {
fos.write(MALFORMED_UTF16);
var read = Files.readString(f, UTF_16);
throw new RuntimeException("Exception should be thrown for a malformed input. Bytes read: " +
HexFormat.of()
.withPrefix("x")
.withUpperCase()
.formatHex(read.getBytes(UTF_16)));
} catch (CharacterCodingException cce) {
// success
} finally {
Files.delete(f);
}
}
/**
* Verifies getBytesNoRepl() throws a CharacterCodingException.
* The method is invoked by `Files.writeString()` method.
*/
@Test
public void getBytesNoReplTest() throws IOException {
var f = Files.createTempFile(null, null);
try {
Files.writeString(f, MALFORMED_WINDOWS_1252, WINDOWS_1252);
throw new RuntimeException("Exception should be thrown");
} catch (CharacterCodingException cce) {
// success
} finally {
Files.delete(f);
}
}
}

@ -24,10 +24,12 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_16;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.file.Files;
import java.nio.file.OpenOption;
@ -46,7 +48,7 @@ import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/* @test
* @bug 8201276 8205058 8209576 8287541
* @bug 8201276 8205058 8209576 8287541 8288589
* @build ReadWriteString PassThroughFileSystem
* @run testng ReadWriteString
* @summary Unit test for methods for Files readString and write methods.
@ -60,6 +62,8 @@ public class ReadWriteString {
final String TEXT_UNICODE = "\u201CHello\u201D";
final String TEXT_ASCII = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n abcdefghijklmnopqrstuvwxyz\n 1234567890\n";
private static final String JA_STRING = "\u65e5\u672c\u8a9e\u6587\u5b57\u5217";
private static final Charset WINDOWS_1252 = Charset.forName("windows-1252");
private static final Charset WINDOWS_31J = Charset.forName("windows-31j");
static byte[] data = getData();
@ -88,14 +92,14 @@ public class ReadWriteString {
*/
@DataProvider(name = "malformedWrite")
public Object[][] getMalformedWrite() throws IOException {
Path path = Files.createTempFile("malformedWrite", null);
Path path = Files.createFile(Path.of("malformedWrite"));
return new Object[][]{
{path, "\ud800", null}, //the default Charset is UTF_8
{path, "\u00A0\u00A1", US_ASCII},
{path, "\ud800", UTF_8},
{path, JA_STRING, ISO_8859_1},
{path, "\u041e", Charset.forName("windows-1252")}, // cyrillic capital letter O
{path, "\u091c", Charset.forName("windows-31j")}, // devanagari letter ja
{path, "\u041e", WINDOWS_1252}, // cyrillic capital letter O
{path, "\u091c", WINDOWS_31J}, // devanagari letter ja
};
}
@ -105,13 +109,26 @@ public class ReadWriteString {
*/
@DataProvider(name = "illegalInput")
public Object[][] getIllegalInput() throws IOException {
Path path = Files.createTempFile("illegalInput", null);
Path path = Files.createFile(Path.of("illegalInput"));
return new Object[][]{
{path, data, ISO_8859_1, null},
{path, data, ISO_8859_1, UTF_8}
};
}
/*
* DataProvider for illegal input bytes test
*/
@DataProvider(name = "illegalInputBytes")
public Object[][] getIllegalInputBytes() throws IOException {
return new Object[][]{
{new byte[] {(byte)0x00, (byte)0x20, (byte)0x00}, UTF_16, MalformedInputException.class},
{new byte[] {-50}, UTF_16, MalformedInputException.class},
{new byte[] {(byte)0x81}, WINDOWS_1252, UnmappableCharacterException.class}, // unused in Cp1252
{new byte[] {(byte)0x81, (byte)0xff}, WINDOWS_31J, UnmappableCharacterException.class}, // invalid trailing byte
};
}
/*
* DataProvider for writeString test
* Writes the data using both the existing and new method and compares the results.
@ -143,16 +160,9 @@ public class ReadWriteString {
@BeforeClass
void setup() throws IOException {
testFiles[0] = Files.createTempFile("readWriteString", null);
testFiles[1] = Files.createTempFile("writeString_file1", null);
testFiles[2] = Files.createTempFile("writeString_file2", null);
}
@AfterClass
void cleanup() throws IOException {
for (Path path : testFiles) {
Files.deleteIfExists(path);
}
testFiles[0] = Files.createFile(Path.of("readWriteString"));
testFiles[1] = Files.createFile(Path.of("writeString_file1"));
testFiles[2] = Files.createFile(Path.of("writeString_file2"));
}
/**
@ -241,11 +251,10 @@ public class ReadWriteString {
*/
@Test(dataProvider = "malformedWrite", expectedExceptions = UnmappableCharacterException.class)
public void testMalformedWrite(Path path, String s, Charset cs) throws IOException {
path.toFile().deleteOnExit();
if (cs == null) {
Files.writeString(path, s, CREATE);
Files.writeString(path, s);
} else {
Files.writeString(path, s, cs, CREATE);
Files.writeString(path, s, cs);
}
}
@ -261,9 +270,8 @@ public class ReadWriteString {
*/
@Test(dataProvider = "illegalInput", expectedExceptions = MalformedInputException.class)
public void testMalformedRead(Path path, byte[] data, Charset csWrite, Charset csRead) throws IOException {
path.toFile().deleteOnExit();
String temp = new String(data, csWrite);
Files.writeString(path, temp, csWrite, CREATE);
Files.writeString(path, temp, csWrite);
if (csRead == null) {
Files.readString(path);
} else {
@ -271,6 +279,31 @@ public class ReadWriteString {
}
}
/**
* Verifies that IOException is thrown when reading a file containing
* illegal bytes
*
* @param data the data used for the test
* @param csRead the Charset to use for reading the file
* @param expected exception class
* @throws IOException when the Charset used for reading the file is incorrect
*/
@Test(dataProvider = "illegalInputBytes")
public void testMalformedReadBytes(byte[] data, Charset csRead, Class<CharacterCodingException> expected)
throws IOException {
Path path = Path.of("illegalInputBytes");
Files.write(path, data);
try {
Files.readString(path, csRead);
} catch (MalformedInputException | UnmappableCharacterException e) {
if (expected.isInstance(e)) {
// success
return;
}
}
throw new RuntimeException("An instance of " + expected + " should be thrown");
}
private void checkNullPointerException(Callable<?> c) {
try {
c.call();