[lldb/Interpreter] Fix ambiguous partial command resolution (#101934)

This patch is a follow-up to #97263 that fix ambigous abbreviated
command resolution.

When multiple commands are resolved, instead of failing to pick a
command to
run, this patch changes to resolution logic to check if there is a
single
alias match and if so, it will run the alias instead of the other
matches.

This has as a side-effect that we don't need to make aliases for every
substring of aliases to support abbrivated alias resolution.

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
This commit is contained in:
Med Ismail Bennani
2024-08-08 12:55:10 -07:00
committed by GitHub
parent 6a482972e0
commit 8334d2bfd3
6 changed files with 116 additions and 20 deletions

View File

@@ -168,6 +168,10 @@ is more convenient to make the basic commands unique down to a letter or two,
and then learn these sequences than to fill the namespace with lots of aliases,
and then have to type them all the way out.
If the alias abbreviation or the full alias command collides with another
existing command, the command resolver will prefer to use the alias over any
other command as far as there is only one alias command match.
However, users are free to customize LLDB's command set however they like, and
since LLDB reads the file ``~/.lldbinit`` at startup, you can store all your
aliases there and they will be generally available to you. Your aliases are

View File

@@ -295,6 +295,10 @@ public:
StringList *matches = nullptr,
StringList *descriptions = nullptr) const;
CommandObject *
GetAliasCommandObject(llvm::StringRef cmd, StringList *matches = nullptr,
StringList *descriptions = nullptr) const;
/// Determine whether a root level, built-in command with this name exists.
bool CommandExists(llvm::StringRef cmd) const;

View File

@@ -322,7 +322,13 @@ rather than using a positional placeholder:"
(lldb) command alias bl3 breakpoint set -f %1 -l 3
Always sets a breakpoint on line 3 of whatever file is indicated.)");
Always sets a breakpoint on line 3 of whatever file is indicated.
)"
"If the alias abbreviation or the full alias command collides with another \
existing command, the command resolver will prefer to use the alias over any \
other command as far as there is only one alias command match.");
CommandArgumentEntry arg1;
CommandArgumentEntry arg2;

View File

@@ -520,10 +520,6 @@ void CommandInterpreter::Initialize() {
cmd_obj_sp = GetCommandSPExact("scripting run");
if (cmd_obj_sp) {
AddAlias("sc", cmd_obj_sp);
AddAlias("scr", cmd_obj_sp);
AddAlias("scri", cmd_obj_sp);
AddAlias("scrip", cmd_obj_sp);
AddAlias("script", cmd_obj_sp);
}
@@ -1302,6 +1298,39 @@ CommandObject *CommandInterpreter::GetUserCommandObject(
return {};
}
CommandObject *CommandInterpreter::GetAliasCommandObject(
llvm::StringRef cmd, StringList *matches, StringList *descriptions) const {
auto find_exact =
[&](const CommandObject::CommandMap &map) -> CommandObject * {
auto found_elem = map.find(cmd.str());
if (found_elem == map.end())
return (CommandObject *)nullptr;
CommandObject *exact_cmd = found_elem->second.get();
if (!exact_cmd)
return nullptr;
if (matches)
matches->AppendString(exact_cmd->GetCommandName());
if (descriptions)
descriptions->AppendString(exact_cmd->GetHelp());
return exact_cmd;
return nullptr;
};
CommandObject *exact_cmd = find_exact(GetAliases());
if (exact_cmd)
return exact_cmd;
// We didn't have an exact command, so now look for partial matches.
StringList tmp_list;
StringList *matches_ptr = matches ? matches : &tmp_list;
AddNamesMatchingPartialString(GetAliases(), cmd, *matches_ptr);
return {};
}
bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const {
return m_command_dict.find(std::string(cmd)) != m_command_dict.end();
}
@@ -3421,6 +3450,19 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
std::string next_word;
StringList matches;
bool done = false;
auto build_alias_cmd = [&](std::string &full_name) {
revised_command_line.Clear();
matches.Clear();
std::string alias_result;
cmd_obj =
BuildAliasResult(full_name, scratch_command, alias_result, result);
revised_command_line.Printf("%s", alias_result.c_str());
if (cmd_obj) {
wants_raw_input = cmd_obj->WantsRawCommandString();
}
};
while (!done) {
char quote_char = '\0';
std::string suffix;
@@ -3432,14 +3474,7 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
bool is_real_command =
(!is_alias) || (cmd_obj != nullptr && !cmd_obj->IsAlias());
if (!is_real_command) {
matches.Clear();
std::string alias_result;
cmd_obj =
BuildAliasResult(full_name, scratch_command, alias_result, result);
revised_command_line.Printf("%s", alias_result.c_str());
if (cmd_obj) {
wants_raw_input = cmd_obj->WantsRawCommandString();
}
build_alias_cmd(full_name);
} else {
if (cmd_obj) {
llvm::StringRef cmd_name = cmd_obj->GetCommandName();
@@ -3486,21 +3521,32 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
if (cmd_obj == nullptr) {
const size_t num_matches = matches.GetSize();
if (matches.GetSize() > 1) {
StreamString error_msg;
error_msg.Printf("Ambiguous command '%s'. Possible matches:\n",
next_word.c_str());
StringList alias_matches;
GetAliasCommandObject(next_word, &alias_matches);
for (uint32_t i = 0; i < num_matches; ++i) {
error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i));
if (alias_matches.GetSize() == 1) {
std::string full_name;
GetAliasFullName(alias_matches.GetStringAtIndex(0), full_name);
build_alias_cmd(full_name);
done = static_cast<bool>(cmd_obj);
} else {
StreamString error_msg;
error_msg.Printf("Ambiguous command '%s'. Possible matches:\n",
next_word.c_str());
for (uint32_t i = 0; i < num_matches; ++i) {
error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i));
}
result.AppendRawError(error_msg.GetString());
}
result.AppendRawError(error_msg.GetString());
} else {
// We didn't have only one match, otherwise we wouldn't get here.
lldbassert(num_matches == 0);
result.AppendErrorWithFormat("'%s' is not a valid command.\n",
next_word.c_str());
}
return nullptr;
if (!done)
return nullptr;
}
if (cmd_obj->IsMultiwordObject()) {

View File

@@ -0,0 +1,35 @@
"""
Test how lldb reacts to ambiguous commands
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class AmbiguousCommandTestCase(TestBase):
@no_debug_info_test
def test_ambiguous_command_with_alias(self):
command_interpreter = self.dbg.GetCommandInterpreter()
self.assertTrue(command_interpreter, VALID_COMMAND_INTERPRETER)
result = lldb.SBCommandReturnObject()
command_interpreter.HandleCommand(
"command alias corefile target create -c %0", result
)
self.assertTrue(result.Succeeded())
command_interpreter.ResolveCommand("co", result)
self.assertFalse(result.Succeeded())
self.assertEqual(
result.GetError(),
"Ambiguous command 'co'. Possible matches:\n\tcommand\n\tcontinue\n\tcorefile\n",
)
command_interpreter.HandleCommand("command unalias continue", result)
self.assertTrue(result.Succeeded())
command_interpreter.ResolveCommand("co", result)
self.assertTrue(result.Succeeded())
self.assertEqual(result.GetOutput(), "target create -c %0")

View File

@@ -0,0 +1 @@
cmdline