Skip to content

Commit 9449fea

Browse files
committed
8358552: EndOfFileException in System.in.read() and IO.readln() etc. in JShell
Reviewed-by: jlahoda
1 parent 1fa772e commit 9449fea

File tree

6 files changed

+91
-32
lines changed

6 files changed

+91
-32
lines changed

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

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ public void resume() {
843843
public void beforeUserCode() {
844844
synchronized (this) {
845845
pendingBytes = null;
846-
pendingLine = null;
846+
pendingLineCharacters = null;
847847
}
848848
input.setState(State.BUFFER);
849849
}
@@ -977,26 +977,32 @@ public void perform(LineReaderImpl in) throws IOException {
977977
private static final Charset stdinCharset =
978978
Charset.forName(System.getProperty("stdin.encoding"),
979979
Charset.defaultCharset());
980-
private String pendingLine;
981-
private int pendingLinePointer;
980+
private char[] pendingLineCharacters;
981+
private int pendingLineCharactersPointer;
982982
private byte[] pendingBytes;
983983
private int pendingBytesPointer;
984984

985985
@Override
986986
public synchronized int readUserInput() throws IOException {
987987
if (pendingBytes == null || pendingBytes.length <= pendingBytesPointer) {
988-
char userChar = readUserInputChar();
988+
int userCharInput = readUserInputChar();
989+
if (userCharInput == (-1)) {
990+
return -1;
991+
}
992+
char userChar = (char) userCharInput;
989993
StringBuilder dataToConvert = new StringBuilder();
990994
dataToConvert.append(userChar);
991995
if (Character.isHighSurrogate(userChar)) {
992996
//surrogates cannot be converted independently,
993997
//read the low surrogate and append it to dataToConvert:
994-
char lowSurrogate = readUserInputChar();
995-
if (Character.isLowSurrogate(lowSurrogate)) {
996-
dataToConvert.append(lowSurrogate);
998+
int lowSurrogateInput = readUserInputChar();
999+
if (lowSurrogateInput == (-1)) {
1000+
//end of input, ignore at this stage
1001+
} else if (Character.isLowSurrogate((char) lowSurrogateInput)) {
1002+
dataToConvert.append((char) lowSurrogateInput);
9971003
} else {
9981004
//if not the low surrogate, rollback the reading of the character:
999-
pendingLinePointer--;
1005+
pendingLineCharactersPointer--;
10001006
}
10011007
}
10021008
pendingBytes = dataToConvert.toString().getBytes(stdinCharset);
@@ -1006,19 +1012,32 @@ public synchronized int readUserInput() throws IOException {
10061012
}
10071013

10081014
@Override
1009-
public synchronized char readUserInputChar() throws IOException {
1010-
while (pendingLine == null || pendingLine.length() <= pendingLinePointer) {
1011-
pendingLine = doReadUserLine("", null) + System.getProperty("line.separator");
1012-
pendingLinePointer = 0;
1015+
public synchronized int readUserInputChar() throws IOException {
1016+
if (pendingLineCharacters != null && pendingLineCharacters.length == 0) {
1017+
return -1;
1018+
}
1019+
while (pendingLineCharacters == null || pendingLineCharacters.length <= pendingLineCharactersPointer) {
1020+
String readLine = doReadUserLine("", null);
1021+
if (readLine == null) {
1022+
pendingLineCharacters = new char[0];
1023+
return -1;
1024+
} else {
1025+
pendingLineCharacters = (readLine + System.getProperty("line.separator")).toCharArray();
1026+
}
1027+
pendingLineCharactersPointer = 0;
10131028
}
1014-
return pendingLine.charAt(pendingLinePointer++);
1029+
return pendingLineCharacters[pendingLineCharactersPointer++];
10151030
}
10161031

10171032
@Override
10181033
public synchronized String readUserLine(String prompt) throws IOException {
10191034
//TODO: correct behavior w.r.t. pre-read stuff?
1020-
if (pendingLine != null && pendingLine.length() > pendingLinePointer) {
1021-
return pendingLine.substring(pendingLinePointer);
1035+
if (pendingLineCharacters != null && pendingLineCharacters.length > pendingLineCharactersPointer) {
1036+
String result = new String(pendingLineCharacters,
1037+
pendingLineCharactersPointer,
1038+
pendingLineCharacters.length - pendingLineCharactersPointer);
1039+
pendingLineCharacters = null;
1040+
return result;
10221041
}
10231042
return doReadUserLine(prompt, null);
10241043
}
@@ -1041,6 +1060,8 @@ private synchronized String doReadUserLine(String prompt, Character mask) throws
10411060
return in.readLine(prompt.replace("%", "%%"), mask);
10421061
} catch (UserInterruptException ex) {
10431062
throw new InterruptedIOException();
1063+
} catch (EndOfFileException ex) {
1064+
return null; // Signal that Ctrl+D or similar happened
10441065
} finally {
10451066
in.setParser(prevParser);
10461067
in.setHistory(prevHistory);
@@ -1051,7 +1072,11 @@ private synchronized String doReadUserLine(String prompt, Character mask) throws
10511072

10521073
public char[] readPassword(String prompt) throws IOException {
10531074
//TODO: correct behavior w.r.t. pre-read stuff?
1054-
return doReadUserLine(prompt, '\0').toCharArray();
1075+
String line = doReadUserLine(prompt, '\0');
1076+
if (line == null) {
1077+
return null;
1078+
}
1079+
return line.toCharArray();
10551080
}
10561081

10571082
@Override

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.io.IOException;
2929
import java.io.Writer;
3030
import java.nio.charset.Charset;
31-
import jdk.internal.org.jline.reader.UserInterruptException;
3231

3332
/**
3433
* Interface for defining user interaction with the shell.
@@ -59,18 +58,18 @@ abstract class IOContext implements AutoCloseable {
5958

6059
public abstract int readUserInput() throws IOException;
6160

62-
public char readUserInputChar() throws IOException {
63-
throw new UserInterruptException("");
61+
public int readUserInputChar() throws IOException {
62+
return -1;
6463
}
6564

6665
public String readUserLine(String prompt) throws IOException {
6766
userOutput().write(prompt);
6867
userOutput().flush();
69-
throw new UserInterruptException("");
68+
return null;
7069
}
7170

7271
public String readUserLine() throws IOException {
73-
throw new UserInterruptException("");
72+
return null;
7473
}
7574

7675
public Writer userOutput() {
@@ -80,7 +79,7 @@ public Writer userOutput() {
8079
public char[] readPassword(String prompt) throws IOException {
8180
userOutput().write(prompt);
8281
userOutput().flush();
83-
throw new UserInterruptException("");
82+
return null;
8483
}
8584

8685
public void setIndent(int indent) {}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4121,7 +4121,11 @@ public synchronized Reader reader() {
41214121
public int read(char[] cbuf, int off, int len) throws IOException {
41224122
if (len == 0) return 0;
41234123
try {
4124-
cbuf[off] = input.readUserInputChar();
4124+
int r = input.readUserInputChar();
4125+
if (r == (-1)) {
4126+
return -1;
4127+
}
4128+
cbuf[off] = (char) r;
41254129
return 1;
41264130
} catch (UserInterruptException ex) {
41274131
return -1;

src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,18 @@ public synchronized Reader reader() {
182182
reader = new Reader() {
183183
@Override
184184
public int read(char[] cbuf, int off, int len) throws IOException {
185+
if (len == 0) {
186+
return 0;
187+
}
185188
return sendAndReceive(() -> {
186189
remoteInput.write(Task.READ_CHARS.ordinal());
187-
return readChars(cbuf, off, len);
190+
int r = readInt();
191+
if (r == (-1)) {
192+
return -1;
193+
} else {
194+
cbuf[off] = (char) r;
195+
return 1;
196+
}
188197
});
189198
}
190199

@@ -374,13 +383,9 @@ public synchronized void write(int b) throws IOException {
374383
bp = 0;
375384
}
376385
case READ_CHARS -> {
377-
if (bp >= 5) {
378-
int len = readInt(1);
379-
int c = console.reader().read();
380-
//XXX: EOF handling!
381-
sendChars(sinkOutput, new char[] {(char) c}, 0, 1);
382-
bp = 0;
383-
}
386+
int c = console.reader().read();
387+
sendInt(sinkOutput, c);
388+
bp = 0;
384389
}
385390
case READ_LINE -> {
386391
char[] data = readCharsOrNull(1);

test/langtools/jdk/jshell/InputUITest.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
/*
2525
* @test
26-
* @bug 8356165
26+
* @bug 8356165 8358552
2727
* @summary Check user input works properly
2828
* @modules
2929
* jdk.compiler/com.sun.tools.javac.api
@@ -38,6 +38,7 @@
3838
* @run testng/othervm -Dstderr.encoding=UTF-8 -Dstdin.encoding=UTF-8 -Dstdout.encoding=UTF-8 InputUITest
3939
*/
4040

41+
import java.util.Map;
4142
import java.util.function.Function;
4243
import org.testng.annotations.Test;
4344

@@ -67,4 +68,21 @@ public void testUserInputWithSurrogates() throws Exception {
6768
}, false);
6869
}
6970

71+
public void testCloseInputSinkWhileReadingUserInputSimulatingCtrlD() throws Exception {
72+
var snippets = Map.of(
73+
"System.in.read()", " ==> -1",
74+
"System.console().reader().read()", " ==> -1",
75+
"System.console().readLine()", " ==> null",
76+
"System.console().readPassword()", " ==> null",
77+
"IO.readln()", " ==> null",
78+
"System.in.readAllBytes()", " ==> byte[0] { }"
79+
);
80+
for (var snippet : snippets.entrySet()) {
81+
doRunTest((inputSink, out) -> {
82+
inputSink.write(snippet.getKey() + "\n");
83+
inputSink.close(); // Does not work: inputSink.write("\u0004"); // CTRL + D
84+
waitOutput(out, patternQuote(snippet.getValue()), patternQuote("EndOfFileException"));
85+
}, false);
86+
}
87+
}
7088
}

test/langtools/jdk/jshell/UITesting.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,19 @@ protected void doRunTest(Test test, boolean setUserInput) throws Exception {
106106
});
107107

108108
Writer inputSink = new OutputStreamWriter(input.createOutput(), StandardCharsets.UTF_8) {
109+
boolean closed = false;
109110
@Override
110111
public void write(String str) throws IOException {
112+
if (closed) return; // prevents exception thrown due to closed writer
111113
super.write(str);
112114
flush();
113115
}
116+
117+
@Override
118+
public void close() throws IOException {
119+
super.close();
120+
closed = true;
121+
}
114122
};
115123

116124
runner.start();

0 commit comments

Comments
 (0)