Skip to content

Commit fe6a202

Browse files
jakobcornellplummercj
authored andcommitted
8271356: Modify jdb to treat an empty command as a repeat of the previous command
Reviewed-by: cjplummer, iklam
1 parent cef9db9 commit fe6a202

File tree

10 files changed

+554
-43
lines changed

10 files changed

+554
-43
lines changed

src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/Commands.java

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -45,6 +45,10 @@
4545
import java.io.*;
4646

4747
class Commands {
48+
/**
49+
* Number of lines to show before the target line during {@code list}.
50+
*/
51+
protected static final int LIST_LINE_LOOKBEHIND = 4;
4852

4953
abstract class AsyncExecution {
5054
abstract void action();
@@ -1472,39 +1476,46 @@ void commandUntrace(StringTokenizer t) {
14721476
}
14731477
}
14741478

1475-
void commandList(StringTokenizer t) {
1479+
/**
1480+
* @param lineHint source line number to target by default, or {@code null} to use the execution point
1481+
* @return line hint to be used in a subsequent {@code list} command, if any
1482+
*/
1483+
Integer commandList(StringTokenizer t, Integer lineHint) {
14761484
StackFrame frame = null;
14771485
ThreadInfo threadInfo = ThreadInfo.getCurrentThreadInfo();
14781486
if (threadInfo == null) {
14791487
MessageOutput.println("No thread specified.");
1480-
return;
1488+
return null;
14811489
}
14821490
try {
14831491
frame = threadInfo.getCurrentFrame();
14841492
} catch (IncompatibleThreadStateException e) {
14851493
MessageOutput.println("Current thread isnt suspended.");
1486-
return;
1494+
return null;
14871495
}
14881496

14891497
if (frame == null) {
14901498
MessageOutput.println("No frames on the current call stack");
1491-
return;
1499+
return null;
14921500
}
14931501

14941502
Location loc = frame.location();
14951503
if (loc.method().isNative()) {
14961504
MessageOutput.println("Current method is native");
1497-
return;
1505+
return null;
14981506
}
14991507

15001508
String sourceFileName = null;
15011509
try {
15021510
sourceFileName = loc.sourceName();
15031511

15041512
ReferenceType refType = loc.declaringType();
1505-
int lineno = loc.lineNumber();
15061513

1507-
if (t.hasMoreTokens()) {
1514+
int lineno;
1515+
var useDefault = !t.hasMoreTokens();
1516+
if (useDefault) {
1517+
lineno = lineHint == null ? loc.lineNumber() : lineHint;
1518+
} else {
15081519
String id = t.nextToken();
15091520

15101521
// See if token is a line number.
@@ -1515,35 +1526,48 @@ void commandList(StringTokenizer t) {
15151526
lineno = n.intValue();
15161527
} catch (java.text.ParseException jtpe) {
15171528
// It isn't -- see if it's a method name.
1518-
List<Method> meths = refType.methodsByName(id);
1519-
if (meths == null || meths.size() == 0) {
1520-
MessageOutput.println("is not a valid line number or method name for",
1521-
new Object [] {id, refType.name()});
1522-
return;
1523-
} else if (meths.size() > 1) {
1524-
MessageOutput.println("is an ambiguous method name in",
1525-
new Object [] {id, refType.name()});
1526-
return;
1527-
}
1528-
loc = meths.get(0).location();
1529-
lineno = loc.lineNumber();
1529+
List<Method> meths = refType.methodsByName(id);
1530+
if (meths == null || meths.size() == 0) {
1531+
MessageOutput.println("is not a valid line number or method name for",
1532+
new Object [] {id, refType.name()});
1533+
return null;
1534+
} else if (meths.size() > 1) {
1535+
MessageOutput.println("is an ambiguous method name in",
1536+
new Object [] {id, refType.name()});
1537+
return null;
1538+
}
1539+
loc = meths.get(0).location();
1540+
lineno = loc.lineNumber();
15301541
}
15311542
}
1532-
int startLine = Math.max(lineno - 4, 1);
1533-
int endLine = startLine + 9;
1543+
int startLine = Math.max(lineno - LIST_LINE_LOOKBEHIND, 1);
1544+
int endLine = startLine + 10;
15341545
if (lineno < 0) {
15351546
MessageOutput.println("Line number information not available for");
1536-
} else if (Env.sourceLine(loc, lineno) == null) {
1537-
MessageOutput.println("is an invalid line number for",
1538-
new Object [] {Integer.valueOf(lineno),
1539-
refType.name()});
1547+
} else if (useDefault && Env.sourceLine(loc, startLine) == null) {
1548+
/*
1549+
* If we're out of range with a default line number then we've hit EOF on auto-advance. Stay at this
1550+
* position until a different source location is requested, as in GDB.
1551+
*/
1552+
MessageOutput.println("EOF");
1553+
return lineno;
1554+
} else if (!useDefault && Env.sourceLine(loc, lineno) == null) {
1555+
MessageOutput.println(
1556+
"is an invalid line number for",
1557+
new Object[] {
1558+
Integer.valueOf(lineno),
1559+
refType.name()
1560+
}
1561+
);
1562+
return null;
15401563
} else {
1541-
for (int i = startLine; i <= endLine; i++) {
1564+
for (int i = startLine; i < endLine; i++) {
15421565
String sourceLine = Env.sourceLine(loc, i);
15431566
if (sourceLine == null) {
1544-
break;
1567+
// Next listing will start just out of range, triggering an EOF message.
1568+
return i + LIST_LINE_LOOKBEHIND;
15451569
}
1546-
if (i == lineno) {
1570+
if (i == loc.lineNumber()) {
15471571
MessageOutput.println("source line number current line and line",
15481572
new Object [] {Integer.valueOf(i),
15491573
sourceLine});
@@ -1553,6 +1577,7 @@ void commandList(StringTokenizer t) {
15531577
sourceLine});
15541578
}
15551579
}
1580+
return endLine + LIST_LINE_LOOKBEHIND; // start next listing with `endLine'
15561581
}
15571582
} catch (AbsentInformationException e) {
15581583
MessageOutput.println("No source information available for:", loc.toString());
@@ -1561,6 +1586,7 @@ void commandList(StringTokenizer t) {
15611586
} catch(IOException exc) {
15621587
MessageOutput.println("I/O exception occurred:", exc.toString());
15631588
}
1589+
return null;
15641590
}
15651591

15661592
void commandLines(StringTokenizer t) { // Undocumented command: useful for testing

src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTY.java

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -44,6 +44,21 @@
4444
import java.io.*;
4545

4646
public class TTY implements EventNotifier {
47+
/**
48+
* Commands that are repeatable on empty input.
49+
*/
50+
protected static final Set<String> REPEATABLE = Set.of(
51+
"up", "down", "step", "stepi", "next", "cont", "list", "pop", "reenter"
52+
);
53+
54+
/**
55+
* Commands that reset the default source line to be displayed by {@code list}.
56+
*/
57+
protected static final Set<String> LIST_RESET = Set.of(
58+
"run", "suspend", "resume", "up", "down", "kill", "interrupt", "threadgroup", "step", "stepi", "next", "cont",
59+
"pop", "reenter"
60+
);
61+
4762
EventHandler handler = null;
4863

4964
/**
@@ -59,6 +74,16 @@ public class TTY implements EventNotifier {
5974

6075
private volatile boolean shuttingDown = false;
6176

77+
/**
78+
* The number of the next source line to target for {@code list}, if any.
79+
*/
80+
protected Integer nextListTarget = null;
81+
82+
/**
83+
* Whether to repeat when the user enters an empty command.
84+
*/
85+
protected boolean repeat = false;
86+
6287
public void setShuttingDown(boolean s) {
6388
shuttingDown = s;
6489
}
@@ -335,6 +360,7 @@ void help() {
335360
{"read", "y", "y"},
336361
{"redefine", "n", "n"},
337362
{"reenter", "n", "n"},
363+
{"repeat", "y", "y"},
338364
{"resume", "n", "n"},
339365
{"run", "y", "n"},
340366
{"save", "n", "n"},
@@ -408,12 +434,15 @@ private boolean isReadOnlyCmd(int ii) {
408434
};
409435

410436

411-
void executeCommand(StringTokenizer t) {
437+
/**
438+
* @return the name (first token) of the command processed
439+
*/
440+
String executeCommand(StringTokenizer t) {
412441
String cmd = t.nextToken().toLowerCase();
442+
413443
// Normally, prompt for the next command after this one is done
414444
boolean showPrompt = true;
415445

416-
417446
/*
418447
* Anything starting with # is discarded as a no-op or 'comment'.
419448
*/
@@ -426,15 +455,16 @@ void executeCommand(StringTokenizer t) {
426455
try {
427456
int repeat = Integer.parseInt(cmd);
428457
String subcom = t.nextToken("");
429-
while (repeat-- > 0) {
430-
executeCommand(new StringTokenizer(subcom));
458+
for (int r = 0; r < repeat; r += 1) {
459+
cmd = executeCommand(new StringTokenizer(subcom));
431460
showPrompt = false; // Bypass the printPrompt() below.
432461
}
433462
} catch (NumberFormatException exc) {
434463
MessageOutput.println("Unrecognized command. Try help...", cmd);
435464
}
436465
} else {
437466
int commandNumber = isCommand(cmd);
467+
438468
/*
439469
* Check for an unknown command
440470
*/
@@ -448,7 +478,6 @@ void executeCommand(StringTokenizer t) {
448478
MessageOutput.println("Command is not supported on a read-only VM connection",
449479
cmd);
450480
} else {
451-
452481
Commands evaluator = new Commands();
453482
try {
454483
if (cmd.equals("print")) {
@@ -550,7 +579,7 @@ void executeCommand(StringTokenizer t) {
550579
} else if (cmd.equals("unwatch")) {
551580
evaluator.commandUnwatch(t);
552581
} else if (cmd.equals("list")) {
553-
evaluator.commandList(t);
582+
nextListTarget = evaluator.commandList(t, repeat ? nextListTarget : null);
554583
} else if (cmd.equals("lines")) { // Undocumented command: useful for testing.
555584
evaluator.commandLines(t);
556585
} else if (cmd.equals("classpath")) {
@@ -596,6 +625,8 @@ void executeCommand(StringTokenizer t) {
596625
} else if (cmd.equals("version")) {
597626
evaluator.commandVersion(progname,
598627
Bootstrap.virtualMachineManager());
628+
} else if (cmd.equals("repeat")) {
629+
doRepeat(t);
599630
} else if (cmd.equals("quit") || cmd.equals("exit")) {
600631
if (handler != null) {
601632
handler.shutdown();
@@ -620,6 +651,12 @@ void executeCommand(StringTokenizer t) {
620651
if (showPrompt) {
621652
MessageOutput.printPrompt();
622653
}
654+
655+
if (LIST_RESET.contains(cmd)) {
656+
nextListTarget = null;
657+
}
658+
659+
return cmd;
623660
}
624661

625662
/*
@@ -661,7 +698,6 @@ void unmonitorCommand(StringTokenizer t) {
661698
}
662699
}
663700

664-
665701
void readCommand(StringTokenizer t) {
666702
if (t.hasMoreTokens()) {
667703
String cmdfname = t.nextToken();
@@ -673,6 +709,19 @@ void readCommand(StringTokenizer t) {
673709
}
674710
}
675711

712+
protected void doRepeat(StringTokenizer t) {
713+
if (t.hasMoreTokens()) {
714+
var choice = t.nextToken().toLowerCase();
715+
if ((choice.equals("on") || choice.equals("off")) && !t.hasMoreTokens()) {
716+
repeat = choice.equals("on");
717+
} else {
718+
MessageOutput.println("repeat usage");
719+
}
720+
} else {
721+
MessageOutput.println(repeat ? "repeat is on" : "repeat is off");
722+
}
723+
}
724+
676725
/**
677726
* Read and execute a command file. Return true if the file was read
678727
* else false;
@@ -749,8 +798,6 @@ public TTY() throws Exception {
749798
BufferedReader in =
750799
new BufferedReader(new InputStreamReader(System.in));
751800

752-
String lastLine = null;
753-
754801
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
755802

756803
/*
@@ -788,6 +835,9 @@ public TTY() throws Exception {
788835

789836
// Process interactive commands.
790837
MessageOutput.printPrompt();
838+
839+
String lastLine = null;
840+
String lastCommandName = null;
791841
while (true) {
792842
String ln = in.readLine();
793843
if (ln == null) {
@@ -809,7 +859,11 @@ public TTY() throws Exception {
809859
StringTokenizer t = new StringTokenizer(ln);
810860
if (t.hasMoreTokens()) {
811861
lastLine = ln;
812-
executeCommand(t);
862+
lastCommandName = executeCommand(t);
863+
} else if (repeat && lastLine != null && REPEATABLE.contains(lastCommandName)) {
864+
// We want list auto-advance even if the user started with a listing target.
865+
String newCommand = lastCommandName.equals("list") && nextListTarget != null ? "list" : lastLine;
866+
executeCommand(new StringTokenizer(newCommand));
813867
} else {
814868
MessageOutput.printPrompt();
815869
}

src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTYResources.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -110,6 +110,7 @@ public Object[][] getContents() {
110110
{"dbgtrace command value must be an integer:", "dbgtrace command value must be an integer: {0}"},
111111
{"Deferring.", "Deferring {0}.\nIt will be set after the class is loaded."},
112112
{"End of stack.", "End of stack."},
113+
{"EOF", "EOF"},
113114
{"Error popping frame", "Error popping frame - {0}"},
114115
{"Error reading file", "Error reading ''{0}'' - {1}"},
115116
{"Error redefining class to file", "Error redefining {0} to {1} - {2}"},
@@ -260,6 +261,9 @@ public Object[][] getContents() {
260261
" <class_id>.<method>[(argument_type,...)]"
261262
},
262263
{"Removed:", "Removed: {0}"},
264+
{"repeat is on", "Repeat is on"},
265+
{"repeat is off", "Repeat is off"},
266+
{"repeat usage", "Usage: repeat <on|off>"},
263267
{"Requested stack frame is no longer active:", "Requested stack frame is no longer active: {0,number,integer}"},
264268
{"run <args> command is valid only with launched VMs", "'run <args>' command is valid only with launched VMs"},
265269
{"run", "run {0}"},
@@ -436,6 +440,8 @@ public Object[][] getContents() {
436440
"\n" +
437441
"!! -- repeat last command\n" +
438442
"<n> <command> -- repeat command n times\n" +
443+
"repeat -- show whether GDB-style empty command repetition is enabled\n" +
444+
"repeat <on|off> -- enable/disable GDB-style repetition\n" +
439445
"# <command> -- discard (no-op)\n" +
440446
"help (or ?) -- list commands\n" +
441447
"dbgtrace [flag] -- same as dbgtrace command line option\n" +

0 commit comments

Comments
 (0)