Skip to content

Commit

Permalink
8288589: Files.readString ignores encoding errors for UTF-16
Browse files Browse the repository at this point in the history
Reviewed-by: rriggs, iris, alanb
  • Loading branch information
naotoj committed Jun 23, 2022
1 parent ef17ee4 commit 2728770
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 88 deletions.
41 changes: 24 additions & 17 deletions src/java.base/share/classes/java/lang/String.java
Expand Up @@ -658,14 +658,22 @@ public String(byte[] bytes, int offset, int length, Charset charset) {

// 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) {
bytes = Arrays.copyOfRange(bytes, offset, offset + length);
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) {
Expand Down Expand Up @@ -791,7 +799,13 @@ private static String newStringNoRepl1(byte[] src, Charset cs) {
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) {
Expand Down Expand Up @@ -1199,23 +1213,16 @@ private static int decodeUTF8_UTF16(byte[] src, int sp, int sl, byte[] dst, int
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();
}

Expand Down
50 changes: 0 additions & 50 deletions test/jdk/java/lang/String/NewStringNoRepl.java

This file was deleted.

85 changes: 85 additions & 0 deletions test/jdk/java/lang/String/NoReplTest.java
@@ -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);
}
}
}
75 changes: 54 additions & 21 deletions test/jdk/java/nio/file/Files/ReadWriteString.java
Expand Up @@ -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;
Expand All @@ -46,7 +48,7 @@
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.
Expand All @@ -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();

Expand Down Expand Up @@ -88,14 +92,14 @@ static byte[] getData() {
*/
@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
};
}

Expand All @@ -105,13 +109,26 @@ public Object[][] getMalformedWrite() throws IOException {
*/
@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.
Expand Down Expand Up @@ -143,16 +160,9 @@ public Object[][] getReadString() {

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

/**
Expand Down Expand Up @@ -241,11 +251,10 @@ public void testReadString(Path path, String text, Charset cs, Charset cs2) thro
*/
@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);
}
}

Expand All @@ -261,16 +270,40 @@ public void testMalformedWrite(Path path, String s, Charset cs) throws IOExcepti
*/
@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 {
Files.readString(path, csRead);
}
}

/**
* 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();
Expand Down

5 comments on commit 2728770

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naotoj
Copy link
Member Author

@naotoj naotoj commented on 2728770 Jun 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/backport jdk19

@openjdk
Copy link

@openjdk openjdk bot commented on 2728770 Jun 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naotoj the backport was successfully created on the branch naotoj-backport-2728770e in my personal fork of openjdk/jdk19. To create a pull request with this backport targeting openjdk/jdk19:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 2728770e from the openjdk/jdk repository.

The commit being backported was authored by Naoto Sato on 23 Jun 2022 and was reviewed by Roger Riggs, Iris Clark and Alan Bateman.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk19:

$ git fetch https://github.com/openjdk-bots/jdk19 naotoj-backport-2728770e:naotoj-backport-2728770e
$ git checkout naotoj-backport-2728770e
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk19 naotoj-backport-2728770e

@GoeLin
Copy link
Member

@GoeLin GoeLin commented on 2728770 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/backport jdk17u-dev

@openjdk
Copy link

@openjdk openjdk bot commented on 2728770 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GoeLin the backport was successfully created on the branch GoeLin-backport-2728770e in my personal fork of openjdk/jdk17u-dev. To create a pull request with this backport targeting openjdk/jdk17u-dev:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 2728770e from the openjdk/jdk repository.

The commit being backported was authored by Naoto Sato on 23 Jun 2022 and was reviewed by Roger Riggs, Iris Clark and Alan Bateman.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk17u-dev:

$ git fetch https://github.com/openjdk-bots/jdk17u-dev.git GoeLin-backport-2728770e:GoeLin-backport-2728770e
$ git checkout GoeLin-backport-2728770e
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk17u-dev.git GoeLin-backport-2728770e

Please sign in to comment.