8165405: jshell tool: /classpath is inconsistent

8172103: JShell: crash in TaskFactory$WrapSourceHandler.diag

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2017-01-06 10:31:25 -08:00
parent 4ec30a933a
commit 6f796f5684
7 changed files with 607 additions and 267 deletions
langtools

@ -47,7 +47,9 @@ import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -185,10 +187,7 @@ public class JShellTool implements MessageHandler {
private IOContext input = null;
private boolean regenerateOnDeath = true;
private boolean live = false;
private boolean feedbackInitialized = false;
private String commandLineFeedbackMode = null;
private List<String> remoteVMOptions = new ArrayList<>();
private List<String> compilerOptions = new ArrayList<>();
private Options options;
SourceCodeAnalysis analysis;
JShell state = null;
@ -198,7 +197,6 @@ public class JShellTool implements MessageHandler {
private boolean debug = false;
public boolean testPrompt = false;
private String cmdlineClasspath = null;
private String defaultStartup = null;
private String startup = null;
private String executionControlSpec = null;
@ -221,6 +219,16 @@ public class JShellTool implements MessageHandler {
static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+");
static final String BUILTIN_FILE_PATH_FORMAT = "jrt:/jdk.jshell/jdk/jshell/tool/resources/%s.jsh";
// match anything followed by whitespace
private static final Pattern OPTION_PRE_PATTERN =
Pattern.compile("\\s*(\\S+\\s+)*?");
// match a (possibly incomplete) option flag with optional double-dash and/or internal dashes
private static final Pattern OPTION_PATTERN =
Pattern.compile(OPTION_PRE_PATTERN.pattern() + "(?<dd>-??)(?<flag>-([a-z][a-z\\-]*)?)");
// match an option flag and a (possibly missing or incomplete) value
private static final Pattern OPTION_VALUE_PATTERN =
Pattern.compile(OPTION_PATTERN.pattern() + "\\s+(?<val>\\S*)");
// Tool id (tid) mapping: the three name spaces
NameSpace mainNamespace;
NameSpace startNamespace;
@ -229,7 +237,278 @@ public class JShellTool implements MessageHandler {
// Tool id (tid) mapping: the current name spaces
NameSpace currentNameSpace;
Map<Snippet,SnippetInfo> mapSnippet;
Map<Snippet, SnippetInfo> mapSnippet;
// Kinds of compiler/runtime init options
private enum OptionKind {
CLASS_PATH("--class-path", true),
MODULE_PATH("--module-path", true),
ADD_MODULES("--add-modules", false),
ADD_EXPORTS("--add-exports", false),
TO_COMPILER("-C", false, false, true, false),
TO_REMOTE_VM("-R", false, false, false, true),;
final String optionFlag;
final boolean onlyOne;
final boolean passFlag;
final boolean toCompiler;
final boolean toRemoteVm;
private OptionKind(String optionFlag, boolean onlyOne) {
this(optionFlag, onlyOne, true, true, true);
}
private OptionKind(String optionFlag, boolean onlyOne, boolean passFlag,
boolean toCompiler, boolean toRemoteVm) {
this.optionFlag = optionFlag;
this.onlyOne = onlyOne;
this.passFlag = passFlag;
this.toCompiler = toCompiler;
this.toRemoteVm = toRemoteVm;
}
}
// compiler/runtime init option values
private static class Options {
private Map<OptionKind, List<String>> optMap = new HashMap<>();
private String[] selectOptions(Predicate<Entry<OptionKind, List<String>>> pred) {
return optMap.entrySet().stream()
.filter(pred)
.flatMap(e -> e.getValue().stream())
.toArray(String[]::new);
}
String[] remoteVmOptions() {
return selectOptions(e -> e.getKey().toRemoteVm);
}
String[] compilerOptions() {
return selectOptions(e -> e.getKey().toCompiler);
}
String[] commonOptions() {
return selectOptions(e -> e.getKey().passFlag);
}
void addAll(OptionKind kind, Collection<String> vals) {
optMap.computeIfAbsent(kind, k -> new ArrayList<>())
.addAll(vals);
}
void override(Options newer) {
newer.optMap.entrySet().stream()
.forEach(e -> {
if (e.getKey().onlyOne) {
// Only one allowed, override last
optMap.put(e.getKey(), e.getValue());
} else {
// Additive
addAll(e.getKey(), e.getValue());
}
});
}
}
// base option parsing of /env, /reload, and /reset and command-line options
private class OptionParserBase {
final OptionParser parser = new OptionParser();
private final OptionSpec<String> argClassPath = parser.accepts("class-path").withRequiredArg();
private final OptionSpec<String> argModulePath = parser.accepts("module-path").withRequiredArg();
private final OptionSpec<String> argAddModules = parser.accepts("add-modules").withRequiredArg();
private final OptionSpec<String> argAddExports = parser.accepts("add-exports").withRequiredArg();
private final NonOptionArgumentSpec<String> argNonOptions = parser.nonOptions();
private Options opts = new Options();
private List<String> nonOptions;
private boolean failed = false;
List<String> nonOptions() {
return nonOptions;
}
void msg(String key, Object... args) {
errormsg(key, args);
}
Options parse(String[] args) throws OptionException {
try {
OptionSet oset = parser.parse(args);
nonOptions = oset.valuesOf(argNonOptions);
return parse(oset);
} catch (OptionException ex) {
if (ex.options().isEmpty()) {
msg("jshell.err.opt.invalid", stream(args).collect(joining(", ")));
} else {
boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next());
msg(isKnown
? "jshell.err.opt.arg"
: "jshell.err.opt.unknown",
ex.options()
.stream()
.collect(joining(", ")));
}
return null;
}
}
Options parse(OptionSet options) {
addOptions(OptionKind.CLASS_PATH, options.valuesOf(argClassPath));
addOptions(OptionKind.MODULE_PATH, options.valuesOf(argModulePath));
addOptions(OptionKind.ADD_MODULES, options.valuesOf(argAddModules));
addOptions(OptionKind.ADD_EXPORTS, options.valuesOf(argAddExports).stream()
.map(mp -> mp.contains("=") ? mp : mp + "=ALL-UNNAMED")
.collect(toList())
);
return failed ? null : opts;
}
void addOptions(OptionKind kind, Collection<String> vals) {
if (!vals.isEmpty()) {
if (kind.onlyOne && vals.size() > 1) {
msg("jshell.err.opt.one", kind.optionFlag);
failed = true;
return;
}
if (kind.passFlag) {
vals = vals.stream()
.flatMap(mp -> Stream.of(kind.optionFlag, mp))
.collect(toList());
}
opts.addAll(kind, vals);
}
}
}
// option parsing for /reload (adds -restore -quiet)
private class OptionParserReload extends OptionParserBase {
private final OptionSpecBuilder argRestore = parser.accepts("restore");
private final OptionSpecBuilder argQuiet = parser.accepts("quiet");
private boolean restore = false;
private boolean quiet = false;
boolean restore() {
return restore;
}
boolean quiet() {
return quiet;
}
@Override
Options parse(OptionSet options) {
if (options.has(argRestore)) {
restore = true;
}
if (options.has(argQuiet)) {
quiet = true;
}
return super.parse(options);
}
}
// option parsing for command-line
private class OptionParserCommandLine extends OptionParserBase {
private final OptionSpec<String> argStart = parser.accepts("startup").withRequiredArg();
private final OptionSpecBuilder argNoStart = parser.acceptsAll(asList("n", "no-startup"));
private final OptionSpec<String> argFeedback = parser.accepts("feedback").withRequiredArg();
private final OptionSpec<String> argExecution = parser.accepts("execution").withRequiredArg();
private final OptionSpecBuilder argQ = parser.accepts("q");
private final OptionSpecBuilder argS = parser.accepts("s");
private final OptionSpecBuilder argV = parser.accepts("v");
private final OptionSpec<String> argR = parser.accepts("R").withRequiredArg();
private final OptionSpec<String> argC = parser.accepts("C").withRequiredArg();
private final OptionSpecBuilder argHelp = parser.acceptsAll(asList("h", "help"));
private final OptionSpecBuilder argVersion = parser.accepts("version");
private final OptionSpecBuilder argFullVersion = parser.accepts("full-version");
private final OptionSpecBuilder argX = parser.accepts("X");
private String feedbackMode = null;
private String initialStartup = null;
String feedbackMode() {
return feedbackMode;
}
String startup() {
return initialStartup;
}
@Override
void msg(String key, Object... args) {
startmsg(key, args);
}
@Override
Options parse(OptionSet options) {
if (options.has(argHelp)) {
printUsage();
return null;
}
if (options.has(argX)) {
printUsageX();
return null;
}
if (options.has(argVersion)) {
cmdout.printf("jshell %s\n", version());
return null;
}
if (options.has(argFullVersion)) {
cmdout.printf("jshell %s\n", fullVersion());
return null;
}
if ((options.valuesOf(argFeedback).size() +
(options.has(argQ) ? 1 : 0) +
(options.has(argS) ? 1 : 0) +
(options.has(argV) ? 1 : 0)) > 1) {
msg("jshell.err.opt.feedback.one");
return null;
} else if (options.has(argFeedback)) {
feedbackMode = options.valueOf(argFeedback);
} else if (options.has("q")) {
feedbackMode = "concise";
} else if (options.has("s")) {
feedbackMode = "silent";
} else if (options.has("v")) {
feedbackMode = "verbose";
}
if (options.has(argStart)) {
List<String> sts = options.valuesOf(argStart);
if (options.has("no-startup")) {
startmsg("jshell.err.opt.startup.conflict");
return null;
}
StringBuilder sb = new StringBuilder();
for (String fn : sts) {
String s = readFile(fn, "--startup");
if (s == null) {
return null;
}
sb.append(s);
}
initialStartup = sb.toString();
} else if (options.has(argNoStart)) {
initialStartup = "";
} else {
initialStartup = prefs.get(STARTUP_KEY);
if (initialStartup == null) {
initialStartup = defaultStartup();
}
}
if (options.has(argExecution)) {
executionControlSpec = options.valueOf(argExecution);
}
addOptions(OptionKind.TO_REMOTE_VM, options.valuesOf(argR));
addOptions(OptionKind.TO_COMPILER, options.valuesOf(argC));
return super.parse(options);
}
}
/**
* Is the input/output currently interactive
@ -464,43 +743,45 @@ public class JShellTool implements MessageHandler {
}
public void start(String[] args) throws Exception {
List<String> loadList = processCommandArgs(args);
if (loadList == null) {
OptionParserCommandLine commandLineArgs = new OptionParserCommandLine();
options = commandLineArgs.parse(args);
if (options == null) {
// Abort
return;
}
try (IOContext in = new ConsoleIOContext(this, cmdin, console)) {
start(in, loadList);
}
}
private void start(IOContext in, List<String> loadList) {
// If startup hasn't been set by command line, set from retained/default
if (startup == null) {
startup = prefs.get(STARTUP_KEY);
if (startup == null) {
startup = defaultStartup();
}
}
startup = commandLineArgs.startup();
// initialize editor settings
configEditor();
resetState(); // Initialize
// initialize JShell instance
resetState();
// Read replay history from last jshell session into previous history
String prevReplay = prefs.get(REPLAY_RESTORE_KEY);
if (prevReplay != null) {
replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR));
}
for (String loadFile : loadList) {
// load snippet/command files given on command-line
for (String loadFile : commandLineArgs.nonOptions()) {
runFile(loadFile, "jshell");
}
if (regenerateOnDeath && feedback.shouldDisplayCommandFluff()) {
hardmsg("jshell.msg.welcome", version());
// if we survived that...
if (regenerateOnDeath) {
// initialize the predefined feedback modes
initFeedback(commandLineArgs.feedbackMode());
}
// check again, as feedback setting could have failed
if (regenerateOnDeath) {
// if we haven't died, and the feedback mode wants fluff, print welcome
if (feedback.shouldDisplayCommandFluff()) {
hardmsg("jshell.msg.welcome", version());
}
// execute from user input
try (IOContext in = new ConsoleIOContext(this, cmdin, console)) {
start(in);
}
}
}
private void start(IOContext in) {
try {
while (regenerateOnDeath) {
if (!live) {
@ -530,144 +811,6 @@ public class JShellTool implements MessageHandler {
return editor = BUILT_IN_EDITOR;
}
/**
* Process the command line arguments.
* Set options.
* @param args the command line arguments
* @return the list of files to be loaded
*/
private List<String> processCommandArgs(String[] args) {
OptionParser parser = new OptionParser();
OptionSpec<String> cp = parser.accepts("class-path").withRequiredArg();
OptionSpec<String> mpath = parser.accepts("module-path").withRequiredArg();
OptionSpec<String> amods = parser.accepts("add-modules").withRequiredArg();
OptionSpec<String> st = parser.accepts("startup").withRequiredArg();
parser.acceptsAll(asList("n", "no-startup"));
OptionSpec<String> fb = parser.accepts("feedback").withRequiredArg();
OptionSpec<String> ec = parser.accepts("execution").withRequiredArg();
parser.accepts("q");
parser.accepts("s");
parser.accepts("v");
OptionSpec<String> r = parser.accepts("R").withRequiredArg();
OptionSpec<String> c = parser.accepts("C").withRequiredArg();
parser.acceptsAll(asList("h", "help"));
parser.accepts("version");
parser.accepts("full-version");
parser.accepts("X");
OptionSpec<String> addExports = parser.accepts("add-exports").withRequiredArg();
NonOptionArgumentSpec<String> loadFileSpec = parser.nonOptions();
OptionSet options;
try {
options = parser.parse(args);
} catch (OptionException ex) {
if (ex.options().isEmpty()) {
startmsg("jshell.err.opt.invalid", stream(args).collect(joining(", ")));
} else {
boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next());
startmsg(isKnown
? "jshell.err.opt.arg"
: "jshell.err.opt.unknown",
ex.options()
.stream()
.collect(joining(", ")));
}
return null;
}
if (options.has("help")) {
printUsage();
return null;
}
if (options.has("X")) {
printUsageX();
return null;
}
if (options.has("version")) {
cmdout.printf("jshell %s\n", version());
return null;
}
if (options.has("full-version")) {
cmdout.printf("jshell %s\n", fullVersion());
return null;
}
if (options.has(cp)) {
List<String> cps = options.valuesOf(cp);
if (cps.size() > 1) {
startmsg("jshell.err.opt.one", "--class-path");
return null;
}
cmdlineClasspath = cps.get(0);
}
if (options.has(st)) {
List<String> sts = options.valuesOf(st);
if (options.has("no-startup")) {
startmsg("jshell.err.opt.startup.conflict");
return null;
}
StringBuilder sb = new StringBuilder();
for (String fn : sts) {
String s = readFile(fn, "--startup");
if (s == null) {
return null;
}
sb.append(s);
}
startup = sb.toString();
} else if (options.has("no-startup")) {
startup = "";
}
if ((options.valuesOf(fb).size() +
(options.has("q") ? 1 : 0) +
(options.has("s") ? 1 : 0) +
(options.has("v") ? 1 : 0)) > 1) {
startmsg("jshell.err.opt.feedback.one");
return null;
} else if (options.has(fb)) {
commandLineFeedbackMode = options.valueOf(fb);
} else if (options.has("q")) {
commandLineFeedbackMode = "concise";
} else if (options.has("s")) {
commandLineFeedbackMode = "silent";
} else if (options.has("v")) {
commandLineFeedbackMode = "verbose";
}
if (options.has(r)) {
remoteVMOptions.addAll(options.valuesOf(r));
}
if (options.has(c)) {
compilerOptions.addAll(options.valuesOf(c));
}
if (options.has(mpath)) {
compilerOptions.add("--module-path");
compilerOptions.addAll(options.valuesOf(mpath));
remoteVMOptions.add("--module-path");
remoteVMOptions.addAll(options.valuesOf(mpath));
}
if (options.has(amods)) {
compilerOptions.add("--add-modules");
compilerOptions.addAll(options.valuesOf(amods));
remoteVMOptions.add("--add-modules");
remoteVMOptions.addAll(options.valuesOf(amods));
}
if (options.has(ec)) {
executionControlSpec = options.valueOf(ec);
}
if (options.has(addExports)) {
List<String> exports = options.valuesOf(addExports).stream()
.map(mp -> mp + "=ALL-UNNAMED")
.flatMap(mp -> Stream.of("--add-exports", mp))
.collect(toList());
remoteVMOptions.addAll(exports);
compilerOptions.addAll(exports);
}
return options.valuesOf(loadFileSpec);
}
private void printUsage() {
cmdout.print(getResourceString("help.usage"));
}
@ -734,8 +877,8 @@ public class JShellTool implements MessageHandler {
.idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive())
? currentNameSpace.tid(sn)
: errorNamespace.tid(sn))
.remoteVMOptions(remoteVMOptions.stream().toArray(String[]::new))
.compilerOptions(compilerOptions.stream().toArray(String[]::new));
.remoteVMOptions(options.remoteVmOptions())
.compilerOptions(options.compilerOptions());
if (executionControlSpec != null) {
builder.executionEngine(executionControlSpec);
}
@ -748,15 +891,6 @@ public class JShellTool implements MessageHandler {
});
analysis = state.sourceCodeAnalysis();
live = true;
if (!feedbackInitialized) {
// One time per run feedback initialization
feedbackInitialized = true;
initFeedback();
}
if (cmdlineClasspath != null) {
state.addToClasspath(cmdlineClasspath);
}
startUpRun(startup);
currentNameSpace = mainNamespace;
@ -767,7 +901,7 @@ public class JShellTool implements MessageHandler {
}
//where -- one-time per run initialization of feedback modes
private void initFeedback() {
private void initFeedback(String initMode) {
// No fluff, no prefix, for init failures
MessageHandler initmh = new InitMessageHandler();
// Execute the feedback initialization code in the resource file
@ -782,12 +916,11 @@ public class JShellTool implements MessageHandler {
prefs.remove(MODE_KEY);
}
}
if (commandLineFeedbackMode != null) {
if (initMode != null) {
// The feedback mode to use was specified on the command line, use it
if (!setFeedback(initmh, new ArgTokenizer("--feedback", commandLineFeedbackMode))) {
if (!setFeedback(initmh, new ArgTokenizer("--feedback", initMode))) {
regenerateOnDeath = false;
}
commandLineFeedbackMode = null;
} else {
String fb = prefs.get(FEEDBACK_KEY);
if (fb != null) {
@ -1016,6 +1149,13 @@ public class JShellTool implements MessageHandler {
this.alternatives = alternatives;
}
// Add more options to an existing provider
public FixedCompletionProvider(FixedCompletionProvider base, String... alternatives) {
List<String> l = new ArrayList<>(Arrays.asList(base.alternatives));
l.addAll(Arrays.asList(alternatives));
this.alternatives = l.toArray(new String[l.size()]);
}
@Override
public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) {
List<Suggestion> result = new ArrayList<>();
@ -1037,11 +1177,20 @@ public class JShellTool implements MessageHandler {
private static final CompletionProvider SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start ", "-history");
private static final CompletionProvider SAVE_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history ");
private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " );
private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore ", "-quiet ");
private static final CompletionProvider RESTORE_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore");
private static final CompletionProvider QUIET_COMPLETION_PROVIDER = new FixedCompletionProvider("-quiet");
private static final FixedCompletionProvider COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider(
"-class-path ", "-module-path ", "-add-modules ", "-add-exports ");
private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider(
COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER,
"-restore ", "-quiet ");
private static final CompletionProvider SET_MODE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-command", "-quiet", "-delete");
private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
private static final Map<String, CompletionProvider> ARG_OPTIONS = new HashMap<>();
static {
ARG_OPTIONS.put("-class-path", classPathCompletion());
ARG_OPTIONS.put("-module-path", fileCompletions(Files::isDirectory));
ARG_OPTIONS.put("-add-modules", EMPTY_COMPLETION_PROVIDER);
ARG_OPTIONS.put("-add-exports", EMPTY_COMPLETION_PROVIDER);
}
private final Map<String, Command> commands = new LinkedHashMap<>();
private void registerCommand(Command cmd) {
commands.put(cmd.command, cmd);
@ -1167,32 +1316,70 @@ public class JShellTool implements MessageHandler {
};
}
private static CompletionProvider reloadCompletion() {
// command-line-like option completion -- options with values
private static CompletionProvider optionCompletion(CompletionProvider provider) {
return (code, cursor, anchor) -> {
CompletionProvider provider;
int pastSpace = code.indexOf(' ') + 1; // zero if no space
if (pastSpace == 0) {
provider = RELOAD_OPTIONS_COMPLETION_PROVIDER;
} else {
switch (code.substring(0, pastSpace - 1)) {
case "-quiet":
provider = RESTORE_COMPLETION_PROVIDER;
break;
case "-restore":
provider = QUIET_COMPLETION_PROVIDER;
break;
default:
provider = EMPTY_COMPLETION_PROVIDER;
break;
Matcher ovm = OPTION_VALUE_PATTERN.matcher(code);
if (ovm.matches()) {
String flag = ovm.group("flag");
List<CompletionProvider> ps = ARG_OPTIONS.entrySet().stream()
.filter(es -> es.getKey().startsWith(flag))
.map(es -> es.getValue())
.collect(toList());
if (ps.size() == 1) {
int pastSpace = ovm.start("val");
List<Suggestion> result = ps.get(0).completionSuggestions(
ovm.group("val"), cursor - pastSpace, anchor);
anchor[0] += pastSpace;
return result;
}
}
List<Suggestion> result = provider.completionSuggestions(
code.substring(pastSpace), cursor - pastSpace, anchor);
anchor[0] += pastSpace;
return result;
Matcher om = OPTION_PATTERN.matcher(code);
if (om.matches()) {
int pastSpace = om.start("flag");
List<Suggestion> result = provider.completionSuggestions(
om.group("flag"), cursor - pastSpace, anchor);
if (!om.group("dd").isEmpty()) {
result = result.stream()
.map(sug -> new Suggestion() {
@Override
public String continuation() {
return "-" + sug.continuation();
}
@Override
public boolean matchesType() {
return false;
}
})
.collect(toList());
--pastSpace;
}
anchor[0] += pastSpace;
return result;
}
Matcher opp = OPTION_PRE_PATTERN.matcher(code);
if (opp.matches()) {
int pastSpace = opp.end();
List<Suggestion> result = provider.completionSuggestions(
"", cursor - pastSpace, anchor);
anchor[0] += pastSpace;
return result;
}
return Collections.emptyList();
};
}
// /reload command completion
private static CompletionProvider reloadCompletion() {
return optionCompletion(RELOAD_OPTIONS_COMPLETION_PROVIDER);
}
// /env command completion
private static CompletionProvider envCompletion() {
return optionCompletion(COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER);
}
private static CompletionProvider orMostSpecificCompletion(
CompletionProvider left, CompletionProvider right) {
return (code, cursor, anchor) -> {
@ -1286,16 +1473,15 @@ public class JShellTool implements MessageHandler {
registerCommand(new Command("/exit",
arg -> cmdExit(),
EMPTY_COMPLETION_PROVIDER));
registerCommand(new Command("/env",
arg -> cmdEnv(arg),
envCompletion()));
registerCommand(new Command("/reset",
arg -> cmdReset(),
EMPTY_COMPLETION_PROVIDER));
arg -> cmdReset(arg),
envCompletion()));
registerCommand(new Command("/reload",
this::cmdReload,
reloadCompletion()));
registerCommand(new Command("/classpath",
this::cmdClasspath,
classPathCompletion(),
CommandKind.REPLAY));
registerCommand(new Command("/history",
arg -> cmdHistory(),
EMPTY_COMPLETION_PROVIDER));
@ -1344,6 +1530,9 @@ public class JShellTool implements MessageHandler {
registerCommand(new Command("shortcuts",
"help.shortcuts",
CommandKind.HELP_SUBJECT));
registerCommand(new Command("context",
"help.context",
CommandKind.HELP_SUBJECT));
commandCompletions = new ContinuousCompletionProvider(
commands.values().stream()
@ -1692,17 +1881,6 @@ public class JShellTool implements MessageHandler {
hard(stset);
}
boolean cmdClasspath(String arg) {
if (arg.isEmpty()) {
errormsg("jshell.err.classpath.arg");
return false;
} else {
state.addToClasspath(toPathResolvingUserHome(arg).toString());
fluffmsg("jshell.msg.classpath", arg);
return true;
}
}
boolean cmdDebug(String arg) {
if (arg.isEmpty()) {
debug = !debug;
@ -2228,7 +2406,6 @@ public class JShellTool implements MessageHandler {
}
// Read a built-in file from resources or null
String getResource(String name) {
if (BUILTIN_FILE_PATTERN.matcher(name).matches()) {
try {
@ -2266,20 +2443,22 @@ public class JShellTool implements MessageHandler {
return defaultStartup;
}
private boolean cmdReset() {
private boolean cmdReset(String rawargs) {
if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) {
return false;
}
live = false;
fluffmsg("jshell.msg.resetting.state");
return true;
}
private boolean cmdReload(String rawargs) {
ArgTokenizer at = new ArgTokenizer("/reload", rawargs.trim());
at.allowedOptions("-restore", "-quiet");
if (!checkOptionsAndRemainingInput(at)) {
OptionParserReload ap = new OptionParserReload();
if (!parseCommandLineLikeFlags(rawargs, ap)) {
return false;
}
Iterable<String> history;
if (at.hasOption("-restore")) {
if (ap.restore()) {
if (replayableHistoryPrevious == null) {
errormsg("jshell.err.reload.no.previous");
return false;
@ -2290,13 +2469,57 @@ public class JShellTool implements MessageHandler {
history = replayableHistory;
fluffmsg("jshell.err.reload.restarting.state");
}
boolean echo = !at.hasOption("-quiet");
return doReload(history, !ap.quiet());
}
private boolean cmdEnv(String rawargs) {
if (rawargs.trim().isEmpty()) {
// No arguments, display current settings (as option flags)
StringBuilder sb = new StringBuilder();
for (String a : options.commonOptions()) {
sb.append(
a.startsWith("-")
? sb.length() > 0
? "\n "
: " "
: " ");
sb.append(a);
}
if (sb.length() > 0) {
rawout(prefix(sb.toString()));
}
return false;
}
if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) {
return false;
}
fluffmsg("jshell.msg.set.restore");
return doReload(replayableHistory, false);
}
private boolean doReload(Iterable<String> history, boolean echo) {
resetState();
run(new ReloadIOContext(history,
echo ? cmdout : null));
return true;
}
private boolean parseCommandLineLikeFlags(String rawargs, OptionParserBase ap) {
String[] args = Arrays.stream(rawargs.split("\\s+"))
.filter(s -> !s.isEmpty())
.toArray(String[]::new);
Options opts = ap.parse(args);
if (opts == null) {
return false;
}
if (!ap.nonOptions().isEmpty()) {
errormsg("jshell.err.unexpected.at.end", ap.nonOptions(), rawargs);
return false;
}
options.override(opts);
return true;
}
private boolean cmdSave(String rawargs) {
ArgTokenizer at = new ArgTokenizer("/save", rawargs.trim());
at.allowedOptions("-all", "-start", "-history");

@ -52,6 +52,7 @@ jshell.err.unexpected.exception = Unexpected exception: {0}
jshell.err.no.such.command.or.snippet.id = No such command or snippet id: {0}
jshell.err.command.ambiguous = Command: ''{0}'' is ambiguous: {1}
jshell.msg.set.restore = Setting new options and restoring state.
jshell.msg.set.editor.set = Editor set to: {0}
jshell.msg.set.editor.retain = Editor setting retained: {0}
jshell.err.no.builtin.editor = Built-in editor not available.
@ -319,21 +320,25 @@ Leave the jshell tool. No work is saved.\n\
Save any work before using this command
help.reset.summary = reset jshell
help.reset.args =
help.reset.args = \
[-class-path <path>] [-module-path <path>] [-add-modules <modules>]...
help.reset =\
Reset the jshell tool code and execution state:\n\t\
* All entered code is lost.\n\t\
* Start-up code is re-executed.\n\t\
* The execution state is restarted.\n\t\
* The classpath is cleared.\n\
Tool settings are maintained, as set with: /set ...\n\
Save any work before using this command
Save any work before using this command.\n\
The /reset command accepts context options, see:\n\n\t\
/help context\n\
help.reload.summary = reset and replay relevant history -- current or previous (-restore)
help.reload.args = [-restore] [-quiet]
help.reload.args = \
[-restore] [-quiet] [-class-path <path>] [-module-path <path>]...
help.reload =\
Reset the jshell tool code and execution state then replay each valid snippet\n\
and any /drop or /classpath commands in the order they were entered.\n\
and any /drop commands in the order they were entered.\n\
\n\
/reload\n\t\
Reset and replay the valid history since jshell was entered, or\n\t\
@ -345,12 +350,31 @@ and any /drop or /classpath commands in the order they were entered.\n\
command was executed. This can thus be used to restore a previous\n\t\
jshell tool session.\n\n\
/reload [-restore] -quiet\n\t\
With the '-quiet' argument the replay is not shown. Errors will display.
With the '-quiet' argument the replay is not shown. Errors will display.\n\
\n\
Each of the above accepts context options, see:\n\n\t\
/help context\n\
\n\
For example:\n\n\t\
/reload -add-modules com.greetings -restore
help.classpath.summary = add a path to the classpath
help.classpath.args = <path>
help.classpath =\
Append a additional path to the classpath.
help.env.summary = view or change the evaluation context
help.env.args = \
[-class-path <path>] [-module-path <path>] [-add-modules <modules>] ...
help.env =\
View or change the evaluation context. The evaluation context is the class path,\n\
module path, etc.\n\
/env\n\t\
Show the evaluation context displayed as context options.\n\n\
/env [-class-path <path>] [-module-path <path>] [-add-modules <modules>] ...\n\t\
With at least one option set, sets the evaluation context. If snippets\n\t\
have been defined, the execution state is reset with the new\n\t\
evaluation context and the snippets will be replayed -- the replay is not\n\t\
shown, however, errors will display. This is equivalent to: /reload -quiet\n\t\
For details of context options, see:\n\n\t\t\
/help context\n\n\t\
For example:\n\n\t\t\
/env -add-modules com.greetings
help.history.summary = history of what you have typed
help.history.args =
@ -473,6 +497,37 @@ Shift-<tab>\n\t\t\
possible fully qualified names based on the content of the specified classpath.\n\t\t\
The "<fix-shortcut>" is either Alt-F1 or Alt-Enter, depending on the platform.
help.context.summary = the evaluation context options for /env /reload and /reset
help.context =\
These options configure the evaluation context, they can be specified when\n\
jshell is started: on the command-line, or restarted with the commands /env,\n\
/reload, or /reset.\n\
\n\
They are:\n\t\
--class-path <class search path of directories and zip/jar files>\n\t\t\
A list of directories, JAR archives,\n\t\t\
and ZIP archives to search for class files.\n\t\t\
The list is separated with the path separator\n\t\t\
(a : on unix/linux/mac, and ; on windows).\n\t\
--module-path <module path>...\n\t\t\
A list of directories, each directory\n\t\t\
is a directory of modules.\n\t\t\
The list is separated with the path separator\n\t\t\
(a : on unix/linux/mac, and ; on windows).\n\t\
--add-modules <modulename>[,<modulename>...]\n\t\t\
root modules to resolve in addition to the initial module.\n\t\t\
<modulename> can also be ALL-DEFAULT, ALL-SYSTEM,\n\t\t\
ALL-MODULE-PATH.\n\t\
--add-exports <module>/<package>=<target-module>(,<target-module>)*\n\t\t\
updates <module> to export <package> to <target-module>,\n\t\t\
regardless of module declaration.\n\t\t\
<target-module> can be ALL-UNNAMED to export to all\n\t\t\
unnamed modules. In jshell, if the <target-module> is not\n\t\t\
specified (no =) then ALL-UNNAMED is used.\n\
\n\
On the command-line these options must have two dashes, e.g.: --module-path\n\
On jshell commands they can have one or two dashes, e.g.: -module-path\n\
help.set._retain = \
The '-retain' option saves a setting so that it is used in future sessions.\n\
The -retain option can be used on the following forms of /set:\n\n\t\

@ -161,6 +161,10 @@ class TaskFactory {
@Override
public Diag diag(Diagnostic<? extends JavaFileObject> d) {
SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
if (smjfo == null) {
// Handle failure that doesn't preserve mapping
return new StringSourceHandler().diag(d);
}
OuterWrap w = (OuterWrap) smjfo.getOrigin();
return w.wrapDiag(d);
}

@ -23,7 +23,7 @@
/*
* @test
* @bug 8144095 8164825 8169818 8153402
* @bug 8144095 8164825 8169818 8153402 8165405
* @summary Test Command Completion
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -138,7 +138,7 @@ public class CommandCompletionTest extends ReplToolTesting {
@Test
public void testEdit() {
test(false, new String[]{"--no-startup"},
a -> assertCompletion(a, "/e|", false, "/edit ", "/exit "),
a -> assertCompletion(a, "/e|", false, "/edit ", "/env ", "/exit "),
a -> assertCompletion(a, "/ed|", false, "/edit "),
a -> assertClass(a, "class cTest {}", "class", "cTest"),
a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
@ -158,15 +158,17 @@ public class CommandCompletionTest extends ReplToolTesting {
public void testHelp() {
testNoStartUp(
a -> assertCompletion(a, "/help |", false,
"/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
"/edit ", "/exit ", "/help ", "/history ", "/imports ",
"/! ", "/-<n> ", "/<id> ", "/? ", "/drop ",
"/edit ", "/env ", "/exit ",
"/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "),
a -> assertCompletion(a, "/? |", false,
"/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
"/edit ", "/exit ", "/help ", "/history ", "/imports ",
"/! ", "/-<n> ", "/<id> ", "/? ", "/drop ",
"/edit ", "/env ", "/exit ",
"/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "),
a -> assertCompletion(a, "/help /s|", false,
"/save ", "/set "),
a -> assertCompletion(a, "/help /set |", false,
@ -177,17 +179,63 @@ public class CommandCompletionTest extends ReplToolTesting {
@Test
public void testReload() {
String[] ropts = new String[] { "-add-exports ", "-add-modules ",
"-class-path ", "-module-path ", "-quiet ", "-restore " };
String[] dropts = new String[] { "--add-exports ", "--add-modules ",
"--class-path ", "--module-path ", "--quiet ", "--restore " };
testNoStartUp(
a -> assertCompletion(a, "/reload |", false, "-quiet ", "-restore "),
a -> assertCompletion(a, "/reload -restore |", false, "-quiet"),
a -> assertCompletion(a, "/reload -quiet |", false, "-restore"),
a -> assertCompletion(a, "/reload -restore -quiet |", false)
a -> assertCompletion(a, "/reloa |", false, ropts),
a -> assertCompletion(a, "/relo |", false, ropts),
a -> assertCompletion(a, "/reload -|", false, ropts),
a -> assertCompletion(a, "/reload --|", false, dropts),
a -> assertCompletion(a, "/reload -restore |", false, ropts),
a -> assertCompletion(a, "/reload -restore --|", false, dropts),
a -> assertCompletion(a, "/reload -rest|", false, "-restore "),
a -> assertCompletion(a, "/reload --r|", false, "--restore "),
a -> assertCompletion(a, "/reload -q|", false, "-quiet "),
a -> assertCompletion(a, "/reload -add|", false, "-add-exports ", "-add-modules "),
a -> assertCompletion(a, "/reload -class-path . -quiet |", false, ropts)
);
}
@Test
public void testEnv() {
String[] ropts = new String[] { "-add-exports ", "-add-modules ",
"-class-path ", "-module-path " };
String[] dropts = new String[] { "--add-exports ", "--add-modules ",
"--class-path ", "--module-path " };
testNoStartUp(
a -> assertCompletion(a, "/env |", false, ropts),
a -> assertCompletion(a, "/env -|", false, ropts),
a -> assertCompletion(a, "/env --|", false, dropts),
a -> assertCompletion(a, "/env --a|", false, "--add-exports ", "--add-modules "),
a -> assertCompletion(a, "/env -add-|", false, "-add-exports ", "-add-modules "),
a -> assertCompletion(a, "/env -class-path . |", false, ropts),
a -> assertCompletion(a, "/env -class-path . --|", false, dropts)
);
}
@Test
public void testReset() {
String[] ropts = new String[] { "-add-exports ", "-add-modules ",
"-class-path ", "-module-path " };
String[] dropts = new String[] { "--add-exports ", "--add-modules ",
"--class-path ", "--module-path " };
testNoStartUp(
a -> assertCompletion(a, "/reset |", false, ropts),
a -> assertCompletion(a, "/res -m|", false, "-module-path "),
a -> assertCompletion(a, "/res -module-|", false, "-module-path "),
a -> assertCompletion(a, "/res --m|", false, "--module-path "),
a -> assertCompletion(a, "/res --module-|", false, "--module-path "),
a -> assertCompletion(a, "/reset -add|", false, "-add-exports ", "-add-modules "),
a -> assertCompletion(a, "/rese -class-path . |", false, ropts),
a -> assertCompletion(a, "/rese -class-path . --|", false, dropts)
);
}
@Test
public void testVarsMethodsTypes() {
test(false, new String[]{"--no-startup"},
testNoStartUp(
a -> assertCompletion(a, "/v|", false, "/vars "),
a -> assertCompletion(a, "/m|", false, "/methods "),
a -> assertCompletion(a, "/t|", false, "/types "),
@ -245,9 +293,6 @@ public class CommandCompletionTest extends ReplToolTesting {
@Test
public void testClassPath() throws IOException {
testNoStartUp(
a -> assertCompletion(a, "/classp|", false, "/classpath ")
);
Compiler compiler = new Compiler();
Path outDir = compiler.getPath("testClasspathCompletion");
Files.createDirectories(outDir);
@ -259,8 +304,13 @@ public class CommandCompletionTest extends ReplToolTesting {
compiler.jar(outDir, jarName, "pkg/A.class");
compiler.getPath(outDir).resolve(jarName);
List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
String[] pathArray = paths.toArray(new String[paths.size()]);
testNoStartUp(
a -> assertCompletion(a, "/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]))
a -> assertCompletion(a, "/env -class-path " + outDir + "/|", false, pathArray),
a -> assertCompletion(a, "/env --class-path " + outDir + "/|", false, pathArray),
a -> assertCompletion(a, "/env -clas " + outDir + "/|", false, pathArray),
a -> assertCompletion(a, "/env --class-p " + outDir + "/|", false, pathArray),
a -> assertCompletion(a, "/env --module-path . --class-p " + outDir + "/|", false, pathArray)
);
}
@ -275,7 +325,7 @@ public class CommandCompletionTest extends ReplToolTesting {
.collect(Collectors.toList());
}
testNoStartUp(
a -> assertCompletion(a, "/classpath ~/|", false, completions.toArray(new String[completions.size()]))
a -> assertCompletion(a, "/env --class-path ~/|", false, completions.toArray(new String[completions.size()]))
);
}

@ -23,7 +23,7 @@
/*
* @test
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 8165405
* @summary Tests for Basic tests for REPL tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -263,7 +263,8 @@ public class ToolBasicTest extends ReplToolTesting {
compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
Path classpath = compiler.getPath(outDir);
test(
(a) -> assertCommand(a, "/classpath " + classpath, String.format("| Path '%s' added to classpath", classpath)),
(a) -> assertCommand(a, "/env --class-path " + classpath,
"| Setting new options and restoring state."),
(a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
);
test(new String[] { "--class-path", classpath.toString() },
@ -279,7 +280,8 @@ public class ToolBasicTest extends ReplToolTesting {
compiler.jar(outDir, jarName, "pkg/A.class");
Path jarPath = compiler.getPath(outDir).resolve(jarName);
test(
(a) -> assertCommand(a, "/classpath " + jarPath, String.format("| Path '%s' added to classpath", jarPath)),
(a) -> assertCommand(a, "/env --class-path " + jarPath,
"| Setting new options and restoring state."),
(a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
);
test(new String[] { "--class-path", jarPath.toString() },

@ -24,7 +24,7 @@
/*
* @test
* @key intermittent
* @bug 8081845 8147898 8143955
* @bug 8081845 8147898 8143955 8165405
* @summary Tests for /reload in JShell tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -70,8 +70,8 @@ public class ToolReloadTest extends ReplToolTesting {
compiler.compile(outDir, prog.apply("A"));
Path classpath = compiler.getPath(outDir);
test(
(a) -> assertCommand(a, "/classpath " + classpath,
String.format("| Path '%s' added to classpath", classpath)),
(a) -> assertCommand(a, "/env --class-path " + classpath,
"| Setting new options and restoring state."),
(a) -> assertMethod(a, "String foo() { return (new pkg.A()).toString(); }",
"()String", "foo"),
(a) -> assertVariable(a, "String", "v", "foo()", "\"A\""),
@ -79,7 +79,6 @@ public class ToolReloadTest extends ReplToolTesting {
if (!a) compiler.compile(outDir, prog.apply("Aprime"));
assertCommand(a, "/reload",
"| Restarting and restoring state.\n" +
"-: /classpath " + classpath + "\n" +
"-: String foo() { return (new pkg.A()).toString(); }\n" +
"-: String v = foo();\n");
},

@ -23,7 +23,7 @@
/*
* @test
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405
* @summary Simple jshell tool tests
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -196,7 +196,7 @@ public class ToolSimpleTest extends ReplToolTesting {
@Test
public void testEmptyClassPath() {
test(after -> assertCommand(after, "/classpath", "| The /classpath command requires a path argument."));
test(after -> assertCommand(after, "/env --class-path", "| Argument to class-path missing."));
}
@Test
@ -605,6 +605,13 @@ public class ToolSimpleTest extends ReplToolTesting {
);
}
@Test
public void testWrapSourceHandlerDiagCrash() {
test(new String[]{"--add-exports", "jdk.javadoc/ALL-UNNAMED"},
(a) -> assertCommand(a, "1+1", "$1 ==> 2")
);
}
@Test
public void test8156910() {
test(