mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8358552: EndOfFileException in System.in.read() and IO.readln() etc. in JShell
Reviewed-by: jlahoda
This commit is contained in:
parent
1fa772e814
commit
9449fea2cd
@ -843,7 +843,7 @@ class ConsoleIOContext extends IOContext {
|
||||
public void beforeUserCode() {
|
||||
synchronized (this) {
|
||||
pendingBytes = null;
|
||||
pendingLine = null;
|
||||
pendingLineCharacters = null;
|
||||
}
|
||||
input.setState(State.BUFFER);
|
||||
}
|
||||
@ -977,26 +977,32 @@ class ConsoleIOContext extends IOContext {
|
||||
private static final Charset stdinCharset =
|
||||
Charset.forName(System.getProperty("stdin.encoding"),
|
||||
Charset.defaultCharset());
|
||||
private String pendingLine;
|
||||
private int pendingLinePointer;
|
||||
private char[] pendingLineCharacters;
|
||||
private int pendingLineCharactersPointer;
|
||||
private byte[] pendingBytes;
|
||||
private int pendingBytesPointer;
|
||||
|
||||
@Override
|
||||
public synchronized int readUserInput() throws IOException {
|
||||
if (pendingBytes == null || pendingBytes.length <= pendingBytesPointer) {
|
||||
char userChar = readUserInputChar();
|
||||
int userCharInput = readUserInputChar();
|
||||
if (userCharInput == (-1)) {
|
||||
return -1;
|
||||
}
|
||||
char userChar = (char) userCharInput;
|
||||
StringBuilder dataToConvert = new StringBuilder();
|
||||
dataToConvert.append(userChar);
|
||||
if (Character.isHighSurrogate(userChar)) {
|
||||
//surrogates cannot be converted independently,
|
||||
//read the low surrogate and append it to dataToConvert:
|
||||
char lowSurrogate = readUserInputChar();
|
||||
if (Character.isLowSurrogate(lowSurrogate)) {
|
||||
dataToConvert.append(lowSurrogate);
|
||||
int lowSurrogateInput = readUserInputChar();
|
||||
if (lowSurrogateInput == (-1)) {
|
||||
//end of input, ignore at this stage
|
||||
} else if (Character.isLowSurrogate((char) lowSurrogateInput)) {
|
||||
dataToConvert.append((char) lowSurrogateInput);
|
||||
} else {
|
||||
//if not the low surrogate, rollback the reading of the character:
|
||||
pendingLinePointer--;
|
||||
pendingLineCharactersPointer--;
|
||||
}
|
||||
}
|
||||
pendingBytes = dataToConvert.toString().getBytes(stdinCharset);
|
||||
@ -1006,19 +1012,32 @@ class ConsoleIOContext extends IOContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized char readUserInputChar() throws IOException {
|
||||
while (pendingLine == null || pendingLine.length() <= pendingLinePointer) {
|
||||
pendingLine = doReadUserLine("", null) + System.getProperty("line.separator");
|
||||
pendingLinePointer = 0;
|
||||
public synchronized int readUserInputChar() throws IOException {
|
||||
if (pendingLineCharacters != null && pendingLineCharacters.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
return pendingLine.charAt(pendingLinePointer++);
|
||||
while (pendingLineCharacters == null || pendingLineCharacters.length <= pendingLineCharactersPointer) {
|
||||
String readLine = doReadUserLine("", null);
|
||||
if (readLine == null) {
|
||||
pendingLineCharacters = new char[0];
|
||||
return -1;
|
||||
} else {
|
||||
pendingLineCharacters = (readLine + System.getProperty("line.separator")).toCharArray();
|
||||
}
|
||||
pendingLineCharactersPointer = 0;
|
||||
}
|
||||
return pendingLineCharacters[pendingLineCharactersPointer++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String readUserLine(String prompt) throws IOException {
|
||||
//TODO: correct behavior w.r.t. pre-read stuff?
|
||||
if (pendingLine != null && pendingLine.length() > pendingLinePointer) {
|
||||
return pendingLine.substring(pendingLinePointer);
|
||||
if (pendingLineCharacters != null && pendingLineCharacters.length > pendingLineCharactersPointer) {
|
||||
String result = new String(pendingLineCharacters,
|
||||
pendingLineCharactersPointer,
|
||||
pendingLineCharacters.length - pendingLineCharactersPointer);
|
||||
pendingLineCharacters = null;
|
||||
return result;
|
||||
}
|
||||
return doReadUserLine(prompt, null);
|
||||
}
|
||||
@ -1041,6 +1060,8 @@ class ConsoleIOContext extends IOContext {
|
||||
return in.readLine(prompt.replace("%", "%%"), mask);
|
||||
} catch (UserInterruptException ex) {
|
||||
throw new InterruptedIOException();
|
||||
} catch (EndOfFileException ex) {
|
||||
return null; // Signal that Ctrl+D or similar happened
|
||||
} finally {
|
||||
in.setParser(prevParser);
|
||||
in.setHistory(prevHistory);
|
||||
@ -1051,7 +1072,11 @@ class ConsoleIOContext extends IOContext {
|
||||
|
||||
public char[] readPassword(String prompt) throws IOException {
|
||||
//TODO: correct behavior w.r.t. pre-read stuff?
|
||||
return doReadUserLine(prompt, '\0').toCharArray();
|
||||
String line = doReadUserLine(prompt, '\0');
|
||||
if (line == null) {
|
||||
return null;
|
||||
}
|
||||
return line.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -28,7 +28,6 @@ package jdk.internal.jshell.tool;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import jdk.internal.org.jline.reader.UserInterruptException;
|
||||
|
||||
/**
|
||||
* Interface for defining user interaction with the shell.
|
||||
@ -59,18 +58,18 @@ abstract class IOContext implements AutoCloseable {
|
||||
|
||||
public abstract int readUserInput() throws IOException;
|
||||
|
||||
public char readUserInputChar() throws IOException {
|
||||
throw new UserInterruptException("");
|
||||
public int readUserInputChar() throws IOException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String readUserLine(String prompt) throws IOException {
|
||||
userOutput().write(prompt);
|
||||
userOutput().flush();
|
||||
throw new UserInterruptException("");
|
||||
return null;
|
||||
}
|
||||
|
||||
public String readUserLine() throws IOException {
|
||||
throw new UserInterruptException("");
|
||||
return null;
|
||||
}
|
||||
|
||||
public Writer userOutput() {
|
||||
@ -80,7 +79,7 @@ abstract class IOContext implements AutoCloseable {
|
||||
public char[] readPassword(String prompt) throws IOException {
|
||||
userOutput().write(prompt);
|
||||
userOutput().flush();
|
||||
throw new UserInterruptException("");
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setIndent(int indent) {}
|
||||
|
||||
@ -4121,7 +4121,11 @@ public class JShellTool implements MessageHandler {
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
if (len == 0) return 0;
|
||||
try {
|
||||
cbuf[off] = input.readUserInputChar();
|
||||
int r = input.readUserInputChar();
|
||||
if (r == (-1)) {
|
||||
return -1;
|
||||
}
|
||||
cbuf[off] = (char) r;
|
||||
return 1;
|
||||
} catch (UserInterruptException ex) {
|
||||
return -1;
|
||||
|
||||
@ -182,9 +182,18 @@ public class ConsoleImpl {
|
||||
reader = new Reader() {
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
return sendAndReceive(() -> {
|
||||
remoteInput.write(Task.READ_CHARS.ordinal());
|
||||
return readChars(cbuf, off, len);
|
||||
int r = readInt();
|
||||
if (r == (-1)) {
|
||||
return -1;
|
||||
} else {
|
||||
cbuf[off] = (char) r;
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -374,13 +383,9 @@ public class ConsoleImpl {
|
||||
bp = 0;
|
||||
}
|
||||
case READ_CHARS -> {
|
||||
if (bp >= 5) {
|
||||
int len = readInt(1);
|
||||
int c = console.reader().read();
|
||||
//XXX: EOF handling!
|
||||
sendChars(sinkOutput, new char[] {(char) c}, 0, 1);
|
||||
bp = 0;
|
||||
}
|
||||
int c = console.reader().read();
|
||||
sendInt(sinkOutput, c);
|
||||
bp = 0;
|
||||
}
|
||||
case READ_LINE -> {
|
||||
char[] data = readCharsOrNull(1);
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8356165
|
||||
* @bug 8356165 8358552
|
||||
* @summary Check user input works properly
|
||||
* @modules
|
||||
* jdk.compiler/com.sun.tools.javac.api
|
||||
@ -38,6 +38,7 @@
|
||||
* @run testng/othervm -Dstderr.encoding=UTF-8 -Dstdin.encoding=UTF-8 -Dstdout.encoding=UTF-8 InputUITest
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@ -67,4 +68,21 @@ public class InputUITest extends UITesting {
|
||||
}, false);
|
||||
}
|
||||
|
||||
public void testCloseInputSinkWhileReadingUserInputSimulatingCtrlD() throws Exception {
|
||||
var snippets = Map.of(
|
||||
"System.in.read()", " ==> -1",
|
||||
"System.console().reader().read()", " ==> -1",
|
||||
"System.console().readLine()", " ==> null",
|
||||
"System.console().readPassword()", " ==> null",
|
||||
"IO.readln()", " ==> null",
|
||||
"System.in.readAllBytes()", " ==> byte[0] { }"
|
||||
);
|
||||
for (var snippet : snippets.entrySet()) {
|
||||
doRunTest((inputSink, out) -> {
|
||||
inputSink.write(snippet.getKey() + "\n");
|
||||
inputSink.close(); // Does not work: inputSink.write("\u0004"); // CTRL + D
|
||||
waitOutput(out, patternQuote(snippet.getValue()), patternQuote("EndOfFileException"));
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,11 +106,19 @@ public class UITesting {
|
||||
});
|
||||
|
||||
Writer inputSink = new OutputStreamWriter(input.createOutput(), StandardCharsets.UTF_8) {
|
||||
boolean closed = false;
|
||||
@Override
|
||||
public void write(String str) throws IOException {
|
||||
if (closed) return; // prevents exception thrown due to closed writer
|
||||
super.write(str);
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
closed = true;
|
||||
}
|
||||
};
|
||||
|
||||
runner.start();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user