Skip to content

Commit 8334d2b

Browse files
authored
[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 <[email protected]>
1 parent 6a48297 commit 8334d2b

File tree

6 files changed

+116
-20
lines changed

6 files changed

+116
-20
lines changed

lldb/docs/use/tutorial.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ is more convenient to make the basic commands unique down to a letter or two,
168168
and then learn these sequences than to fill the namespace with lots of aliases,
169169
and then have to type them all the way out.
170170

171+
If the alias abbreviation or the full alias command collides with another
172+
existing command, the command resolver will prefer to use the alias over any
173+
other command as far as there is only one alias command match.
174+
171175
However, users are free to customize LLDB's command set however they like, and
172176
since LLDB reads the file ``~/.lldbinit`` at startup, you can store all your
173177
aliases there and they will be generally available to you. Your aliases are

lldb/include/lldb/Interpreter/CommandInterpreter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ class CommandInterpreter : public Broadcaster,
295295
StringList *matches = nullptr,
296296
StringList *descriptions = nullptr) const;
297297

298+
CommandObject *
299+
GetAliasCommandObject(llvm::StringRef cmd, StringList *matches = nullptr,
300+
StringList *descriptions = nullptr) const;
301+
298302
/// Determine whether a root level, built-in command with this name exists.
299303
bool CommandExists(llvm::StringRef cmd) const;
300304

lldb/source/Commands/CommandObjectCommands.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,13 @@ rather than using a positional placeholder:"
322322
323323
(lldb) command alias bl3 breakpoint set -f %1 -l 3
324324
325-
Always sets a breakpoint on line 3 of whatever file is indicated.)");
325+
Always sets a breakpoint on line 3 of whatever file is indicated.
326+
327+
)"
328+
329+
"If the alias abbreviation or the full alias command collides with another \
330+
existing command, the command resolver will prefer to use the alias over any \
331+
other command as far as there is only one alias command match.");
326332

