https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/101934
>From b2061b3d60ceb43b8bbd57dd4fb56eacd834d644 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <ism...@bennani.ma> Date: Thu, 8 Aug 2024 00:45:54 -0700 Subject: [PATCH] [lldb/Interpreter] Fix ambiguous partial command resolution 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 <ism...@bennani.ma> --- lldb/docs/use/tutorial.rst | 4 + .../lldb/Interpreter/CommandInterpreter.h | 4 + .../source/Commands/CommandObjectCommands.cpp | 8 +- .../source/Interpreter/CommandInterpreter.cpp | 84 ++++++++++++++----- .../TestAmbiguousCommands.py | 35 ++++++++ .../ambigous_commands/categories | 1 + 6 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py create mode 100644 lldb/test/API/functionalities/ambigous_commands/categories diff --git a/lldb/docs/use/tutorial.rst b/lldb/docs/use/tutorial.rst index 22354c6720e14a..00e7befdd087a4 100644 --- a/lldb/docs/use/tutorial.rst +++ b/lldb/docs/use/tutorial.rst @@ -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 diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 48f6618ab0e392..2bafc30cc8e23a 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -295,6 +295,10 @@ class CommandInterpreter : public Broadcaster, 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; diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index c63445b7c8c868..7c439f4ddb93e3 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -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; diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index fc07168b6c0ac1..f3cabbd28e976e 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -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); + + 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)); + 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()) { diff --git a/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py b/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py new file mode 100644 index 00000000000000..14c66fefea7efd --- /dev/null +++ b/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py @@ -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") diff --git a/lldb/test/API/functionalities/ambigous_commands/categories b/lldb/test/API/functionalities/ambigous_commands/categories new file mode 100644 index 00000000000000..3a3f4df6416b9c --- /dev/null +++ b/lldb/test/API/functionalities/ambigous_commands/categories @@ -0,0 +1 @@ +cmdline _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits