8362116: System.in.read() etc. don't accept input once immediate Ctrl+D pressed in JShell

Reviewed-by: liach, cstein
This commit is contained in:
Jan Lahoda 2025-07-15 06:13:45 +00:00
parent 25e509b0db
commit 40d159d4a9
6 changed files with 84 additions and 6 deletions

View File

@ -1431,6 +1431,14 @@ class ConsoleIOContext extends IOContext {
private TestTerminal(InputStream input, OutputStream output, Size size) throws Exception {
super(input, output, "ansi", size, size);
}
@Override
public Attributes enterRawMode() {
Attributes res = super.enterRawMode();
res.setControlChar(ControlChar.VEOF, 4);
return res;
}
}
private static final class CompletionState {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, 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
@ -56,6 +56,7 @@ import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import jdk.jshell.spi.ExecutionControlProvider;
import jdk.jshell.spi.ExecutionEnv;
import static jdk.jshell.Util.expunge;
import jdk.jshell.execution.impl.RestartableInputStream;
/**
* The JShell evaluation state engine. This is the central class in the JShell
@ -115,7 +116,7 @@ public class JShell implements AutoCloseable {
private static ResourceBundle outputRB = null;
JShell(Builder b) throws IllegalStateException {
this.in = b.in;
this.in = new RestartableInputStream(b.in);
this.out = b.out;
this.err = b.err;
this.console = Optional.ofNullable(b.console);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, 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
@ -47,6 +47,7 @@ import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import static java.nio.charset.StandardCharsets.UTF_8;
import jdk.jshell.execution.impl.RestartableInputStream;
/**
* Miscellaneous utility methods for setting-up implementations of
@ -62,6 +63,7 @@ public class Util {
private static final int TAG_DATA = 0;
private static final int TAG_CLOSED = 1;
private static final int TAG_EXCEPTION = 2;
private static final int TAG_EOF = 3;
// never instantiated
private Util() {}
@ -122,6 +124,7 @@ public class Util {
switch (tag) {
case TAG_DATA: return super.read();
case TAG_CLOSED: close(); return -1;
case TAG_EOF: return -1;
case TAG_EXCEPTION:
int len = (super.read() << 0) + (super.read() << 8) + (super.read() << 16) + (super.read() << 24);
byte[] message = new byte[len];
@ -183,7 +186,11 @@ public class Util {
try {
int r = in.read();
if (r == (-1)) {
inTarget.write(TAG_CLOSED);
if (in instanceof RestartableInputStream ris && !ris.isClosed()) {
inTarget.write(TAG_EOF);
} else {
inTarget.write(TAG_CLOSED);
}
} else {
inTarget.write(new byte[] {TAG_DATA, (byte) r});
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2025, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.jshell.execution.impl;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
public class RestartableInputStream extends FilterInputStream {
private final AtomicBoolean closed = new AtomicBoolean();
public RestartableInputStream(InputStream delegate) {
super(delegate);
}
@Override
public void close() throws IOException {
closed.set(true);
super.close();
}
public boolean isClosed() {
return closed.get();
}
}

View File

@ -79,10 +79,22 @@ public class InputUITest extends UITesting {
);
for (var snippet : snippets.entrySet()) {
doRunTest((inputSink, out) -> {
inputSink.write(snippet.getKey() + "\n");
inputSink.close(); // Does not work: inputSink.write("\u0004"); // CTRL + D
inputSink.write(snippet.getKey() + "\n" + CTRL_D);
waitOutput(out, patternQuote(snippet.getValue()), patternQuote("EndOfFileException"));
}, false);
}
}
public void testUserInputWithCtrlDAndMultipleSnippets() throws Exception {
doRunTest((inputSink, out) -> {
inputSink.write("IO.readln()\n" + CTRL_D);
waitOutput(out, patternQuote("==> null"));
inputSink.write("IO.readln()\nAB\n");
waitOutput(out, patternQuote("==> \"AB\""));
inputSink.write("System.in.read()\n" + CTRL_D);
waitOutput(out, patternQuote("==> -1"));
inputSink.write("System.in.read()\nA\n");
waitOutput(out, patternQuote("==> 65"));
}, false);
}
}

View File

@ -52,6 +52,7 @@ public class UITesting {
protected static final String REDRAW_PROMPT = "\n\r?" + PROMPT;
protected static final String UP = "\033[A";
protected static final String DOWN = "\033[B";
protected static final String CTRL_D = "\u0004";
private final boolean laxLineEndings;
public UITesting() {