Skip to content

Commit e961b13

Browse files
committed
8356165: System.in in jshell replace supplementary characters with ??
Reviewed-by: cstein, asotona
1 parent f8d7f66 commit e961b13

3 files changed

Lines changed: 92 additions & 4 deletions

File tree

src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -977,7 +977,20 @@ public void perform(LineReaderImpl in) throws IOException {
977977
public synchronized int readUserInput() throws IOException {
978978
if (pendingBytes == null || pendingBytes.length <= pendingBytesPointer) {
979979
char userChar = readUserInputChar();
980-
pendingBytes = String.valueOf(userChar).getBytes();
980+
StringBuilder dataToConvert = new StringBuilder();
981+
dataToConvert.append(userChar);
982+
if (Character.isHighSurrogate(userChar)) {
983+
//surrogates cannot be converted independently,
984+
//read the low surrogate and append it to dataToConvert:
985+
char lowSurrogate = readUserInputChar();
986+
if (Character.isLowSurrogate(lowSurrogate)) {
987+
dataToConvert.append(lowSurrogate);
988+
} else {
989+
//if not the low surrogate, rollback the reading of the character:
990+
pendingLinePointer--;
991+
}
992+
}
993+
pendingBytes = dataToConvert.toString().getBytes();
981994
pendingBytesPointer = 0;
982995
}
983996
return pendingBytes[pendingBytesPointer++];
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2025, 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 8356165
27+
* @summary Check user input works properly
28+
* @modules
29+
* jdk.compiler/com.sun.tools.javac.api
30+
* jdk.compiler/com.sun.tools.javac.main
31+
* jdk.jshell/jdk.internal.jshell.tool:open
32+
* jdk.jshell/jdk.internal.jshell.tool.resources:open
33+
* jdk.jshell/jdk.jshell:open
34+
* @library /tools/lib
35+
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
36+
* @build Compiler UITesting
37+
* @compile InputUITest.java
38+
* @run testng InputUITest
39+
*/
40+
41+
import java.util.function.Function;
42+
import org.testng.annotations.Test;
43+
44+
@Test
45+
public class InputUITest extends UITesting {
46+
47+
static final String LINE_SEPARATOR = System.getProperty("line.separator");
48+
static final String LINE_SEPARATOR_ESCAPED = LINE_SEPARATOR.replace("\n", "\\n")
49+
.replace("\r", "\\r");
50+
51+
public InputUITest() {
52+
super(true);
53+
}
54+
55+
public void testUserInputWithSurrogates() throws Exception {
56+
Function<Integer, String> genSnippet =
57+
realCharsToRead -> "new String(System.in.readNBytes(" +
58+
(realCharsToRead + LINE_SEPARATOR.length()) +
59+
"))\n";
60+
doRunTest((inputSink, out) -> {
61+
inputSink.write(genSnippet.apply(4) + "\uD83D\uDE03\n");
62+
waitOutput(out, patternQuote("\"\uD83D\uDE03" + LINE_SEPARATOR_ESCAPED + "\""));
63+
inputSink.write(genSnippet.apply(1) + "\uD83D\n");
64+
waitOutput(out, patternQuote("\"?" + LINE_SEPARATOR_ESCAPED + "\""));
65+
inputSink.write(genSnippet.apply(1) + "\uDE03\n");
66+
waitOutput(out, patternQuote("\"?" + LINE_SEPARATOR_ESCAPED + "\""));
67+
}, false);
68+
}
69+
70+
}

test/langtools/jdk/jshell/UITesting.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -21,6 +21,7 @@
2121
* questions.
2222
*/
2323

24+
import java.io.ByteArrayOutputStream;
2425
import java.io.IOException;
2526
import java.io.InputStream;
2627
import java.io.OutputStream;
@@ -62,6 +63,10 @@ public UITesting(boolean laxLineEndings) {
6263
}
6364

6465
protected void doRunTest(Test test) throws Exception {
66+
doRunTest(test, true);
67+
}
68+
69+
protected void doRunTest(Test test, boolean setUserInput) throws Exception {
6570
// turn on logging of launch failures
6671
Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL);
6772

@@ -87,7 +92,7 @@ protected void doRunTest(Test test) throws Exception {
8792
Thread runner = new Thread(() -> {
8893
try {
8994
JavaShellToolBuilder.builder()
90-
.in(input, input)
95+
.in(input, setUserInput ? input : null)
9196
.out(outS)
9297
.err(outS)
9398
.promptCapture(true)

0 commit comments

Comments
 (0)