327333
CommandArgumentEntry arg1;
328334
CommandArgumentEntry arg2;

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -520,10 +520,6 @@ void CommandInterpreter::Initialize() {
520520

521521
cmd_obj_sp = GetCommandSPExact("scripting run");
522522
if (cmd_obj_sp) {
523-
AddAlias("sc", cmd_obj_sp);
524-
AddAlias("scr", cmd_obj_sp);
525-
AddAlias("scri", cmd_obj_sp);
526-
AddAlias("scrip", cmd_obj_sp);
527523
AddAlias("script", cmd_obj_sp);
528524
}
529525

@@ -1302,6 +1298,39 @@ CommandObject *CommandInterpreter::GetUserCommandObject(
13021298
return {};
13031299
}
13041300

1301+
CommandObject *CommandInterpreter::GetAliasCommandObject(
1302+
llvm::StringRef cmd, StringList *matches, StringList *descriptions) const {
1303+
auto find_exact =
1304+
[&](const CommandObject::CommandMap &map) -> CommandObject * {
1305+
auto found_elem = map.find(cmd.str());
1306+
if (found_elem == map.end())
1307+
return (CommandObject *)nullptr;
1308+
CommandObject *exact_cmd = found_elem->second.get();
1309+
if (!exact_cmd)
1310+
return nullptr;
1311+
1312+
if (matches)
1313+
matches->AppendString(exact_cmd->GetCommandName());
1314+
1315+
if (descriptions)
1316+
descriptions->AppendString(exact_cmd->GetHelp());
1317+
1318+
return exact_cmd;
1319+
return nullptr;
1320+
};
1321+
1322+
CommandObject *exact_cmd = find_exact(GetAliases());
1323+
if (exact_cmd)
1324+
return exact_cmd;
1325+
1326+
// We didn't have an exact command, so now look for partial matches.
1327+
StringList tmp_list;
1328+
StringList *matches_ptr = matches ? matches : &tmp_list;
1329+
AddNamesMatchingPartialString(GetAliases(), cmd, *matches_ptr);
1330+
1331+
return {};
1332+
}
1333+
13051334
bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const {
13061335
return m_command_dict.find(std::string(cmd)) != m_command_dict.end();
13071336
}
@@ -3421,6 +3450,19 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
34213450
std::string next_word;
34223451
StringList matches;
34233452
bool done = false;
3453+
3454+
auto build_alias_cmd = [&](std::string &full_name) {
3455+
revised_command_line.Clear();
3456+
matches.Clear();
3457+
std::string alias_result;
3458+
cmd_obj =
3459+
BuildAliasResult(full_name, scratch_command, alias_result, result);
3460+
revised_command_line.Printf("%s", alias_result.c_str());
3461+
if (cmd_obj) {
3462+
wants_raw_input = cmd_obj->WantsRawCommandString();
3463+
}
3464+
};
3465+
34243466
while (!done) {
34253467
char quote_char = '\0';
34263468
std::string suffix;
@@ -3432,14 +3474,7 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
34323474
bool is_real_command =
34333475
(!is_alias) || (cmd_obj != nullptr && !cmd_obj->IsAlias());
34343476
if (!is_real_command) {
3435-
matches.Clear();
3436-
std::string alias_result;
3437-
cmd_obj =
3438-
BuildAliasResult(full_name, scratch_command, alias_result, result);
3439-
revised_command_line.Printf("%s", alias_result.c_str());
3440-
if (cmd_obj) {
3441-
wants_raw_input = cmd_obj->WantsRawCommandString();
3442-
}
3477+
build_alias_cmd(full_name);
34433478
} else {
34443479
if (cmd_obj) {
34453480
llvm::StringRef cmd_name = cmd_obj->GetCommandName();
@@ -3486,21 +3521,32 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
34863521
if (cmd_obj == nullptr) {
34873522
const size_t num_matches = matches.GetSize();
34883523
if (matches.GetSize() > 1) {
3489-
StreamString error_msg;
3490-
error_msg.Printf("Ambiguous command '%s'. Possible matches:\n",
3491-
next_word.c_str());
3524+
StringList alias_matches;
3525+
GetAliasCommandObject(next_word, &alias_matches);
3526+
3527+
if (alias_matches.GetSize() == 1) {
3528+
std::string full_name;
3529+
GetAliasFullName(alias_matches.GetStringAtIndex(0), full_name);
3530+
build_alias_cmd(full_name);
3531+
done = static_cast<bool>(cmd_obj);
3532+
} else {
3533+
StreamString error_msg;
3534+
error_msg.Printf("Ambiguous command '%s'. Possible matches:\n",
3535+
next_word.c_str());
34923536

3493-
for (uint32_t i = 0; i < num_matches; ++i) {
3494-
error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i));
3537+
for (uint32_t i = 0; i < num_matches; ++i) {
3538+
error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i));
3539+
}
3540+
result.AppendRawError(error_msg.GetString());
34953541
}
3496-
result.AppendRawError(error_msg.GetString());
34973542
} else {
34983543
// We didn't have only one match, otherwise we wouldn't get here.
34993544
lldbassert(num_matches == 0);
35003545
result.AppendErrorWithFormat("'%s' is not a valid command.\n",
35013546
next_word.c_str());
35023547
}
3503-
return nullptr;
3548+
if (!done)
3549+
return nullptr;
35043550
}
35053551

35063552
if (cmd_obj->IsMultiwordObject()) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Test how lldb reacts to ambiguous commands
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
class AmbiguousCommandTestCase(TestBase):
12+
@no_debug_info_test
13+
def test_ambiguous_command_with_alias(self):
14+
command_interpreter = self.dbg.GetCommandInterpreter()
15+
self.assertTrue(command_interpreter, VALID_COMMAND_INTERPRETER)
16+
result = lldb.SBCommandReturnObject()
17+
18+
command_interpreter.HandleCommand(
19+
"command alias corefile target create -c %0", result
20+
)
21+
self.assertTrue(result.Succeeded())
22+
23+
command_interpreter.ResolveCommand("co", result)
24+
self.assertFalse(result.Succeeded())
25+
self.assertEqual(
26+
result.GetError(),
27+
"Ambiguous command 'co'. Possible matches:\n\tcommand\n\tcontinue\n\tcorefile\n",
28+
)
29+
30+
command_interpreter.HandleCommand("command unalias continue", result)
31+
self.assertTrue(result.Succeeded())
32+
33+
command_interpreter.ResolveCommand("co", result)
34+
self.assertTrue(result.Succeeded())
35+
self.assertEqual(result.GetOutput(), "target create -c %0")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cmdline

0 commit comments

Comments
 (0)