https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/136766
>From 94caf0b58ace58ae5159e3819f776ad6b2988329 Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Tue, 22 Apr 2025 13:58:25 -0700 Subject: [PATCH 1/7] [lldb] Expose language plugin commands based based on language of current frame --- .../lldb/Interpreter/CommandInterpreter.h | 6 ++ .../source/Interpreter/CommandInterpreter.cpp | 55 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 724d88d65f6ac..26e0767951e7f 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -730,6 +730,12 @@ class CommandInterpreter : public Broadcaster, bool EchoCommandNonInteractive(llvm::StringRef line, const Flags &io_handler_flags) const; + /// Return the language specific command object for the current frame. + /// + /// For example, when stopped on a C++ frame, this returns the command object + /// for "language cplusplus" (`CommandObjectMultiwordItaniumABI`). + lldb::CommandObjectSP GetFrameLanguageCommand() const; + // A very simple state machine which models the command handling transitions enum class CommandHandlingState { eIdle, diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index eb4741feb0aa5..2ff02ae5086b4 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1018,6 +1018,26 @@ CommandInterpreter::VerifyUserMultiwordCmdPath(Args &path, bool leaf_is_command, return cur_as_multi; } +CommandObjectSP CommandInterpreter::GetFrameLanguageCommand() const { + if (auto frame_sp = GetExecutionContext().GetFrameSP()) { + auto frame_language = Language::GetPrimaryLanguage( + frame_sp->GuessLanguage().AsLanguageType()); + + auto it = m_command_dict.find("language"); + if (it != m_command_dict.end()) { + // The root "language" command. + CommandObjectSP language_cmd_sp = it->second; + + if (auto *plugin = Language::FindPlugin(frame_language)) { + // "cplusplus", "objc", etc. + auto lang_name = plugin->GetPluginName(); + return language_cmd_sp->GetSubcommandSPExact(lang_name); + } + } + } + return {}; +} + CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, bool exact, StringList *matches, @@ -1050,11 +1070,20 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, command_sp = pos->second; } + // The `language` subcommand ("language objc", "language cplusplus", etc). + CommandObjectMultiword *lang_subcmd = nullptr; + if (!command_sp) { + if (auto subcmd_sp = GetFrameLanguageCommand()) { + lang_subcmd = subcmd_sp->GetAsMultiwordCommand(); + command_sp = subcmd_sp->GetSubcommandSPExact(cmd_str); + } + } + if (!exact && !command_sp) { // We will only get into here if we didn't find any exact matches. CommandObjectSP user_match_sp, user_mw_match_sp, alias_match_sp, - real_match_sp; + real_match_sp, lang_match_sp; StringList local_matches; if (matches == nullptr) @@ -1064,6 +1093,7 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, unsigned int num_alias_matches = 0; unsigned int num_user_matches = 0; unsigned int num_user_mw_matches = 0; + unsigned int num_lang_matches = 0; // Look through the command dictionaries one by one, and if we get only one // match from any of them in toto, then return that, otherwise return an @@ -1121,11 +1151,28 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, user_mw_match_sp = pos->second; } + if (lang_subcmd) { + num_lang_matches = + AddNamesMatchingPartialString(lang_subcmd->GetSubcommandDictionary(), + cmd_str, *matches, descriptions); + } + + if (num_lang_matches == 1) { + cmd.assign(matches->GetStringAtIndex(num_cmd_matches + num_alias_matches + + num_user_matches + + num_user_mw_matches)); + + auto &lang_dict = lang_subcmd->GetSubcommandDictionary(); + auto pos = lang_dict.find(cmd); + if (pos != lang_dict.end()) + lang_match_sp = pos->second; + } + // If we got exactly one match, return that, otherwise return the match // list. if (num_user_matches + num_user_mw_matches + num_cmd_matches + - num_alias_matches == + num_alias_matches + num_lang_matches == 1) { if (num_cmd_matches) return real_match_sp; @@ -1133,8 +1180,10 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, return alias_match_sp; else if (num_user_mw_matches) return user_mw_match_sp; - else + else if (num_user_matches) return user_match_sp; + else + return lang_match_sp; } } else if (matches && command_sp) { matches->AppendString(cmd_str); >From dcc95c1c17e6c2a50002fe90d6420ca270efeb68 Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Wed, 23 Apr 2025 13:04:27 -0700 Subject: [PATCH 2/7] Add test --- .../API/commands/command/language/Makefile | 3 ++ .../language/TestFrameLanguageCommands.py | 43 +++++++++++++++++++ .../API/commands/command/language/lib.cpp | 3 ++ .../API/commands/command/language/main.mm | 6 +++ 4 files changed, 55 insertions(+) create mode 100644 lldb/test/API/commands/command/language/Makefile create mode 100644 lldb/test/API/commands/command/language/TestFrameLanguageCommands.py create mode 100644 lldb/test/API/commands/command/language/lib.cpp create mode 100644 lldb/test/API/commands/command/language/main.mm diff --git a/lldb/test/API/commands/command/language/Makefile b/lldb/test/API/commands/command/language/Makefile new file mode 100644 index 0000000000000..ce845d59ac035 --- /dev/null +++ b/lldb/test/API/commands/command/language/Makefile @@ -0,0 +1,3 @@ +OBJCXX_SOURCES := main.mm +CXX_SOURCES := lib.cpp +include Makefile.rules diff --git a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py new file mode 100644 index 0000000000000..89439856470a7 --- /dev/null +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -0,0 +1,43 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil + + +class TestCase(TestBase): + def test(self): + self.build() + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("lib.cpp") + ) + + frame = thread.selected_frame + self.assertEqual(frame.GuessLanguage(), lldb.eLanguageTypeC_plus_plus_11) + self.assertEqual(frame.name, "f()") + self.expect( + "help demangle", + substrs=[ + "Demangle a C++ mangled name.", + "Syntax: language cplusplus demangle [<mangled-name> ...]", + ], + ) + self.expect("demangle _Z1fv", startstr="_Z1fv ---> f()") + + # Switch the objc caller. + self.runCmd("up") + frame = thread.selected_frame + self.assertEqual(frame.GuessLanguage(), lldb.eLanguageTypeObjC_plus_plus) + self.assertEqual(frame.name, "main") + self.expect("help demangle", error=True) + self.expect( + "help tagged-pointer", + substrs=[ + "Commands for operating on Objective-C tagged pointers.", + "Syntax: class-table <subcommand> [<subcommand-options>]", + ], + ) + self.expect( + "tagged-pointer info 0", + error=True, + startstr="error: could not convert '0' to a valid address", + ) diff --git a/lldb/test/API/commands/command/language/lib.cpp b/lldb/test/API/commands/command/language/lib.cpp new file mode 100644 index 0000000000000..225d2992d36d2 --- /dev/null +++ b/lldb/test/API/commands/command/language/lib.cpp @@ -0,0 +1,3 @@ +#include <stdio.h> +extern void f(); +void f() { puts("break here"); } diff --git a/lldb/test/API/commands/command/language/main.mm b/lldb/test/API/commands/command/language/main.mm new file mode 100644 index 0000000000000..93b87eb4d3176 --- /dev/null +++ b/lldb/test/API/commands/command/language/main.mm @@ -0,0 +1,6 @@ +extern void f(); + +int main() { + f(); + return 0; +} >From 40691abf97d122182c6e9ea13d38ffe96e2631bf Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Wed, 23 Apr 2025 13:33:16 -0700 Subject: [PATCH 3/7] Check prefix matching in the test --- .../language/TestFrameLanguageCommands.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py index 89439856470a7..5a4c62c5721bb 100644 --- a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -14,6 +14,8 @@ def test(self): frame = thread.selected_frame self.assertEqual(frame.GuessLanguage(), lldb.eLanguageTypeC_plus_plus_11) self.assertEqual(frame.name, "f()") + + # Test `help`. self.expect( "help demangle", substrs=[ @@ -21,21 +23,21 @@ def test(self): "Syntax: language cplusplus demangle [<mangled-name> ...]", ], ) - self.expect("demangle _Z1fv", startstr="_Z1fv ---> f()") - # Switch the objc caller. + # Run a `language cplusplus` command. + self.expect(f"demangle _Z1fv", startstr="_Z1fv ---> f()") + # Test prefix matching. + self.expect("dem _Z1fv", startstr="_Z1fv ---> f()") + + # Select the objc caller. self.runCmd("up") frame = thread.selected_frame self.assertEqual(frame.GuessLanguage(), lldb.eLanguageTypeObjC_plus_plus) self.assertEqual(frame.name, "main") + + # Ensure `demangle` doesn't resolve from the objc frame. self.expect("help demangle", error=True) - self.expect( - "help tagged-pointer", - substrs=[ - "Commands for operating on Objective-C tagged pointers.", - "Syntax: class-table <subcommand> [<subcommand-options>]", - ], - ) + # Run a `language objc` command. self.expect( "tagged-pointer info 0", error=True, >From 25585ccde84122f742abe987d45be29e4f2959a5 Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Wed, 23 Apr 2025 16:10:35 -0700 Subject: [PATCH 4/7] Change Makefile to (hopefully) load the objc runtime --- lldb/test/API/commands/command/language/Makefile | 2 ++ .../API/commands/command/language/TestFrameLanguageCommands.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lldb/test/API/commands/command/language/Makefile b/lldb/test/API/commands/command/language/Makefile index ce845d59ac035..48d511771b0a6 100644 --- a/lldb/test/API/commands/command/language/Makefile +++ b/lldb/test/API/commands/command/language/Makefile @@ -1,3 +1,5 @@ OBJCXX_SOURCES := main.mm +CFLAGS_EXTRAS := -fobjc-arc CXX_SOURCES := lib.cpp +LD_EXTRAS := -lobjc include Makefile.rules diff --git a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py index 5a4c62c5721bb..3936f06abeb80 100644 --- a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -25,7 +25,7 @@ def test(self): ) # Run a `language cplusplus` command. - self.expect(f"demangle _Z1fv", startstr="_Z1fv ---> f()") + self.expect("demangle _Z1fv", startstr="_Z1fv ---> f()") # Test prefix matching. self.expect("dem _Z1fv", startstr="_Z1fv ---> f()") >From 72f43cc2a728ca18aa166f222565182a53275fd7 Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Thu, 24 Apr 2025 10:11:24 -0700 Subject: [PATCH 5/7] Remove -fobjc-arc (not supported on linux) --- lldb/test/API/commands/command/language/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/lldb/test/API/commands/command/language/Makefile b/lldb/test/API/commands/command/language/Makefile index 48d511771b0a6..2d5049417ee70 100644 --- a/lldb/test/API/commands/command/language/Makefile +++ b/lldb/test/API/commands/command/language/Makefile @@ -1,5 +1,4 @@ OBJCXX_SOURCES := main.mm -CFLAGS_EXTRAS := -fobjc-arc CXX_SOURCES := lib.cpp LD_EXTRAS := -lobjc include Makefile.rules >From 4546da047b89ad5ee3b736fdc90e760dc346d9d9 Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Thu, 24 Apr 2025 14:24:28 -0700 Subject: [PATCH 6/7] Fix test for linux --- .../command/language/TestFrameLanguageCommands.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py index 3936f06abeb80..f11eb8a472939 100644 --- a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -37,9 +37,14 @@ def test(self): # Ensure `demangle` doesn't resolve from the objc frame. self.expect("help demangle", error=True) + # Run a `language objc` command. self.expect( - "tagged-pointer info 0", - error=True, - startstr="error: could not convert '0' to a valid address", + "tagged-pointer", + substrs=[ + "Commands for operating on Objective-C tagged pointers.", + "Syntax: tagged-pointer <subcommand> [<subcommand-options>]", + "The following subcommands are supported:", + "info -- Dump information on a tagged pointer.", + ], ) >From 722390b8b65f9236cc7da69cd1c4855eb884fbbc Mon Sep 17 00:00:00 2001 From: Dave Lee <davelee....@gmail.com> Date: Thu, 1 May 2025 12:27:01 -0700 Subject: [PATCH 7/7] Change logic to ensure compatibility --- .../source/Interpreter/CommandInterpreter.cpp | 100 +++++++++--------- .../language/TestFrameLanguageCommands.py | 12 ++- .../API/commands/command/language/commands.py | 5 + 3 files changed, 66 insertions(+), 51 deletions(-) create mode 100644 lldb/test/API/commands/command/language/commands.py diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 2ff02ae5086b4..ed85ac25b0370 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1019,23 +1019,25 @@ CommandInterpreter::VerifyUserMultiwordCmdPath(Args &path, bool leaf_is_command, } CommandObjectSP CommandInterpreter::GetFrameLanguageCommand() const { - if (auto frame_sp = GetExecutionContext().GetFrameSP()) { - auto frame_language = Language::GetPrimaryLanguage( - frame_sp->GuessLanguage().AsLanguageType()); - - auto it = m_command_dict.find("language"); - if (it != m_command_dict.end()) { - // The root "language" command. - CommandObjectSP language_cmd_sp = it->second; - - if (auto *plugin = Language::FindPlugin(frame_language)) { - // "cplusplus", "objc", etc. - auto lang_name = plugin->GetPluginName(); - return language_cmd_sp->GetSubcommandSPExact(lang_name); - } - } - } - return {}; + auto frame_sp = GetExecutionContext().GetFrameSP(); + if (!frame_sp) + return {}; + auto frame_language = + Language::GetPrimaryLanguage(frame_sp->GuessLanguage().AsLanguageType()); + + auto it = m_command_dict.find("language"); + if (it == m_command_dict.end()) + return {}; + // The root "language" command. + CommandObjectSP language_cmd_sp = it->second; + + auto *plugin = Language::FindPlugin(frame_language); + if (!plugin) + return {}; + // "cplusplus", "objc", etc. + auto lang_name = plugin->GetPluginName(); + + return language_cmd_sp->GetSubcommandSPExact(lang_name); } CommandObjectSP @@ -1070,20 +1072,11 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, command_sp = pos->second; } - // The `language` subcommand ("language objc", "language cplusplus", etc). - CommandObjectMultiword *lang_subcmd = nullptr; - if (!command_sp) { - if (auto subcmd_sp = GetFrameLanguageCommand()) { - lang_subcmd = subcmd_sp->GetAsMultiwordCommand(); - command_sp = subcmd_sp->GetSubcommandSPExact(cmd_str); - } - } - if (!exact && !command_sp) { // We will only get into here if we didn't find any exact matches. CommandObjectSP user_match_sp, user_mw_match_sp, alias_match_sp, - real_match_sp, lang_match_sp; + real_match_sp; StringList local_matches; if (matches == nullptr) @@ -1093,7 +1086,6 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, unsigned int num_alias_matches = 0; unsigned int num_user_matches = 0; unsigned int num_user_mw_matches = 0; - unsigned int num_lang_matches = 0; // Look through the command dictionaries one by one, and if we get only one // match from any of them in toto, then return that, otherwise return an @@ -1151,28 +1143,11 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, user_mw_match_sp = pos->second; } - if (lang_subcmd) { - num_lang_matches = - AddNamesMatchingPartialString(lang_subcmd->GetSubcommandDictionary(), - cmd_str, *matches, descriptions); - } - - if (num_lang_matches == 1) { - cmd.assign(matches->GetStringAtIndex(num_cmd_matches + num_alias_matches + - num_user_matches + - num_user_mw_matches)); - - auto &lang_dict = lang_subcmd->GetSubcommandDictionary(); - auto pos = lang_dict.find(cmd); - if (pos != lang_dict.end()) - lang_match_sp = pos->second; - } - // If we got exactly one match, return that, otherwise return the match // list. if (num_user_matches + num_user_mw_matches + num_cmd_matches + - num_alias_matches + num_lang_matches == + num_alias_matches == 1) { if (num_cmd_matches) return real_match_sp; @@ -1180,12 +1155,37 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, return alias_match_sp; else if (num_user_mw_matches) return user_mw_match_sp; - else if (num_user_matches) - return user_match_sp; else - return lang_match_sp; + return user_match_sp; + } + } + + // When no single match is found, attempt to resolve the command as a language + // plugin subcommand. + if (!command_sp) { + // The `language` subcommand ("language objc", "language cplusplus", etc). + CommandObjectMultiword *lang_subcmd = nullptr; + if (auto lang_subcmd_sp = GetFrameLanguageCommand()) { + lang_subcmd = lang_subcmd_sp->GetAsMultiwordCommand(); + command_sp = lang_subcmd_sp->GetSubcommandSPExact(cmd_str); + } + + if (!command_sp && !exact && lang_subcmd) { + StringList lang_matches; + AddNamesMatchingPartialString(lang_subcmd->GetSubcommandDictionary(), + cmd_str, lang_matches, descriptions); + if (matches) + matches->AppendList(lang_matches); + if (lang_matches.GetSize() == 1) { + const auto &lang_dict = lang_subcmd->GetSubcommandDictionary(); + auto pos = lang_dict.find(lang_matches[0]); + if (pos != lang_dict.end()) + return pos->second; + } } - } else if (matches && command_sp) { + } + + if (matches && command_sp) { matches->AppendString(cmd_str); if (descriptions) descriptions->AppendString(command_sp->GetHelp()); diff --git a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py index f11eb8a472939..419ccc859dbbd 100644 --- a/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -3,7 +3,6 @@ from lldbsuite.test.decorators import * import lldbsuite.test.lldbutil as lldbutil - class TestCase(TestBase): def test(self): self.build() @@ -48,3 +47,14 @@ def test(self): "info -- Dump information on a tagged pointer.", ], ) + + # To ensure compatability with existing scripts, a language specific + # command must not be invoked if another command (such as a python + # command) has the language specific command name as its prefix. + # + # For example, this test loads a `tagged-pointer-collision` command. A + # script could exist that invokes this command using its prefix + # `tagged-pointer`, under the assumption that "tagged-pointer" uniquely + # identifies the python command `tagged-pointer-collision`. + self.runCmd("command script import commands.py") + self.expect("tagged-pointer", startstr="ran tagged-pointer-collision") diff --git a/lldb/test/API/commands/command/language/commands.py b/lldb/test/API/commands/command/language/commands.py new file mode 100644 index 0000000000000..d85e57d9c1a88 --- /dev/null +++ b/lldb/test/API/commands/command/language/commands.py @@ -0,0 +1,5 @@ +import lldb + +@lldb.command("tagged-pointer-collision") +def noop(dbg, cmdstr, ctx, result, _): + print("ran tagged-pointer-collision", file=result) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits