8185426: Jshell crashing on autocompletion

Properly canceling completion on <tab> if needed.

Reviewed-by: rfield
This commit is contained in:
Jan Lahoda 2017-08-25 13:48:49 +02:00
parent 701dfdd390
commit 698882e467
3 changed files with 107 additions and 18 deletions

View File

@ -55,7 +55,6 @@ import jdk.internal.jline.TerminalSupport;
import jdk.internal.jline.WindowsTerminal;
import jdk.internal.jline.console.ConsoleReader;
import jdk.internal.jline.console.KeyMap;
import jdk.internal.jline.console.Operation;
import jdk.internal.jline.console.UserInterruptException;
import jdk.internal.jline.console.history.History;
import jdk.internal.jline.console.history.MemoryHistory;
@ -94,14 +93,14 @@ class ConsoleIOContext extends IOContext {
term = new JShellUnixTerminal(input);
}
term.init();
List<CompletionTask> completionTODO = new ArrayList<>();
CompletionState completionState = new CompletionState();
in = new ConsoleReader(cmdin, cmdout, term) {
@Override public KeyMap getKeys() {
return new CheckCompletionKeyMap(super.getKeys(), completionTODO);
return new CheckCompletionKeyMap(super.getKeys(), completionState);
}
@Override
protected boolean complete() throws IOException {
return ConsoleIOContext.this.complete(completionTODO);
return ConsoleIOContext.this.complete(completionState);
}
};
in.setExpandEvents(false);
@ -201,15 +200,19 @@ class ConsoleIOContext extends IOContext {
private static final String LINE_SEPARATORS2 = LINE_SEPARATOR + LINE_SEPARATOR;
@SuppressWarnings("fallthrough")
private boolean complete(List<CompletionTask> todo) {
private boolean complete(CompletionState completionState) {
//The completion has multiple states (invoked by subsequent presses of <tab>).
//On the first invocation in a given sequence, all steps are precomputed
//and placed into the todo list. The todo list is then followed on both the first
//and subsequent <tab> presses:
//and placed into the todo list (completionState.todo). The todo list is
//then followed on both the first and subsequent completion invocations:
try {
String text = in.getCursorBuffer().toString();
int cursor = in.getCursorBuffer().cursor;
if (todo.isEmpty()) {
List<CompletionTask> todo = completionState.todo;
if (todo.isEmpty() || completionState.actionCount != 1) {
ConsoleIOContextTestSupport.willComputeCompletion();
int[] anchor = new int[] {-1};
List<Suggestion> suggestions;
List<String> doc;
@ -237,6 +240,8 @@ class ConsoleIOContext extends IOContext {
CompletionTask ordinaryCompletion = new OrdinaryCompletionTask(suggestions, anchor[0], !command && !doc.isEmpty(), hasSmart);
CompletionTask allCompletion = new AllSuggestionsCompletionTask(suggestions, anchor[0]);
todo = new ArrayList<>();
//the main decission tree:
if (command) {
CompletionTask shortDocumentation = new CommandSynopsisTask(doc);
@ -310,6 +315,9 @@ class ConsoleIOContext extends IOContext {
}
}
completionState.actionCount = 0;
completionState.todo = todo;
if (repaint) {
in.redrawLine();
in.flush();
@ -1203,12 +1211,12 @@ class ConsoleIOContext extends IOContext {
private static final class CheckCompletionKeyMap extends KeyMap {
private final KeyMap del;
private final List<CompletionTask> completionTODO;
private final CompletionState completionState;
public CheckCompletionKeyMap(KeyMap del, List<CompletionTask> completionTODO) {
public CheckCompletionKeyMap(KeyMap del, CompletionState completionState) {
super(del.getName(), del.isViKeyMap());
this.del = del;
this.completionTODO = completionTODO;
this.completionState = completionState;
}
@Override
@ -1233,13 +1241,9 @@ class ConsoleIOContext extends IOContext {
@Override
public Object getBound(CharSequence keySeq) {
Object res = del.getBound(keySeq);
this.completionState.actionCount++;
if (res != Operation.COMPLETE) {
completionTODO.clear();
}
return res;
return del.getBound(keySeq);
}
@Override
@ -1252,4 +1256,12 @@ class ConsoleIOContext extends IOContext {
return "check: " + del.toString();
}
}
private static final class CompletionState {
/**The number of actions since the last completion invocation. Will be 1 when completion is
* invoked immediately after the last completion invocation.*/
public int actionCount;
/**Precomputed completion actions. Should only be reused if actionCount == 1.*/
public List<CompletionTask> todo = Collections.emptyList();
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2017, 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.internal.jshell.tool;
/**A support class to the ConsoleIOContext, containing callbacks called
* on important points in ConsoleIOContext.
*/
public abstract class ConsoleIOContextTestSupport {
public static ConsoleIOContextTestSupport IMPL;
public static void willComputeCompletion() {
if (IMPL != null)
IMPL.willComputeCompletionCallback();
}
protected abstract void willComputeCompletionCallback();
}

View File

@ -23,10 +23,11 @@
/**
* @test
* @bug 8177076
* @bug 8177076 8185426
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jshell/jdk.internal.jshell.tool
* jdk.jshell/jdk.internal.jshell.tool.resources:open
* jdk.jshell/jdk.jshell:open
* @library /tools/lib
@ -42,10 +43,12 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.regex.Pattern;
import jdk.internal.jshell.tool.ConsoleIOContextTestSupport;
import org.testng.annotations.Test;
@Test
@ -191,6 +194,39 @@ public class ToolTabSnippetTest extends UITesting {
});
}
public void testCleaningCompletionTODO() throws Exception {
doRunTest((inputSink, out) -> {
CountDownLatch testCompleteComputationStarted = new CountDownLatch(1);
CountDownLatch testCompleteComputationContinue = new CountDownLatch(1);
ConsoleIOContextTestSupport.IMPL = new ConsoleIOContextTestSupport() {
@Override
protected void willComputeCompletionCallback() {
if (testCompleteComputationStarted != null) {
testCompleteComputationStarted.countDown();
}
if (testCompleteComputationContinue != null) {
try {
testCompleteComputationContinue.await();
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
}
}
};
//-> <tab>
inputSink.write("\011");
testCompleteComputationStarted.await();
//-> <tab><tab>
inputSink.write("\011\011");
testCompleteComputationContinue.countDown();
waitOutput(out, "\u0005");
//-> <tab>
inputSink.write("\011");
waitOutput(out, "\u0005");
ConsoleIOContextTestSupport.IMPL = null;
});
}
private Path prepareZip() {
String clazz1 =
"package jshelltest;\n" +