-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[LLDB]Provide clearer error message for invalid commands. #111891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Sometimes users (esp. gdb-longtime users) accidentally use GDB syntax, such as `breakpoint foo`, and they would get an error message from LLDB saying simply `Invalid command "breakpoint foo"`, which is not very helpful. This change provides additional suggestions to help correcting the mistake.
@llvm/pr-subscribers-lldb Author: Vy Nguyen (oontvoo) ChangesSometimes users (esp. gdb-longtime users) accidentally use GDB syntax, such as This change provides additional suggestions to help correcting the mistake. Full diff: https://github.com/llvm/llvm-project/pull/111891.diff 3 Files Affected:
diff --git a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
index bceb7f0e51edb6..cee118c3f454b5 100644
--- a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
+++ b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h
@@ -70,6 +70,8 @@ class CommandObjectMultiword : public CommandObject {
return m_subcommand_dict;
}
+ std::string GetTopSubcommands(int count);
+
CommandObject::CommandMap m_subcommand_dict;
bool m_can_be_removed;
};
diff --git a/lldb/source/Commands/CommandObjectMultiword.cpp b/lldb/source/Commands/CommandObjectMultiword.cpp
index 4efa5652a71703..f7bb58187895b4 100644
--- a/lldb/source/Commands/CommandObjectMultiword.cpp
+++ b/lldb/source/Commands/CommandObjectMultiword.cpp
@@ -194,28 +194,50 @@ void CommandObjectMultiword::Execute(const char *args_string,
std::string error_msg;
const size_t num_subcmd_matches = matches.GetSize();
- if (num_subcmd_matches > 0)
+ if (num_subcmd_matches > 0) {
error_msg.assign("ambiguous command ");
- else
- error_msg.assign("invalid command ");
-
- error_msg.append("'");
- error_msg.append(std::string(GetCommandName()));
- error_msg.append(" ");
- error_msg.append(std::string(sub_command));
- error_msg.append("'.");
+ error_msg.append("'");
+ error_msg.append(std::string(GetCommandName()));
+ error_msg.append(" ");
+ error_msg.append(std::string(sub_command));
+ error_msg.append("'.");
- if (num_subcmd_matches > 0) {
error_msg.append(" Possible completions:");
for (const std::string &match : matches) {
error_msg.append("\n\t");
error_msg.append(match);
}
+ } else {
+ // Rather than simply complaining about the invalid (sub) command,
+ // try to offer some alternatives.
+ // This is especially useful for cases where the user types something
+ // seamingly trivial, such as `breakpoint foo`.
+ error_msg.assign(
+ llvm::Twine("'" + sub_command + "' is not a valid subcommand of \"" +
+ GetCommandName() + "\". Valid subcommands are " +
+ GetTopSubcommands(/*count=*/5) + ". Use \"help " +
+ GetCommandName() + "\" to find out more.")
+ .str());
}
error_msg.append("\n");
result.AppendRawError(error_msg.c_str());
}
+std::string CommandObjectMultiword::GetTopSubcommands(int count) {
+ if (m_subcommand_dict.empty())
+ return "<NONE>";
+ std::string buffer = "{";
+ CommandMap::iterator pos;
+ for (pos = m_subcommand_dict.begin();
+ pos != m_subcommand_dict.end() && count > 0; ++pos, --count) {
+ buffer.append("'");
+ buffer.append(pos->first);
+ buffer.append("',");
+ }
+ buffer.append("...}");
+ return buffer;
+}
+
void CommandObjectMultiword::GenerateHelpText(Stream &output_stream) {
// First time through here, generate the help text for the object and push it
// to the return result object as well
diff --git a/lldb/test/Shell/Commands/command-breakpoint-col.test b/lldb/test/Shell/Commands/command-breakpoint-col.test
index 65c1e220794303..fecb773d2b4c66 100644
--- a/lldb/test/Shell/Commands/command-breakpoint-col.test
+++ b/lldb/test/Shell/Commands/command-breakpoint-col.test
@@ -3,8 +3,10 @@
# RUN: %clang_host -g -O0 %S/Inputs/main.c -o %t.out
# RUN: %lldb -b -o 'help breakpoint set' -o 'breakpoint set -f main.c -l 2 -u 21' %t.out | FileCheck %s --check-prefix HELP --check-prefix CHECK
# RUN: %lldb -b -o 'help _regexp-break' -o 'b main.c:2:21' %t.out | FileCheck %s --check-prefix HELP-REGEX --check-prefix CHECK
+# RUN: not %lldb -b -o 'breakpoint foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix ERROR-MSG
# HELP: -u <column> ( --column <column> )
# HELP: Specifies the column number on which to set this breakpoint.
# HELP-REGEX: _regexp-break <filename>:<linenum>:<colnum>
# HELP-REGEX: main.c:12:21{{.*}}Break at line 12 and column 21 of main.c
# CHECK: at main.c:2:21
+# ERROR-MSG: 'foo' is not a valid subcommand of "breakpoint". Valid subcommands are {'clear','command','delete','disable','enable',...}. Use "help breakpoint" to find out more.
|
std::string buffer = "{"; | ||
CommandMap::iterator pos; | ||
for (pos = m_subcommand_dict.begin(); | ||
pos != m_subcommand_dict.end() && count > 0; ++pos, --count) { | ||
buffer.append("'"); | ||
buffer.append(pos->first); | ||
buffer.append("',"); | ||
} | ||
buffer.append("...}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this return something that feels more like natural language? By that I mean dropping the opening and closing braces, adding a space after the comma and maybe even dropping the single quotes.
IIUC, this will also always print ...
even if there are say two subcommands and count was 3, which seems needlessly confusing. We should only append this if there are actually more commands that weren't printed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The programmer in me really wants to make this representation unambiguous, but I think Jonas is right -- the quotes and braces especially would just be noise. Instead of ...
, maybe put "and others" or something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -70,6 +70,8 @@ class CommandObjectMultiword : public CommandObject { | |||
return m_subcommand_dict; | |||
} | |||
|
|||
std::string GetTopSubcommands(int count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call this GetSubcommandsUpTo(int count) or something like that. "TopSubcommands" sounds like you are returning the most popular ones.
Also, maybe size_t not int since this can't be negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely a topic for another patch, but I actually think it would be really cool if this actually returned the first N most popular commands. :) Then we could even have help breakpoint
start off with the most popular subcommand (definitely set
) instead of clear
(which I don't think I've ever used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will rename this to Jim's suggestion.
I like Pavel's idea of having this returning the popular ones, but not sure how we'd determine that here (other than hard-coding the list somewhere)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would have to be really clear that's what we were doing, however. If you saw this list come out, then were curious and did help breakpoint
and the list came out in a different order that would just look like we were being weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was basically thinking just some predetermined order (our estimate of a command usage). I don't want to derail this patch with it though, I just found the remark humorous and couldn't resist the temptation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P.S: Actually, didn't end up renaming it Jim's suggestion, but to something else, hopefully less confusing, which is "GetSubcommandsHint" - whether it returns a top 5 sub-commands or whatever order is transparent to caller.
(Then we can change the content in later patch as needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that's fine too.
@@ -70,6 +70,8 @@ class CommandObjectMultiword : public CommandObject { | |||
return m_subcommand_dict; | |||
} | |||
|
|||
std::string GetTopSubcommands(int count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely a topic for another patch, but I actually think it would be really cool if this actually returned the first N most popular commands. :) Then we could even have help breakpoint
start off with the most popular subcommand (definitely set
) instead of clear
(which I don't think I've ever used).
std::string buffer = "{"; | ||
CommandMap::iterator pos; | ||
for (pos = m_subcommand_dict.begin(); | ||
pos != m_subcommand_dict.end() && count > 0; ++pos, --count) { | ||
buffer.append("'"); | ||
buffer.append(pos->first); | ||
buffer.append("',"); | ||
} | ||
buffer.append("...}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The programmer in me really wants to make this representation unambiguous, but I think Jonas is right -- the quotes and braces especially would just be noise. Instead of ...
, maybe put "and others" or something like that.
// Rather than simply complaining about the invalid (sub) command, | ||
// try to offer some alternatives. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is unnecessarily judgemental. Just say what you're doing (offering alternatives) and why (to help the user).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
70ead3e
to
4493bf0
Compare
const size_t maxCount = 5; | ||
size_t i = 0; | ||
std::string buffer = " Valid subcommand"; | ||
buffer.append(m_subcommand_dict.size() > 1 ? "s are:" : "is"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're missing a space before "is". The output will also be garbled for the case of zero subcommands (I think such a thing can happen if the user registers a custom multiword command (SBCommandInterpreter::AddMultiwordCommand
), but does not add any subcommands to it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're missing a space before "is"
Done.
The output will also be garbled for the case of zero subcommands
Which output? the error msg as a whole or just this function's output?
unless i'm missing something, if there are zero subcommands, the function would have returned "" on line 223, which means the error message would have been something like "foo" is not a valid subcommand of "breakpoint". Use "help breakpoint" to find out more.
(ie., there will be no suggestions on subcommands).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I missed the empty()
check at the beginning of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I missed the empty()
check at the beginning of the function.
# RUN: not %lldb -b -o 'breakpoint foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix BP-MSG | ||
# RUN: not %lldb -b -o 'watchpoint set foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix WP-MSG | ||
# CHECK: at main.c:2:21 | ||
# BP-MSG: 'foo' is not a valid subcommand of "breakpoint". Valid subcommands are: clear, command, delete, disable, enable, and others. Use "help breakpoint" to find out more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'foo' is not a valid subcommand of "breakpoint"
It would be nice if we could be consistent about the kind of quotes we use at least within a single sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -70,6 +70,8 @@ class CommandObjectMultiword : public CommandObject { | |||
return m_subcommand_dict; | |||
} | |||
|
|||
std::string GetTopSubcommands(int count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was basically thinking just some predetermined order (our estimate of a command usage). I don't want to derail this patch with it though, I just found the remark humorous and couldn't resist the temptation.
Can I merge this? Or would anyone else like to have another look/comments? Thanks! |
Sometimes users (esp. gdb-longtime users) accidentally use GDB syntax, such as `breakpoint foo`, and they would get an error message from LLDB saying simply `Invalid command "breakpoint foo"`, which is not very helpful. This change provides additional suggestions to help correcting the mistake.
Sometimes users (esp. gdb-longtime users) accidentally use GDB syntax, such as
breakpoint foo
, and they would get an error message from LLDB saying simplyInvalid command "breakpoint foo"
, which is not very helpful.This change provides additional suggestions to help correcting the mistake.