Author: Dave Lee Date: 2025-05-28T11:04:57-07:00 New Revision: 7ed185a8f2b9f2436db1ccd82964424f641917e9
URL: https://github.com/llvm/llvm-project/commit/7ed185a8f2b9f2436db1ccd82964424f641917e9 DIFF: https://github.com/llvm/llvm-project/commit/7ed185a8f2b9f2436db1ccd82964424f641917e9.diff LOG: [lldb] Expose language plugin commands based based on language of current frame (#136766) Use the current frame's language to lookup commands provided by language plugins. This means commands like `language {objc,cplusplus} <command>` can be used directly, without using the `language <lang>` prefix. For example, when stopped on a C++ frame, `demangle _Z1fv` will run `language cplusplus demangle _Z1fv`. rdar://149882520 Added: lldb/test/API/commands/command/language/Makefile lldb/test/API/commands/command/language/TestFrameLanguageCommands.py lldb/test/API/commands/command/language/commands.py lldb/test/API/commands/command/language/lib.cpp lldb/test/API/commands/command/language/main.mm Modified: lldb/include/lldb/Interpreter/CommandInterpreter.h lldb/source/Commands/CommandObjectLanguage.cpp lldb/source/Interpreter/CommandInterpreter.cpp Removed: ################################################################################ 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/Commands/CommandObjectLanguage.cpp b/lldb/source/Commands/CommandObjectLanguage.cpp index 925db599e4abb..7272241551058 100644 --- a/lldb/source/Commands/CommandObjectLanguage.cpp +++ b/lldb/source/Commands/CommandObjectLanguage.cpp @@ -21,6 +21,18 @@ CommandObjectLanguage::CommandObjectLanguage(CommandInterpreter &interpreter) "language <language-name> <subcommand> [<subcommand-options>]") { // Let the LanguageRuntime populates this command with subcommands LanguageRuntime::InitializeCommands(this); + SetHelpLong( + R"( +Language specific subcommands may be used directly (without the `language +<language-name>` prefix), when stopped on a frame written in that language. For +example, from a C++ frame, users may run `demangle` directly, instead of +`language cplusplus demangle`. + +Language specific subcommands are only available when the command name cannot be +misinterpreted. Take the `demangle` command for example, if a Python command +named `demangle-tree` were loaded, then the invocation `demangle` would run +`demangle-tree`, not `language cplusplus demangle`. + )"); } CommandObjectLanguage::~CommandObjectLanguage() = default; diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 34749d890bf03..4f9ae104dedea 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1018,6 +1018,28 @@ CommandInterpreter::VerifyUserMultiwordCmdPath(Args &path, bool leaf_is_command, return cur_as_multi; } +CommandObjectSP CommandInterpreter::GetFrameLanguageCommand() const { + 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 CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, bool exact, StringList *matches, @@ -1136,7 +1158,34 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases, else return user_match_sp; } - } else if (matches && command_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; + } + } + } + + if (matches && command_sp) { matches->AppendString(cmd_str); if (descriptions) descriptions->AppendString(command_sp->GetHelp()); diff --git a/lldb/test/API/commands/command/language/Makefile b/lldb/test/API/commands/command/language/Makefile new file mode 100644 index 0000000000000..2d5049417ee70 --- /dev/null +++ b/lldb/test/API/commands/command/language/Makefile @@ -0,0 +1,4 @@ +OBJCXX_SOURCES := main.mm +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 new file mode 100644 index 0000000000000..ff00c9d7cb3f6 --- /dev/null +++ b/lldb/test/API/commands/command/language/TestFrameLanguageCommands.py @@ -0,0 +1,61 @@ +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()") + + # Test `help`. + self.expect( + "help demangle", + substrs=[ + "Demangle a C++ mangled name.", + "Syntax: language cplusplus demangle [<mangled-name> ...]", + ], + ) + + # Run a `language cplusplus` command. + self.expect("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) + + # Run a `language objc` command. + self.expect( + "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.", + ], + ) + + # 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..e4215317f3c3e --- /dev/null +++ b/lldb/test/API/commands/command/language/commands.py @@ -0,0 +1,6 @@ +import lldb + + +@lldb.command("tagged-pointer-collision") +def noop(dbg, cmdstr, ctx, result, _): + print("ran tagged-pointer-collision", file=result) 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; +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits