Skip to content

[lldb] Expose language plugin commands based based on language of current frame (#136766) #10755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lldb/include/lldb/Interpreter/CommandInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,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,
Expand Down
12 changes: 12 additions & 0 deletions lldb/source/Commands/CommandObjectLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
51 changes: 50 additions & 1 deletion lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,28 @@ CommandObjectMultiword *CommandInterpreter::VerifyUserMultiwordCmdPath(
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,
Expand Down Expand Up @@ -1144,7 +1166,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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ class CommandObjectMultiwordObjC_TaggedPointer : public CommandObjectMultiword {
: CommandObjectMultiword(
interpreter, "tagged-pointer",
"Commands for operating on Objective-C tagged pointers.",
"class-table <subcommand> [<subcommand-options>]") {
"tagged-pointer <subcommand> [<subcommand-options>]") {
LoadSubCommand(
"info",
CommandObjectSP(
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/commands/command/language/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OBJCXX_SOURCES := main.mm
CXX_SOURCES := lib.cpp
include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil


class TestCase(TestBase):
@skipUnlessDarwin
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")
6 changes: 6 additions & 0 deletions lldb/test/API/commands/command/language/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import lldb


@lldb.command("tagged-pointer-collision")
def noop(dbg, cmdstr, ctx, result, _):
print("ran tagged-pointer-collision", file=result)
3 changes: 3 additions & 0 deletions lldb/test/API/commands/command/language/lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <stdio.h>
extern void f();
void f() { puts("break here"); }
6 changes: 6 additions & 0 deletions lldb/test/API/commands/command/language/main.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extern void f();

int main() {
f();
return 0;
}