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

Reply via email to