Skip to content

Commit a716f79

Browse files
committedJun 23, 2022
8288589: Files.readString ignores encoding errors for UTF-16
Backport-of: 2728770
1 parent 4c9ea7e commit a716f79

File tree

4 files changed

+163
-88
lines changed

4 files changed

+163
-88
lines changed
 

‎src/java.base/share/classes/java/lang/String.java

+24-17
Original file line numberDiff line numberDiff line change
@@ -658,14 +658,22 @@ public String(byte[] bytes, int offset, int length, Charset charset) {
658658

659659
// decode using CharsetDecoder
660660
int en = scale(length, cd.maxCharsPerByte());
661+
cd.onMalformedInput(CodingErrorAction.REPLACE)
662+
.onUnmappableCharacter(CodingErrorAction.REPLACE);
661663
char[] ca = new char[en];
662664
if (charset.getClass().getClassLoader0() != null &&
663665
System.getSecurityManager() != null) {
664666
bytes = Arrays.copyOfRange(bytes, offset, offset + length);
665667
offset = 0;
666668
}
667669

668-
int caLen = decodeWithDecoder(cd, ca, bytes, offset, length);
670+
int caLen;
671+
try {
672+
caLen = decodeWithDecoder(cd, ca, bytes, offset, length);
673+
} catch (CharacterCodingException x) {
674+
// Substitution is enabled, so this shouldn't happen
675+
throw new Error(x);
676+
}
669677
if (COMPACT_STRINGS) {
670678
byte[] bs = StringUTF16.compress(ca, 0, caLen);
671679
if (bs != null) {
@@ -791,7 +799,13 @@ private static String newStringNoRepl1(byte[] src, Charset cs) {
791799
System.getSecurityManager() != null) {
792800
src = Arrays.copyOf(src, len);
793801
}
794-
int caLen = decodeWithDecoder(cd, ca, src, 0, src.length);
802+
int caLen;
803+
try {
804+
caLen = decodeWithDecoder(cd, ca, src, 0, src.length);
805+
} catch (CharacterCodingException x) {
806+
// throw via IAE
807+
throw new IllegalArgumentException(x);
808+
}
795809
if (COMPACT_STRINGS) {
796810
byte[] bs = StringUTF16.compress(ca, 0, caLen);
797811
if (bs != null) {
@@ -1199,23 +1213,16 @@ private static int decodeUTF8_UTF16(byte[] src, int sp, int sl, byte[] dst, int
11991213
return dp;
12001214
}
12011215

1202-
private static int decodeWithDecoder(CharsetDecoder cd, char[] dst, byte[] src, int offset, int length) {
1216+
private static int decodeWithDecoder(CharsetDecoder cd, char[] dst, byte[] src, int offset, int length)
1217+
throws CharacterCodingException {
12031218
ByteBuffer bb = ByteBuffer.wrap(src, offset, length);
12041219
CharBuffer cb = CharBuffer.wrap(dst, 0, dst.length);
1205-
cd.onMalformedInput(CodingErrorAction.REPLACE)
1206-
.onUnmappableCharacter(CodingErrorAction.REPLACE);
1207-
try {
1208-
CoderResult cr = cd.decode(bb, cb, true);
1209-
if (!cr.isUnderflow())
1210-
cr.throwException();
1211-
cr = cd.flush(cb);
1212-
if (!cr.isUnderflow())
1213-
cr.throwException();
1214-
} catch (CharacterCodingException x) {
1215-
// Substitution is always enabled,
1216-
// so this shouldn't happen
1217-
throw new Error(x);
1218-
}
1220+
CoderResult cr = cd.decode(bb, cb, true);
1221+
if (!cr.isUnderflow())
1222+
cr.throwException();
1223+
cr = cd.flush(cb);
1224+
if (!cr.isUnderflow())
1225+
cr.throwException();
12191226
return cb.position();
12201227
}
12211228

‎test/jdk/java/lang/String/NewStringNoRepl.java

-50
This file was deleted.
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8286287 8288589
27+
* @summary Tests for *NoRepl() shared secret methods.
28+
* @run testng NoReplTest
29+
* @modules jdk.charsets
30+
*/
31+
32+
import java.io.IOException;
33+
import java.nio.charset.CharacterCodingException;
34+
import java.nio.charset.Charset;
35+
import java.nio.file.Files;
36+
import java.util.HexFormat;
37+
import static java.nio.charset.StandardCharsets.UTF_16;
38+
39+
import org.testng.annotations.Test;
40+
41+
@Test
42+
public class NoReplTest {
43+
private final static byte[] MALFORMED_UTF16 = {(byte)0x00, (byte)0x20, (byte)0x00};
44+
private final static String MALFORMED_WINDOWS_1252 = "\u0080\u041e";
45+
private final static Charset WINDOWS_1252 = Charset.forName("windows-1252");
46+
47+
/**
48+
* Verifies newStringNoRepl() throws a CharacterCodingException.
49+
* The method is invoked by `Files.readString()` method.
50+
*/
51+
@Test
52+
public void newStringNoReplTest() throws IOException {
53+
var f = Files.createTempFile(null, null);
54+
try (var fos = Files.newOutputStream(f)) {
55+
fos.write(MALFORMED_UTF16);
56+
var read = Files.readString(f, UTF_16);
57+
throw new RuntimeException("Exception should be thrown for a malformed input. Bytes read: " +
58+
HexFormat.of()
59+
.withPrefix("x")
60+
.withUpperCase()
61+
.formatHex(read.getBytes(UTF_16)));
62+
} catch (CharacterCodingException cce) {
63+
// success
64+
} finally {
65+
Files.delete(f);
66+
}
67+
}
68+
69+
/**
70+
* Verifies getBytesNoRepl() throws a CharacterCodingException.
71+
* The method is invoked by `Files.writeString()` method.
72+
*/
73+
@Test
74+
public void getBytesNoReplTest() throws IOException {
75+
var f = Files.createTempFile(null, null);
76+
try {
77+
Files.writeString(f, MALFORMED_WINDOWS_1252, WINDOWS_1252);
78+
throw new RuntimeException("Exception should be thrown");
79+
} catch (CharacterCodingException cce) {
80+
// success
81+
} finally {
82+
Files.delete(f);
83+
}
84+
}
85+
}

‎test/jdk/java/nio/file/Files/ReadWriteString.java

+54-21
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import java.io.ByteArrayOutputStream;
2525
import java.io.IOException;
2626
import java.nio.charset.Charset;
27+
import java.nio.charset.CharacterCodingException;
2728
import java.nio.charset.MalformedInputException;
2829
import java.nio.charset.UnmappableCharacterException;
29-
import static java.nio.charset.StandardCharsets.US_ASCII;
3030
import static java.nio.charset.StandardCharsets.ISO_8859_1;
31+
import static java.nio.charset.StandardCharsets.US_ASCII;
32+
import static java.nio.charset.StandardCharsets.UTF_16;
3133
import static java.nio.charset.StandardCharsets.UTF_8;
3234
import java.nio.file.Files;
3335
import java.nio.file.OpenOption;
@@ -46,7 +48,7 @@
4648
import org.testng.annotations.Test;
4749

4850
/* @test
49-
* @bug 8201276 8205058 8209576 8287541
51+
* @bug 8201276 8205058 8209576 8287541 8288589
5052
* @build ReadWriteString PassThroughFileSystem
5153
* @run testng ReadWriteString
5254
* @summary Unit test for methods for Files readString and write methods.
@@ -60,6 +62,8 @@ public class ReadWriteString {
6062
final String TEXT_UNICODE = "\u201CHello\u201D";
6163
final String TEXT_ASCII = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n abcdefghijklmnopqrstuvwxyz\n 1234567890\n";
6264
private static final String JA_STRING = "\u65e5\u672c\u8a9e\u6587\u5b57\u5217";
65+
private static final Charset WINDOWS_1252 = Charset.forName("windows-1252");
66+
private static final Charset WINDOWS_31J = Charset.forName("windows-31j");
6367

6468
static byte[] data = getData();
6569

@@ -88,14 +92,14 @@ static byte[] getData() {
8892
*/
8993
@DataProvider(name = "malformedWrite")
9094
public Object[][] getMalformedWrite() throws IOException {
91-
Path path = Files.createTempFile("malformedWrite", null);
95+
Path path = Files.createFile(Path.of("malformedWrite"));
9296
return new Object[][]{
9397
{path, "\ud800", null}, //the default Charset is UTF_8
9498
{path, "\u00A0\u00A1", US_ASCII},
9599
{path, "\ud800", UTF_8},
96100
{path, JA_STRING, ISO_8859_1},
97-
{path, "\u041e", Charset.forName("windows-1252")}, // cyrillic capital letter O
98-
{path, "\u091c", Charset.forName("windows-31j")}, // devanagari letter ja
101+
{path, "\u041e", WINDOWS_1252}, // cyrillic capital letter O
102+
{path, "\u091c", WINDOWS_31J}, // devanagari letter ja
99103
};
100104
}
101105

@@ -105,13 +109,26 @@ public Object[][] getMalformedWrite() throws IOException {
105109
*/
106110
@DataProvider(name = "illegalInput")
107111
public Object[][] getIllegalInput() throws IOException {
108-
Path path = Files.createTempFile("illegalInput", null);
112+
Path path = Files.createFile(Path.of("illegalInput"));
109113
return new Object[][]{
110114
{path, data, ISO_8859_1, null},
111115
{path, data, ISO_8859_1, UTF_8}
112116
};
113117
}
114118

119+
/*
120+
* DataProvider for illegal input bytes test
121+
*/
122+
@DataProvider(name = "illegalInputBytes")
123+
public Object[][] getIllegalInputBytes() throws IOException {
124+
return new Object[][]{
125+
{new byte[] {(byte)0x00, (byte)0x20, (byte)0x00}, UTF_16, MalformedInputException.class},
126+
{new byte[] {-50}, UTF_16, MalformedInputException.class},
127+
{new byte[] {(byte)0x81}, WINDOWS_1252, UnmappableCharacterException.class}, // unused in Cp1252
128+
{new byte[] {(byte)0x81, (byte)0xff}, WINDOWS_31J, UnmappableCharacterException.class}, // invalid trailing byte
129+
};
130+
}
131+
115132
/*
116133
* DataProvider for writeString test
117134
* Writes the data using both the existing and new method and compares the results.
@@ -143,16 +160,9 @@ public Object[][] getReadString() {
143160

144161
@BeforeClass
145162
void setup() throws IOException {
146-
testFiles[0] = Files.createTempFile("readWriteString", null);
147-
testFiles[1] = Files.createTempFile("writeString_file1", null);
148-
testFiles[2] = Files.createTempFile("writeString_file2", null);
149-
}
150-
151-
@AfterClass
152-
void cleanup() throws IOException {
153-
for (Path path : testFiles) {
154-
Files.deleteIfExists(path);
155-
}
163+
testFiles[0] = Files.createFile(Path.of("readWriteString"));
164+
testFiles[1] = Files.createFile(Path.of("writeString_file1"));
165+
testFiles[2] = Files.createFile(Path.of("writeString_file2"));
156166
}
157167

158168
/**
@@ -241,11 +251,10 @@ public void testReadString(Path path, String text, Charset cs, Charset cs2) thro
241251
*/
242252
@Test(dataProvider = "malformedWrite", expectedExceptions = UnmappableCharacterException.class)
243253
public void testMalformedWrite(Path path, String s, Charset cs) throws IOException {
244-
path.toFile().deleteOnExit();
245254
if (cs == null) {
246-
Files.writeString(path, s, CREATE);
255+
Files.writeString(path, s);
247256
} else {
248-
Files.writeString(path, s, cs, CREATE);
257+
Files.writeString(path, s, cs);
249258
}
250259
}
251260

@@ -261,16 +270,40 @@ public void testMalformedWrite(Path path, String s, Charset cs) throws IOExcepti
261270
*/
262271
@Test(dataProvider = "illegalInput", expectedExceptions = MalformedInputException.class)
263272
public void testMalformedRead(Path path, byte[] data, Charset csWrite, Charset csRead) throws IOException {
264-
path.toFile().deleteOnExit();
265273
String temp = new String(data, csWrite);
266-
Files.writeString(path, temp, csWrite, CREATE);
274+
Files.writeString(path, temp, csWrite);
267275
if (csRead == null) {
268276
Files.readString(path);
269277
} else {
270278
Files.readString(path, csRead);
271279
}
272280
}
273281

282+
/**
283+
* Verifies that IOException is thrown when reading a file containing
284+
* illegal bytes
285+
*
286+
* @param data the data used for the test
287+
* @param csRead the Charset to use for reading the file
288+
* @param expected exception class
289+
* @throws IOException when the Charset used for reading the file is incorrect
290+
*/
291+
@Test(dataProvider = "illegalInputBytes")
292+
public void testMalformedReadBytes(byte[] data, Charset csRead, Class<CharacterCodingException> expected)
293+
throws IOException {
294+
Path path = Path.of("illegalInputBytes");
295+
Files.write(path, data);
296+
try {
297+
Files.readString(path, csRead);
298+
} catch (MalformedInputException | UnmappableCharacterException e) {
299+
if (expected.isInstance(e)) {
300+
// success
301+
return;
302+
}
303+
}
304+
throw new RuntimeException("An instance of " + expected + " should be thrown");
305+
}
306+
274307
private void checkNullPointerException(Callable<?> c) {
275308
try {
276309
c.call();

0 commit comments

Comments
 (0)
Please sign in to comment.