[Lldb-commits] [lldb] [lldb/script] Migrate frame recognizers onto ScriptedPythonInterface (PR #209805)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 15 23:21:55 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/209805
>From 57fd0680fc95c444b70f439f0ff0095346101e70 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Tue, 14 Jul 2026 22:50:27 -0700
Subject: [PATCH 1/2] [lldb/script] Add scripting extension template generator
This patch adds a `scripting extension generate <ExtensionType>...`
command that introspects a Python extension base class and emits a
skeleton subclass with `# TODO: Implement` stubs for its abstract
methods (or, with `-a`, every method), then opens the result in an
editor.
Generated imports use `from <module> import <class>`, and the
generated `__init__` forwards its arguments to `super().__init__(...)`
since every base class relies on its constructor to set up attributes
(`self.target`, `self.process`, ...) that inherited, non-overridden
methods depend on. When the host can't open an external editor (e.g.
non-macOS), the command reports that as a message rather than an
error, since the file is already written.
To catch regressions in the generator itself instead of just checking
that a file was produced, this patch adds
`TestScriptingExtensionGenerate.py`, which generates a template for
every extension kind on the fly and drives it through a live lldb
session (`process launch -C`, `breakpoint set -P`, `target stop-hook
add -P`, etc.).
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Core/PluginManager.h | 5 +-
.../lldb/Interpreter/ScriptInterpreter.h | 12 +
.../Commands/CommandObjectScripting.cpp | 187 ++++++++++
lldb/source/Commands/Options.td | 29 ++
lldb/source/Core/PluginManager.cpp | 28 +-
lldb/source/Interpreter/ScriptInterpreter.cpp | 9 +
.../Interpreter/embedded_interpreter.py | 125 +++++++
.../Interfaces/ScriptedPythonInterface.h | 7 +-
.../Python/ScriptInterpreterPython.cpp | 345 ++++++++++++++++++
.../Python/ScriptInterpreterPython.h | 17 +
.../scripting_extension_generate/Makefile | 3 +
.../TestScriptingExtensionGenerate.py | 167 +++++++++
.../scripting_extension_generate/main.c | 12 +
13 files changed, 935 insertions(+), 11 deletions(-)
create mode 100644 lldb/test/API/functionalities/scripting_extension_generate/Makefile
create mode 100644 lldb/test/API/functionalities/scripting_extension_generate/TestScriptingExtensionGenerate.py
create mode 100644 lldb/test/API/functionalities/scripting_extension_generate/main.c
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 230a025c2f9e1..8636a40d00b13 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -687,8 +687,9 @@ class PluginManager {
static ScriptedInterfaceUsages
GetScriptedInterfaceUsagesAtIndex(uint32_t idx);
- static void AutoCompleteScriptedExtension(llvm::StringRef partial_name,
- CompletionRequest &request);
+ static void AutoCompleteScriptedExtension(
+ llvm::StringRef partial_name, CompletionRequest &request,
+ lldb::ScriptLanguage language = lldb::eScriptLanguageUnknown);
// REPL
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 2b9cb8f7bb866..12a915636ef92 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -39,6 +39,7 @@
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
+#include "lldb/Utility/UnimplementedError.h"
#include "lldb/lldb-private.h"
#include <optional>
@@ -170,6 +171,12 @@ class ScriptInterpreter : public PluginInterface {
virtual StructuredData::DictionarySP GetInterpreterInfo();
+ virtual llvm::Expected<FileSpec> GenerateExtensionTemplate(
+ const std::string name,
+ std::vector<std::pair<llvm::StringRef,
+ llvm::SmallVector<llvm::StringRef>>> &extensions,
+ bool m_generate_non_abstract_methods, std::string output_file);
+
~ScriptInterpreter() override = default;
virtual bool Interrupt() { return false; }
@@ -526,6 +533,11 @@ class ScriptInterpreter : public PluginInterface {
static lldb::ScriptLanguage StringToLanguage(const llvm::StringRef &string);
+ virtual llvm::Expected<std::string>
+ ExtensionToImportPath(lldb::ScriptedExtension extension) {
+ return llvm::make_error<UnimplementedError>();
+ }
+
static llvm::StringLiteral
ExtensionToString(lldb::ScriptedExtension extension);
diff --git a/lldb/source/Commands/CommandObjectScripting.cpp b/lldb/source/Commands/CommandObjectScripting.cpp
index ff02169df1172..077edcee0490a 100644
--- a/lldb/source/Commands/CommandObjectScripting.cpp
+++ b/lldb/source/Commands/CommandObjectScripting.cpp
@@ -371,6 +371,190 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
CommandOptions m_options;
};
+#define LLDB_OPTIONS_scripting_extension_generate
+#include "CommandOptions.inc"
+
+class CommandObjectScriptingExtensionGenerate : public CommandObjectParsed {
+public:
+ CommandObjectScriptingExtensionGenerate(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "scripting extension generate",
+ "Generate a scripting extension template. ",
+ "scripting extension generate") {
+ AddSimpleArgumentList(eArgTypeScriptedExtension, eArgRepeatPlus);
+ }
+
+ ~CommandObjectScriptingExtensionGenerate() override = default;
+
+ Options *GetOptions() override { return &m_options; }
+
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() = default;
+ ~CommandOptions() override = default;
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const char short_option =
+ g_scripting_extension_generate_options[option_idx].short_option;
+
+ switch (short_option) {
+ case 'a':
+ m_generate_non_abstract_methods = true;
+ break;
+ case 'l':
+ m_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
+ option_arg, GetDefinitions()[option_idx].enum_values,
+ eScriptLanguageNone, error);
+ if (!error.Success())
+ error = Status::FromErrorStringWithFormatv(
+ "unrecognized value for language '{0}'", option_arg);
+ break;
+ case 'n':
+ m_generated_class_prefix = option_arg.str();
+ break;
+ case 'o':
+ m_output_filepath = option_arg.str();
+ break;
+ case 'e': {
+ bool success;
+ m_open_editor = OptionArgParser::ToBoolean(option_arg, true, &success)
+ ? eLazyBoolYes
+ : eLazyBoolNo;
+ if (!success)
+ error = Status::FromErrorStringWithFormatv(
+ "invalid boolean value for -e: '{0}'", option_arg);
+ } break;
+ default:
+ llvm_unreachable("Unimplemented option");
+ }
+
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ m_generate_non_abstract_methods = false;
+ m_language = lldb::eScriptLanguageDefault;
+ m_generated_class_prefix.clear();
+ m_output_filepath.clear();
+ m_open_editor = eLazyBoolCalculate;
+ }
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::ArrayRef(g_scripting_extension_generate_options);
+ }
+
+ bool m_generate_non_abstract_methods = false;
+ lldb::ScriptLanguage m_language = lldb::eScriptLanguageDefault;
+ std::string m_generated_class_prefix;
+ std::string m_output_filepath;
+ LazyBool m_open_editor = eLazyBoolCalculate;
+ };
+
+ void
+ HandleArgumentCompletion(CompletionRequest &request,
+ OptionElementVector &opt_element_vector) override {
+ // If `-l <lang>` was already given, complete only extensions available
+ // in that language. Bare `-l` (no value yet) and any parse failures
+ // fall back to language-agnostic completion.
+ lldb::ScriptLanguage language = lldb::eScriptLanguageUnknown;
+ llvm::ArrayRef<OptionDefinition> defs = m_options.GetDefinitions();
+ for (const OptionArgElement &elem : opt_element_vector) {
+ if (elem.opt_defs_index < 0 ||
+ static_cast<size_t>(elem.opt_defs_index) >= defs.size())
+ continue;
+ if (defs[elem.opt_defs_index].short_option != 'l' ||
+ elem.opt_arg_pos <= 0)
+ continue;
+ llvm::StringRef value =
+ request.GetParsedLine().GetArgumentAtIndex(elem.opt_arg_pos);
+ Status error;
+ auto candidate = static_cast<lldb::ScriptLanguage>(
+ OptionArgParser::ToOptionEnum(value, defs[elem.opt_defs_index].enum_values,
+ lldb::eScriptLanguageUnknown, error));
+ if (error.Success())
+ language = candidate;
+ break;
+ }
+ PluginManager::AutoCompleteScriptedExtension(
+ request.GetCursorArgumentPrefix(), request, language);
+ lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
+ GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
+ }
+
+protected:
+ void DoExecute(Args &command, CommandReturnObject &result) override {
+ if (command.GetArgumentCount() == 0) {
+ result.SetError(
+ Status::FromErrorString("specify extension name to generate"));
+ return;
+ }
+
+ std::vector<std::pair<llvm::StringRef, llvm::SmallVector<llvm::StringRef>>>
+ name_import_pair;
+
+ for (size_t i = 0; i < command.GetArgumentCount(); i++) {
+ llvm::StringRef extension_name = command.GetArgumentAtIndex(i);
+ llvm::SmallVector<llvm::StringRef> extension_components;
+ extension_name.split(extension_components, ".");
+ lldb::ScriptedExtension extension =
+ ScriptInterpreter::StringToExtension(extension_components.back());
+ if (extension == eScriptedExtensionInvalid) {
+ result.SetError(Status::FromErrorStringWithFormatv(
+ "unknown scripted extension: '{0}'", extension_name));
+ return;
+ }
+ name_import_pair.push_back({extension_name, extension_components});
+ }
+
+ lldb::ScriptLanguage language =
+ (m_options.m_language == lldb::eScriptLanguageNone)
+ ? m_interpreter.GetDebugger().GetScriptLanguage()
+ : m_options.m_language;
+
+ if (language == lldb::eScriptLanguageNone) {
+ result.AppendError(
+ "the script-lang setting is set to none - scripting not available");
+ return;
+ }
+
+ ScriptInterpreter *script_interpreter =
+ GetDebugger().GetScriptInterpreter(true, language);
+
+ if (script_interpreter == nullptr) {
+ result.AppendError("no script interpreter");
+ return;
+ }
+
+ auto generated_file_or_err = script_interpreter->GenerateExtensionTemplate(
+ m_options.m_generated_class_prefix, name_import_pair,
+ m_options.m_generate_non_abstract_methods, m_options.m_output_filepath);
+ if (!generated_file_or_err) {
+ result.SetError(generated_file_or_err.takeError());
+ return;
+ }
+
+ // `-e`/`--edit` decides whether to launch an external editor on the
+ // generated file. Defaults to true; pass `-e false` to skip.
+ if (m_options.m_open_editor != eLazyBoolNo) {
+ if (llvm::Error err = Host::OpenFileInExternalEditor(
+ "", *generated_file_or_err, 1, true)) {
+ // Opening the file in an editor is a convenience, not a requirement:
+ // the template was already written to disk successfully, so don't
+ // fail the whole command over it (e.g. no external editor available
+ // on this platform).
+ llvm::consumeError(std::move(err));
+ }
+ }
+ result.AppendMessageWithFormatv(
+ "Generated scripting extension template: {0}",
+ generated_file_or_err->GetPath());
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ }
+
+private:
+ CommandOptions m_options;
+};
+
class CommandObjectMultiwordScriptingExtension : public CommandObjectMultiword {
public:
CommandObjectMultiwordScriptingExtension(CommandInterpreter &interpreter)
@@ -381,6 +565,9 @@ class CommandObjectMultiwordScriptingExtension : public CommandObjectMultiword {
LoadSubCommand(
"list",
CommandObjectSP(new CommandObjectScriptingExtensionList(interpreter)));
+ LoadSubCommand("generate",
+ CommandObjectSP(new CommandObjectScriptingExtensionGenerate(
+ interpreter)));
}
~CommandObjectMultiwordScriptingExtension() override = default;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index bd6fa8efad0a5..f10b6889e8805 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1587,6 +1587,35 @@ let Command = "scripting extension list" in {
Desc<"Output the scripted extension list in json format.">;
}
+let Command = "scripting extension generate" in {
+ def scripting_extension_generate_all_methods
+ : Option<"all", "a">,
+ Desc<"Generate all the extension methods, not only the abstract ones.">;
+ def scripting_extension_generate_language
+ : Option<"language", "l">,
+ EnumArg<"ScriptLang">,
+ Desc<"Specify the scripting "
+ " language. If none is specified the default scripting language "
+ "is used.">;
+ def scripting_extension_generate_class_suffix
+ : Option<"name", "n">,
+ Arg<"ClassName">,
+ Required,
+ Desc<"Specify the class prefix name to include in the generated class "
+ "definitions.">;
+ def scripting_extension_generate_output_file
+ : Option<"output", "o">,
+ Arg<"Filename">,
+ Desc<"File location where the extension is generated.">;
+ def scripting_extension_generate_open_editor
+ : Option<"edit", "e">,
+ Arg<"Boolean">,
+ Desc<"Open the generated file in an external editor after writing "
+ "it to disk. Defaults to true; pass `-e false` to skip "
+ "opening the editor (e.g. under SSH without X forwarding, "
+ "or in scripted runs).">;
+}
+
let Command = "source info" in {
def source_info_count : Option<"count", "c">,
Arg<"Count">,
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 893993d2b24a1..e961fb942fa03 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -20,6 +20,7 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StringList.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/ErrorExtras.h"
@@ -2127,15 +2128,26 @@ PluginManager::GetScriptedInterfaceUsagesAtIndex(uint32_t idx) {
}
void PluginManager::AutoCompleteScriptedExtension(llvm::StringRef name,
- CompletionRequest &request) {
+ CompletionRequest &request,
+ lldb::ScriptLanguage language) {
+ llvm::StringSet<> emitted;
for (size_t idx = 0; idx < GetNumScriptedInterfaces(); idx++) {
- if (auto instance =
- GetScriptedInterfaceInstances().GetInstanceAtIndex(idx)) {
- llvm::StringLiteral extension_name =
- ScriptInterpreter::ExtensionToString(instance->extension);
- if (extension_name.starts_with(name))
- request.AddCompletion(extension_name);
- }
+ auto instance = GetScriptedInterfaceInstances().GetInstanceAtIndex(idx);
+ if (!instance)
+ continue;
+ // Filter to the requested language when the caller pinned one via
+ // `-l`. `eScriptLanguageUnknown` means "no filter".
+ if (language != lldb::eScriptLanguageUnknown &&
+ instance->language != language)
+ continue;
+ llvm::StringLiteral extension_name =
+ ScriptInterpreter::ExtensionToString(instance->extension);
+ if (!extension_name.starts_with(name))
+ continue;
+ // A single extension can back multiple languages, so dedup entries
+ // we've already surfaced.
+ if (emitted.insert(extension_name).second)
+ request.AddCompletion(extension_name);
}
}
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 8420782b02814..c73fbd8204f03 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -15,6 +15,7 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StringList.h"
+#include "lldb/Utility/UnimplementedError.h"
#include "lldb/ValueObject/ValueObject.h"
#include "llvm/ADT/StringSwitch.h"
#if defined(_WIN32)
@@ -50,6 +51,14 @@ StructuredData::DictionarySP ScriptInterpreter::GetInterpreterInfo() {
return nullptr;
}
+llvm::Expected<FileSpec> ScriptInterpreter::GenerateExtensionTemplate(
+ const std::string name,
+ std::vector<std::pair<llvm::StringRef, llvm::SmallVector<llvm::StringRef>>>
+ &extensions,
+ bool generate_non_abstract_methods, std::string output_file) {
+ return llvm::make_error<UnimplementedError>();
+}
+
bool ScriptInterpreter::LoadScriptingModule(
const char *filename, const LoadScriptOptions &options,
lldb_private::Status &error, StructuredData::ObjectSP *module_sp,
diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py
index 12c47bd712816..5c73278d1203f 100644
--- a/lldb/source/Interpreter/embedded_interpreter.py
+++ b/lldb/source/Interpreter/embedded_interpreter.py
@@ -77,6 +77,131 @@ def run_python_interpreter(local_dict):
if e.code:
print("Script exited with code %s" % e.code)
+
+def generate_extension_schema(cls):
+ """Introspect a scripting extension base class and return a JSON schema
+ describing its members. Used by `scripting extension generate` (via
+ `ScriptInterpreterPython::GetExtensionSchema`) to emit a skeleton
+ subclass with `# TODO: Implement` stubs for each method the base class
+ defines. Each method entry carries the signature, type hints,
+ docstring, and whether it's `@abstractmethod`, so the generator can
+ decide which methods to stub out (all of them with `-a`, otherwise
+ just the abstract ones). The schema also lists non-callable
+ attributes the base class exposes -- class-level values plus
+ class-body type annotations -- so the generator can advertise them in
+ the derived class' docstring. A `typing_imports` field enumerates
+ every `typing` generic (`Optional`, `Union`, ...) referenced by the
+ signatures or attribute types, so the generator can add the right
+ `from typing import` line without having to re-scan strings."""
+ import inspect, json, typing
+
+ used_typing = set()
+
+ def _record_typing(s):
+ # Anything from `typing.__all__` referenced as a generic
+ # (`Optional[...]`) gets picked up. Keying off the `[` avoids
+ # matching identifiers that merely embed the name.
+ if not s:
+ return
+ for name in typing.__all__:
+ if f"{name}[" in s:
+ used_typing.add(name)
+
+ def _fmt_type(t):
+ # `type(None)` stringifies as `NoneType`; render it as the
+ # literal `None` so the annotation stays valid Python.
+ if t is type(None):
+ return "None"
+ if isinstance(t, type):
+ if t.__module__ == "builtins":
+ return t.__name__
+ return f"{t.__module__}.{t.__name__}"
+ # `typing` generic aliases stringify with a leading `typing.`
+ # (`typing.Optional[list]`); the module prefix is noise for a
+ # docstring. `Union[int, str, None]` also renders its `None`
+ # component as `NoneType`, so fix that up too.
+ formatted = str(t).replace("typing.", "").replace("NoneType", "None")
+ _record_typing(formatted)
+ return formatted
+
+ def _build_signature(func):
+ # Reconstruct the signature from resolved type hints so forward
+ # refs (`"ScriptedFrame"`) come out as their real class -- what
+ # `inspect.signature(...)`'s own `str` would render as
+ # `ForwardRef('ScriptedFrame')`.
+ try:
+ hints = typing.get_type_hints(func)
+ except Exception:
+ hints = {}
+ sig = inspect.signature(func)
+ parts = []
+ for name, param in sig.parameters.items():
+ piece = name
+ if name in hints:
+ piece += f": {_fmt_type(hints[name])}"
+ if param.default is not inspect.Parameter.empty:
+ piece += f" = {param.default!r}"
+ parts.append(piece)
+ rendered = "(" + ", ".join(parts) + ")"
+ if "return" in hints:
+ rendered += f" -> {_fmt_type(hints['return'])}"
+ return rendered
+
+ def _get_function_metadata(func):
+ try:
+ hints = typing.get_type_hints(func)
+ type_hints = {k: str(v) for k, v in hints.items()}
+ except Exception:
+ type_hints = {}
+ return {
+ "signature": _build_signature(func),
+ "type_hints": type_hints,
+ "is_abstract": getattr(func, "__isabstractmethod__", False),
+ "doc": inspect.getdoc(func),
+ }
+
+ try:
+ class_hints = typing.get_type_hints(cls)
+ except Exception:
+ class_hints = {}
+
+ members = []
+ attributes = []
+ seen_attrs = set()
+ for name, member in inspect.getmembers(cls):
+ if inspect.isfunction(member):
+ members.append({"name": name, **_get_function_metadata(member)})
+ continue
+ if name.startswith("_"):
+ continue
+ entry = {"name": name}
+ if name in class_hints:
+ entry["type"] = _fmt_type(class_hints[name])
+ attributes.append(entry)
+ seen_attrs.add(name)
+
+ # Class-body type annotations without a runtime value
+ # (`target: SBTarget`) don't show up in `inspect.getmembers`, so pick
+ # them up from the hint map directly.
+ for name in class_hints:
+ if name.startswith("_") or name in seen_attrs:
+ continue
+ attributes.append({"name": name, "type": _fmt_type(class_hints[name])})
+ seen_attrs.add(name)
+
+ return json.dumps(
+ {
+ "class": cls.__name__,
+ "module": cls.__module__,
+ "doc": inspect.getdoc(cls),
+ "members": members,
+ "attributes": attributes,
+ "typing_imports": sorted(used_typing),
+ },
+ separators=(",", ":"),
+ )
+
+
def run_one_line(local_dict, input_string):
global g_run_one_line_str
try:
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index 7d0d4cdd3c6d1..aaa0b6a0f7a59 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -251,10 +251,15 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
size_t num_args = sizeof...(Args);
if (arg_info->max_positional_args != PythonCallable::ArgInfo::UNBOUNDED &&
num_args != arg_info->max_positional_args) {
- if (num_args != arg_info->max_positional_args - 1)
+ if (num_args != arg_info->max_positional_args - 1) {
+ // `expected_return_object` starts in an error state; consume it
+ // before we return with a different error, or its destructor
+ // will abort.
+ llvm::consumeError(expected_return_object.takeError());
return create_error("Passed arguments ({0}) doesn't match the number "
"of expected arguments ({1}).",
num_args, arg_info->max_positional_args);
+ }
std::apply(
[&init, &expected_return_object](auto &&...args) {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index f3f2b801d43e2..9d5d8f106a10e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -36,11 +36,13 @@
#include "lldb/Target/ThreadPlan.h"
#include "lldb/Utility/Instrumentation.h"
#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/Timer.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorExtras.h"
@@ -265,6 +267,349 @@ StructuredData::DictionarySP ScriptInterpreterPython::GetInterpreterInfo() {
return info_json.CreateStructuredDictionary();
}
+llvm::Expected<std::string> ScriptInterpreterPython::ExtensionToImportPath(
+ lldb::ScriptedExtension extension) {
+ switch (extension) {
+ case eScriptedExtensionOperatingSystem:
+ return "lldb.plugins.operating_system";
+ case eScriptedExtensionScriptedPlatform:
+ return "lldb.plugins.scripted_platform";
+ case eScriptedExtensionScriptedProcess:
+ return "lldb.plugins.scripted_process";
+ case eScriptedExtensionScriptedHook:
+ return "lldb.plugins.scripted_hook";
+ case eScriptedExtensionScriptedBreakpointResolver:
+ return "lldb.plugins.scripted_breakpoint";
+ case eScriptedExtensionScriptedThreadPlan:
+ return "lldb.plugins.scripted_thread_plan";
+ case eScriptedExtensionScriptedFrameProvider:
+ return "lldb.plugins.scripted_frame_provider";
+ case eScriptedExtensionScriptedThread:
+ case eScriptedExtensionScriptedFrame:
+ return "lldb.plugins.scripted_process";
+ case eScriptedExtensionInvalid:
+ return llvm::createStringError("invalid extension name");
+ }
+ return llvm::createStringError("invalid extension name");
+}
+
+llvm::Expected<StructuredData::ObjectSP>
+ScriptInterpreterPython::GetExtensionSchema(
+ llvm::SmallVector<llvm::StringRef> &extension_path) {
+ lldb::ScriptedExtension extension =
+ ScriptInterpreter::StringToExtension(extension_path.back());
+ auto import_path_or_err = ExtensionToImportPath(extension);
+ if (!import_path_or_err)
+ return import_path_or_err.takeError();
+
+ StreamString command_stream;
+ // __import__(path, fromlist=['']) imports the submodule and returns it
+ // directly (rather than the top-level package), as a single expression --
+ // this keeps the whole call eval-able in one line while guaranteeing the
+ // module is imported first; referencing "<import_path>.<ClassName>"
+ // directly would only work if something else had already imported
+ // <import_path> as a side effect.
+ command_stream.Printf("lldb.embedded_interpreter.generate_extension_schema("
+ "__import__('%s', fromlist=['']).%s)",
+ import_path_or_err->c_str(),
+ ScriptInterpreter::ExtensionToString(extension).data());
+
+ // Use eScriptReturnTypeOpaqueObject: it transfers a real owned reference
+ // we can safely extract the string from. eScriptReturnTypeCharStrOrNone
+ // instead hands back a pointer to a temporary Python object's buffer
+ // that gets destroyed (and, for a freshly created string like this one,
+ // deallocated) as soon as ExecuteOneLineWithReturn returns -- reading
+ // it afterwards is a use-after-free.
+ void *result_obj = nullptr;
+ if (!ExecuteOneLineWithReturn(
+ command_stream.GetData(),
+ ScriptInterpreter::eScriptReturnTypeOpaqueObject, &result_obj,
+ ExecuteScriptOptions().SetEnableIO(false)))
+ return llvm::createStringError("invalid extension schema format");
+
+ // ExecuteOneLineWithReturn releases the GIL before returning, so touching
+ // the returned object (Str() below can execute arbitrary Python code) must
+ // re-acquire it first. py_result is scoped so its destructor (a DECREF)
+ // also runs before the GIL is released below, not after.
+ std::string schema_str;
+ {
+ PyGILState_STATE gil_state = PyGILState_Ensure();
+ {
+ PythonObject py_result(PyRefType::Owned,
+ static_cast<PyObject *>(result_obj));
+ if (py_result.IsAllocated() && py_result.get() != Py_None)
+ schema_str = py_result.Str().GetString().str();
+ }
+ PyGILState_Release(gil_state);
+ }
+
+ if (schema_str.empty())
+ return llvm::createStringError("empty extension schema");
+ return StructuredData::ParseJSON(schema_str);
+}
+
+llvm::Error ScriptInterpreterPython::ParseExtensionSchema(
+ Stream &s, llvm::StringRef output_script_prefix,
+ llvm::SmallVector<llvm::StringRef> &extension_path,
+ bool generate_non_abstract_methods, std::set<std::string> &typing_imports) {
+ auto schema_or_err = GetExtensionSchema(extension_path);
+ if (!schema_or_err)
+ return schema_or_err.takeError();
+
+ StructuredData::ObjectSP schema = *schema_or_err;
+ StructuredData::Dictionary *dict = schema->GetAsDictionary();
+
+ // Merge each class' typing imports into the caller-owned set so the
+ // final `from typing import ...` line covers every class we emit.
+ StructuredData::Array *schema_typing;
+ if (dict->GetValueForKeyAsArray("typing_imports", schema_typing))
+ schema_typing->ForEach([&](StructuredData::Object *entry) {
+ if (auto *str = entry->GetAsString())
+ typing_imports.insert(str->GetValue().str());
+ return true;
+ });
+
+ llvm::StringRef base_class, import_path;
+ if (!dict->GetValueForKeyAsString("class", base_class))
+ return llvm::createStringError(
+ llvm::formatv("extension schema dictionary is missing 'class' key")
+ .str());
+ if (!dict->GetValueForKeyAsString("module", import_path))
+ return llvm::createStringError(
+ llvm::formatv("extension schema dictionary is missing 'module' key")
+ .str());
+
+ // imports
+ s.Printf("from %s import %s\n", import_path.data(), base_class.data());
+ s.EOL();
+
+ // class definition
+ s.Printf("class %s%s(%s):\n", output_script_prefix.data(), base_class.data(),
+ base_class.data());
+ s.IndentMore();
+
+ // Class docstring: list the non-callable members the base class exposes
+ // so the user sees what's available without having to hop back to the
+ // base class definition.
+ bool has_body = false;
+ StructuredData::Array *attributes;
+ if (dict->GetValueForKeyAsArray("attributes", attributes) &&
+ attributes->GetSize()) {
+ s.Indent();
+ s.PutCString("\"\"\"\n");
+ s.Indent();
+ s.Printf("Attributes inherited from %s:\n", base_class.data());
+ for (size_t i = 0; i < attributes->GetSize(); i++) {
+ auto maybe_dict = attributes->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ continue;
+ StructuredData::Dictionary *attr_dict = *maybe_dict;
+ llvm::StringRef attr_name;
+ if (!attr_dict->GetValueForKeyAsString("name", attr_name))
+ continue;
+ llvm::StringRef attr_type;
+ bool has_type = attr_dict->GetValueForKeyAsString("type", attr_type);
+ s.Indent();
+ s.Printf("- %s", attr_name.data());
+ if (has_type)
+ s.Printf(": %s", attr_type.data());
+ s.EOL();
+ }
+ s.Indent();
+ s.PutCString("\"\"\"\n\n");
+ has_body = true;
+ }
+
+ // members
+ StructuredData::Array *members;
+ if (!dict->GetValueForKeyAsArray("members", members))
+ return llvm::createStringError("missing 'members' key in extension schema");
+
+ // If the base class doesn't mark anything `@abstractmethod`, the filter
+ // "only stub abstract methods" would leave the derived class empty --
+ // which isn't a useful starting point. Fall back to emitting every
+ // method in that case so the user has actual code to edit.
+ bool any_abstract = false;
+ for (size_t i = 0; i < members->GetSize(); i++) {
+ auto maybe_dict = members->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ continue;
+ bool is_abstract = false;
+ if ((*maybe_dict)->GetValueForKeyAsBoolean("is_abstract", is_abstract) &&
+ is_abstract) {
+ any_abstract = true;
+ break;
+ }
+ }
+ bool emit_all_methods = generate_non_abstract_methods || !any_abstract;
+
+ for (size_t i = 0; i < members->GetSize(); i++) {
+ auto maybe_dict = members->GetItemAtIndexAsDictionary(i);
+ if (!maybe_dict)
+ return llvm::createStringError(
+ llvm::formatv(
+ "member at index {0} in extension schema isn't a dictionary")
+ .str());
+
+ StructuredData::Dictionary *member_dict = *maybe_dict;
+ llvm::StringRef symbol, args;
+ if (!member_dict->GetValueForKeyAsString("name", symbol))
+ return llvm::createStringError(
+ llvm::formatv(
+ "member at index {0} in extension schema is missing 'name' key")
+ .str());
+ if (!member_dict->GetValueForKeyAsString("signature", args))
+ return llvm::createStringError(
+ llvm::formatv("member at index {0} in extension schema is missing "
+ "'signature' key")
+ .str());
+
+ bool is_abstract = false;
+ bool has_is_abstract =
+ member_dict->GetValueForKeyAsBoolean("is_abstract", is_abstract);
+ if (!emit_all_methods)
+ if (!has_is_abstract || !is_abstract)
+ continue;
+
+ s.Indent();
+ s.Printf("def %s%s:\n", symbol.data(), args.data());
+
+ s.IndentMore();
+ llvm::StringRef documentation;
+ if (member_dict->GetValueForKeyAsString("doc", documentation)) {
+ s.Indent();
+ s.PutCString("\"\"\"\n");
+
+ llvm::SmallVector<llvm::StringRef> lines;
+ documentation.split(lines, "\n");
+
+ for (llvm::StringRef line : lines) {
+ s.Indent();
+ s.PutCString(line);
+ s.EOL();
+ }
+
+ s.Indent();
+ s.PutCString("\"\"\"");
+ s.EOL();
+ }
+
+ if (symbol == "__init__") {
+ // The base class' constructor sets up attributes (e.g. self.target,
+ // self.process) that the inherited, non-overridden methods rely on.
+ // Forward the same arguments so that state is still initialized.
+ // Splitting the param list on `,` requires bracket-depth awareness
+ // because annotations like `Union[X, Y]` also contain commas.
+ llvm::StringRef params = args.trim("()");
+ std::vector<std::string> forwarded_args;
+ int depth = 0;
+ size_t start = 0;
+ auto flush = [&](size_t end) {
+ llvm::StringRef param = params.slice(start, end);
+ param = param.split(':').first.split('=').first.trim();
+ if (!param.empty() && param != "self")
+ forwarded_args.push_back(param.str());
+ };
+ for (size_t i = 0; i < params.size(); ++i) {
+ char c = params[i];
+ if (c == '[' || c == '(' || c == '{')
+ ++depth;
+ else if (c == ']' || c == ')' || c == '}')
+ --depth;
+ else if (c == ',' && depth == 0) {
+ flush(i);
+ start = i + 1;
+ }
+ }
+ flush(params.size());
+ s.Indent();
+ s.Printf("super().__init__(%s)\n",
+ llvm::join(forwarded_args, ", ").c_str());
+ }
+
+ s.Indent();
+ s.PutCString("# TODO: Implement\n");
+ s.Indent();
+ s.PutCString("pass\n\n");
+ s.IndentLess();
+ has_body = true;
+ }
+
+ // A class with no body is a Python syntax error, so emit `pass` when the
+ // base class has nothing to stub out (no methods and no attributes to
+ // document).
+ if (!has_body) {
+ s.Indent();
+ s.PutCString("pass\n");
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<FileSpec> ScriptInterpreterPython::GenerateExtensionTemplate(
+ const std::string name,
+ std::vector<std::pair<llvm::StringRef, llvm::SmallVector<llvm::StringRef>>>
+ &extensions,
+ bool generate_non_abstract_methods, std::string output_file) {
+ // `ParseExtensionSchema` accumulates every `typing` generic it sees
+ // (`Optional`, `Union`, `List`, ...) into this set so we can emit a
+ // targeted `from typing import ...` line only for what's actually
+ // referenced. The Python schema does the detection so we don't have
+ // to re-scan strings here.
+ std::set<std::string> typing_imports;
+ StreamString bodies;
+ for (auto extension_pair : extensions) {
+ if (llvm::Error err =
+ ParseExtensionSchema(bodies, name, extension_pair.second,
+ generate_non_abstract_methods, typing_imports))
+ return std::move(err);
+ bodies.PutCString("\n\n");
+ }
+
+ StreamString generated_file_stream;
+ generated_file_stream.PutCString("import lldb\n");
+ if (!typing_imports.empty()) {
+ std::vector<std::string> sorted_imports(typing_imports.begin(),
+ typing_imports.end());
+ generated_file_stream.Format("from typing import {0}\n",
+ llvm::join(sorted_imports, ", "));
+ }
+ generated_file_stream.PutCString("\n");
+ generated_file_stream.PutCString(bodies.GetString());
+
+ FileSpec save_location;
+ if (output_file.empty()) {
+ const std::string file_name =
+ "lldb_" + llvm::StringRef(name).lower() + "_extension.py";
+ save_location = HostInfo::GetGlobalTempDir();
+ FileSystem::Instance().Resolve(save_location);
+ save_location.AppendPathComponent(file_name);
+ } else {
+ save_location = FileSpec(output_file);
+ FileSystem::Instance().Resolve(save_location);
+ }
+
+ File::OpenOptions flags = File::eOpenOptionWriteOnly |
+ File::eOpenOptionCanCreate |
+ File::eOpenOptionTruncate;
+
+ auto opened_file = FileSystem::Instance().Open(save_location, flags);
+
+ if (!opened_file)
+ return opened_file.takeError();
+
+ FileUP file = std::move(opened_file.get());
+
+ size_t byte_size = generated_file_stream.GetSize();
+
+ Status error = file->Write(generated_file_stream.GetData(), byte_size);
+
+ if (error.Fail() || byte_size != generated_file_stream.GetSize())
+ return llvm::createStringError("Unable to write to destination file. Bytes "
+ "written do not match generated file size.");
+ return save_location;
+}
+
void ScriptInterpreterPython::SharedLibraryDirectoryHelper(
FileSpec &this_file) {
// When we're loaded from python, this_file will point to the file inside the
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
index bade88614f3d9..2cd049d729860 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
@@ -16,6 +16,7 @@
#include "lldb/lldb-private.h"
#include <memory>
+#include <set>
#include <string>
#include <vector>
@@ -41,7 +42,15 @@ class ScriptInterpreterPython : public ScriptInterpreter,
: ScriptInterpreter(debugger, lldb::eScriptLanguagePython),
IOHandlerDelegateMultiline("DONE") {}
+ llvm::Expected<std::string>
+ ExtensionToImportPath(lldb::ScriptedExtension extension) override;
StructuredData::DictionarySP GetInterpreterInfo() override;
+ llvm::Expected<FileSpec> GenerateExtensionTemplate(
+ const std::string name,
+ std::vector<std::pair<llvm::StringRef,
+ llvm::SmallVector<llvm::StringRef>>> &extensions,
+ bool generate_non_abstract_methods, std::string output_file) override;
+
static void Initialize();
static void Terminate();
static llvm::StringRef GetPluginNameStatic() { return "script-python"; }
@@ -50,6 +59,14 @@ class ScriptInterpreterPython : public ScriptInterpreter,
static void SharedLibraryDirectoryHelper(FileSpec &this_file);
protected:
+ llvm::Error
+ ParseExtensionSchema(Stream &s, llvm::StringRef output_script_prefix,
+ llvm::SmallVector<llvm::StringRef> &extension_path,
+ bool generate_non_abstract_methods,
+ std::set<std::string> &typing_imports);
+ llvm::Expected<StructuredData::ObjectSP>
+ GetExtensionSchema(llvm::SmallVector<llvm::StringRef> &extension_path);
+
static void ComputePythonDirForApple(llvm::SmallVectorImpl<char> &path);
static void ComputePythonDir(llvm::SmallVectorImpl<char> &path);
};
diff --git a/lldb/test/API/functionalities/scripting_extension_generate/Makefile b/lldb/test/API/functionalities/scripting_extension_generate/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/scripting_extension_generate/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/scripting_extension_generate/TestScriptingExtensionGenerate.py b/lldb/test/API/functionalities/scripting_extension_generate/TestScriptingExtensionGenerate.py
new file mode 100644
index 0000000000000..1bf7fe53c8166
--- /dev/null
+++ b/lldb/test/API/functionalities/scripting_extension_generate/TestScriptingExtensionGenerate.py
@@ -0,0 +1,167 @@
+"""
+Meta-test: generate every kind of scripting extension template on the fly via
+`scripting extension generate`, then actually register and drive each one
+through a live lldb debug session, to catch regressions in the generator
+itself (e.g. broken imports, wrong base-class name, missing __init__
+chaining) rather than just checking that a Python file was produced.
+
+The stub methods lldb calls into may legitimately report failure (e.g. an
+`is_alive` stub that returns None can make `process launch` report an
+error), so success/failure of the driving command is not asserted. What is
+asserted is that lldb never surfaces an unhandled Python traceback while
+running the generated code.
+"""
+
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestScriptingExtensionGenerate(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def generate_extension(self, kind, generate_all=False):
+ """Run `scripting extension generate` for `kind`, returning the path
+ to the generated file. Fails the test if generation errors out."""
+ output_path = self.getBuildArtifact(f"generated_{kind}.py")
+ if os.path.exists(output_path):
+ os.remove(output_path)
+
+ all_flag = " -a" if generate_all else ""
+ self.runCmd(
+ f"scripting extension generate{all_flag} -n Test -o {output_path} {kind}"
+ )
+
+ self.assertTrue(
+ os.path.exists(output_path),
+ f"scripting extension generate did not create {output_path}",
+ )
+ return output_path
+
+ def run_and_assert_no_traceback(self, cmd):
+ """Run `cmd` and fail the test if lldb surfaced an unhandled Python
+ traceback while executing it. The command itself may still report
+ failure (e.g. a stub method returning None) without that being a
+ generator bug."""
+ self.runCmd(cmd, check=False)
+ output = self.res.GetOutput() + self.res.GetError()
+ self.assertNotIn(
+ "Traceback (most recent call last)",
+ output,
+ f"command {cmd!r} raised a Python exception:\n{output}",
+ )
+
+ def test_generate_operating_system(self):
+ """OperatingSystem: generate, import, and activate via the
+ target.process.python-os-plugin-path setting."""
+ self.build()
+ path = self.generate_extension("OperatingSystem")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c"))
+
+ self.run_and_assert_no_traceback(
+ f"settings set target.process.python-os-plugin-path {path}"
+ )
+ self.run_and_assert_no_traceback("thread list")
+ self.runCmd("settings clear target.process.python-os-plugin-path", check=False)
+
+ def test_generate_scripted_platform(self):
+ """ScriptedPlatform: generate, import, and select via `platform
+ select`."""
+ path = self.generate_extension("ScriptedPlatform")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ self.run_and_assert_no_traceback(
+ "platform select scripted-platform -C "
+ "generated_ScriptedPlatform.TestScriptedPlatform"
+ )
+
+ def test_generate_scripted_process(self):
+ """ScriptedProcess: generate, import, and launch via `process launch
+ -C`."""
+ self.build()
+ path = self.generate_extension("ScriptedProcess")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+ self.assertTrue(target, VALID_TARGET)
+
+ self.run_and_assert_no_traceback(
+ "process launch -C generated_ScriptedProcess.TestScriptedProcess"
+ )
+
+ def test_generate_scripted_thread_plan(self):
+ """ScriptedThreadPlan: generate, import, and drive via `thread
+ step-scripted -C`."""
+ self.build()
+ path = self.generate_extension("ScriptedThreadPlan")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c"))
+
+ self.run_and_assert_no_traceback(
+ "thread step-scripted -C "
+ "generated_ScriptedThreadPlan.TestScriptedThreadPlan"
+ )
+
+ def test_generate_scripted_breakpoint_resolver(self):
+ """ScriptedBreakpointResolver: generate, import, and set via
+ `breakpoint set -P`."""
+ self.build()
+ path = self.generate_extension("ScriptedBreakpointResolver")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+ self.assertTrue(target, VALID_TARGET)
+
+ self.run_and_assert_no_traceback(
+ "breakpoint set -P "
+ "generated_ScriptedBreakpointResolver.TestScriptedBreakpointResolver"
+ )
+
+ def test_generate_scripted_hook_for_stop_hook(self):
+ """ScriptedHook drives `target stop-hook add -P` (the stop-hook is a
+ subset of the general scripted hook interface)."""
+ self.build()
+ path = self.generate_extension("ScriptedHook")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+ self.assertTrue(target, VALID_TARGET)
+
+ self.run_and_assert_no_traceback(
+ "target stop-hook add -P generated_ScriptedHook.TestScriptedHook"
+ )
+
+ def test_generate_scripted_frame_provider(self):
+ """ScriptedFrameProvider: generate, import, and register via `target
+ frame-provider register -C`. Uses -a to generate all methods since
+ `get_description` is a required static abstract method."""
+ self.build()
+ path = self.generate_extension("ScriptedFrameProvider", generate_all=True)
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c"))
+
+ self.run_and_assert_no_traceback(
+ "target frame-provider register -C "
+ "generated_ScriptedFrameProvider.TestScriptedFrameProvider"
+ )
+
+ def test_generate_scripted_hook(self):
+ """ScriptedHook: generate, import, and register via `target hook add
+ -P`."""
+ self.build()
+ path = self.generate_extension("ScriptedHook")
+ self.run_and_assert_no_traceback(f"command script import {path}")
+
+ target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+ self.assertTrue(target, VALID_TARGET)
+
+ self.run_and_assert_no_traceback(
+ "target hook add -P generated_ScriptedHook.TestScriptedHook"
+ )
diff --git a/lldb/test/API/functionalities/scripting_extension_generate/main.c b/lldb/test/API/functionalities/scripting_extension_generate/main.c
new file mode 100644
index 0000000000000..e194b3bf9ec3f
--- /dev/null
+++ b/lldb/test/API/functionalities/scripting_extension_generate/main.c
@@ -0,0 +1,12 @@
+#include <stdio.h>
+
+int foo(int x) {
+ int y = x + 1; // break here
+ return y;
+}
+
+int main() {
+ int result = foo(41);
+ printf("%d\n", result);
+ return 0;
+}
>From ecdfc01ac39d11402a909c1c4a968a711c539493 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Sun, 12 Jul 2026 17:28:34 -0700
Subject: [PATCH 2/2] [lldb/script] Migrate frame recognizers onto
ScriptedPythonInterface
Give `frame recognizer add -l` a formal ScriptedStackFrameRecognizerInterface,
matching the architecture already used by ScriptedProcess, ScriptedBreakpoint,
etc.: a C++ interface header, a Python-backed implementation built on
ScriptedPythonInterface's CreatePluginObject/Dispatch machinery, PluginManager
registration, and a generatable Python ABC template
(scripted_stackframe_recognizer.py).
get_recognized_arguments' documented contract returns a plain list of
lldb.SBValue (not an lldb.SBValueList), so it bypasses the generic
Dispatch<ValueObjectListSP>() extractor and calls the SWIG bridge directly,
matching the legacy behavior exactly.
ScriptedStackFrameRecognizer now holds a single interface object created once
in its constructor and reused across every RecognizeFrame() call, same
lifecycle as before.
---
lldb/bindings/python/CMakeLists.txt | 1 +
lldb/bindings/python/python-wrapper.swig | 47 ----------
lldb/docs/CMakeLists.txt | 1 +
.../scripted_stackframe_recognizer.py | 49 ++++++++++
.../ScriptedStackFrameRecognizerInterface.h | 30 ++++++
.../lldb/Interpreter/ScriptInterpreter.h | 21 +----
.../lldb/Target/StackFrameRecognizer.h | 3 +-
lldb/include/lldb/lldb-enumerations.h | 3 +-
lldb/include/lldb/lldb-forward.h | 3 +
lldb/source/Interpreter/ScriptInterpreter.cpp | 4 +
.../ScriptInterpreter/Python/CMakeLists.txt | 1 +
.../ScriptInterpreterPythonInterfaces.cpp | 2 +
.../ScriptInterpreterPythonInterfaces.h | 1 +
.../Interfaces/ScriptedPythonInterface.cpp | 40 +++++---
...tedStackFrameRecognizerPythonInterface.cpp | 69 ++++++++++++++
...iptedStackFrameRecognizerPythonInterface.h | 51 ++++++++++
.../Python/SWIGPythonBridge.h | 11 ---
.../Python/ScriptInterpreterPython.cpp | 93 ++-----------------
.../Python/ScriptInterpreterPythonImpl.h | 13 +--
lldb/source/Target/StackFrameRecognizer.cpp | 26 ++++--
.../Python/PythonTestSuite.cpp | 12 ---
21 files changed, 277 insertions(+), 204 deletions(-)
create mode 100644 lldb/examples/python/templates/scripted_stackframe_recognizer.py
create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt
index 9a8a8d420e18a..d29b143c1408c 100644
--- a/lldb/bindings/python/CMakeLists.txt
+++ b/lldb/bindings/python/CMakeLists.txt
@@ -119,6 +119,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py"
+ "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
)
if(APPLE)
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index dea4f6b4c7f7c..2392737402e20 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -798,53 +798,6 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
return pfunc(SWIGBridge::ToSWIGWrapper(process_sp));
}
-PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
- const char *python_class_name, const char *session_dictionary_name) {
- if (python_class_name == NULL || python_class_name[0] == '\0' ||
- !session_dictionary_name)
- return PythonObject();
-
- PyErr_Cleaner py_err_cleaner(true);
-
- auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
- session_dictionary_name);
- auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(
- python_class_name, dict);
-
- if (!pfunc.IsAllocated())
- return PythonObject();
-
- return pfunc();
-}
-
-PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
- PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
- static char callee_name[] = "get_recognized_arguments";
-
- PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
-
- PythonString str(callee_name);
- PyObject *result =
- PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
- return result;
-}
-
-bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide(
- PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
- static char callee_name[] = "should_hide";
-
- PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
-
- PythonString str(callee_name);
-
- PyObject *result =
- PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
- bool ret_val = result ? PyObject_IsTrue(result) : false;
- Py_XDECREF(result);
-
- return ret_val;
-}
-
void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting(
void *module, const char *setting, const lldb::TargetSP &target_sp) {
if (!module || !setting)
diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt
index 58c740f16bf78..dd091836dc1aa 100644
--- a/lldb/docs/CMakeLists.txt
+++ b/lldb/docs/CMakeLists.txt
@@ -32,6 +32,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
+ COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMENT "Copying lldb.py to pretend its a Python package.")
add_dependencies(lldb-python-doc-package swig_wrapper_python)
diff --git a/lldb/examples/python/templates/scripted_stackframe_recognizer.py b/lldb/examples/python/templates/scripted_stackframe_recognizer.py
new file mode 100644
index 0000000000000..6e31787a16b7f
--- /dev/null
+++ b/lldb/examples/python/templates/scripted_stackframe_recognizer.py
@@ -0,0 +1,49 @@
+from abc import ABCMeta, abstractmethod
+
+import lldb
+
+
+class ScriptedStackFrameRecognizer(metaclass=ABCMeta):
+ """
+ The base class for a scripted stack frame recognizer.
+
+ A frame recognizer allows you to provide extra information about a stack
+ frame, such as recognized arguments, used by commands like `bt`. Register
+ it with `frame recognizer add -l <ClassName> ...`.
+
+ Most of the base class methods are `@abstractmethod` that need to be
+ overwritten by the inheriting class.
+ """
+
+ def __init__(self):
+ """Construct a scripted stack frame recognizer.
+
+ Recognizers are constructed with no arguments and are shared across
+ every frame they're asked to recognize.
+ """
+ pass
+
+ @abstractmethod
+ def get_recognized_arguments(self, frame: lldb.SBFrame) -> list:
+ """Get the arguments recognized for this frame.
+
+ Args:
+ frame (lldb.SBFrame): The frame to inspect.
+
+ Returns:
+ list of lldb.SBValue: The recognized arguments, or an empty list
+ if none could be recognized.
+ """
+ pass
+
+ def should_hide(self, frame: lldb.SBFrame) -> bool:
+ """Whether this frame should be hidden when displaying backtraces.
+
+ Args:
+ frame (lldb.SBFrame): The frame to inspect.
+
+ Returns:
+ bool: `True` if this frame should be hidden, `False` otherwise.
+ Defaults to `False`.
+ """
+ return False
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
new file mode 100644
index 0000000000000..6612d40d9bf0d
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
+
+#include "ScriptedInterface.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+class ScriptedStackFrameRecognizerInterface : virtual public ScriptedInterface {
+public:
+ virtual llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(const ScriptedMetadata &scripted_metadata) = 0;
+
+ virtual lldb::ValueObjectListSP
+ GetRecognizedArguments(lldb::StackFrameSP frame_sp) {
+ return lldb::ValueObjectListSP();
+ }
+
+ virtual bool ShouldHide(lldb::StackFrameSP frame_sp) { return false; }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 12a915636ef92..e88439e8c9941 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -255,22 +255,6 @@ class ScriptInterpreter : public PluginInterface {
return StructuredData::GenericSP();
}
- virtual StructuredData::GenericSP
- CreateFrameRecognizer(const char *class_name) {
- return StructuredData::GenericSP();
- }
-
- virtual lldb::ValueObjectListSP GetRecognizedArguments(
- const StructuredData::ObjectSP &implementor,
- lldb::StackFrameSP frame_sp) {
- return lldb::ValueObjectListSP();
- }
-
- virtual bool ShouldHide(const StructuredData::ObjectSP &implementor,
- lldb::StackFrameSP frame_sp) {
- return false;
- }
-
virtual StructuredData::ObjectSP
LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
return StructuredData::ObjectSP();
@@ -584,6 +568,11 @@ class ScriptInterpreter : public PluginInterface {
return {};
}
+ virtual lldb::ScriptedStackFrameRecognizerInterfaceSP
+ CreateScriptedStackFrameRecognizerInterface() {
+ return {};
+ }
+
virtual StructuredData::ObjectSP
CreateStructuredDataFromScriptObject(ScriptObject obj) {
return {};
diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h
index 97a35778ae070..95e8a03cac96c 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -77,8 +77,7 @@ class StackFrameRecognizer
/// tracks a particular Python classobject, which will be asked to recognize
/// stack frames.
class ScriptedStackFrameRecognizer : public StackFrameRecognizer {
- lldb_private::ScriptInterpreter *m_interpreter;
- lldb_private::StructuredData::ObjectSP m_python_object_sp;
+ lldb::ScriptedStackFrameRecognizerInterfaceSP m_interface_sp;
std::string m_python_class;
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index b36ad09f2e892..93c252b55de99 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -267,7 +267,8 @@ enum ScriptedExtension {
eScriptedExtensionScriptedHook,
eScriptedExtensionScriptedThread,
eScriptedExtensionScriptedFrame,
- kLastScriptedExtension = eScriptedExtensionScriptedFrame
+ eScriptedExtensionScriptedStackFrameRecognizer,
+ kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer
};
/// Register numbering types.
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 6dbc095f15ef6..157aa5743f016 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -198,6 +198,7 @@ class ScriptedPlatformInterface;
class ScriptedProcessInterface;
class ScriptedThreadInterface;
class ScriptedThreadPlanInterface;
+class ScriptedStackFrameRecognizerInterface;
class ScriptedSyntheticChildren;
class SearchFilter;
class Section;
@@ -435,6 +436,8 @@ typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
ScriptedThreadPlanInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedBreakpointInterface>
ScriptedBreakpointInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedStackFrameRecognizerInterface>
+ ScriptedStackFrameRecognizerInterfaceSP;
typedef std::shared_ptr<lldb_private::Section> SectionSP;
typedef std::unique_ptr<lldb_private::SectionList> SectionListUP;
typedef std::weak_ptr<lldb_private::Section> SectionWP;
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index c73fbd8204f03..8328395f31423 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -221,6 +221,8 @@ ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) {
return "ScriptedThread";
case eScriptedExtensionScriptedFrame:
return "ScriptedFrame";
+ case eScriptedExtensionScriptedStackFrameRecognizer:
+ return "ScriptedStackFrameRecognizer";
}
llvm_unreachable("unhandled ScriptedExtension");
}
@@ -239,6 +241,8 @@ ScriptInterpreter::StringToExtension(llvm::StringRef string) {
.CaseLower("ScriptedHook", eScriptedExtensionScriptedHook)
.CaseLower("ScriptedThread", eScriptedExtensionScriptedThread)
.CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame)
+ .CaseLower("ScriptedStackFrameRecognizer",
+ eScriptedExtensionScriptedStackFrameRecognizer)
.Default(eScriptedExtensionInvalid);
}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index fb7d0cb82f8ca..201574da72a19 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -32,6 +32,7 @@ set(python_plugin_sources
Interfaces/ScriptedPythonInterface.cpp
Interfaces/ScriptedHookPythonInterface.cpp
Interfaces/ScriptedBreakpointPythonInterface.cpp
+ Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
Interfaces/ScriptedThreadPlanPythonInterface.cpp
Interfaces/ScriptedThreadPythonInterface.cpp
)
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
index cdbb624706f83..8914f6b239023 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
@@ -29,6 +29,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
ScriptedFrameProviderPythonInterface::Initialize();
ScriptedThreadPythonInterface::Initialize();
ScriptedFramePythonInterface::Initialize();
+ ScriptedStackFrameRecognizerPythonInterface::Initialize();
}
void ScriptInterpreterPythonInterfaces::Terminate() {
@@ -41,4 +42,5 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
ScriptedFrameProviderPythonInterface::Terminate();
ScriptedThreadPythonInterface::Terminate();
ScriptedFramePythonInterface::Terminate();
+ ScriptedStackFrameRecognizerPythonInterface::Terminate();
}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 9f6324d0503f7..03d747e63a592 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -19,6 +19,7 @@
#include "ScriptedHookPythonInterface.h"
#include "ScriptedPlatformPythonInterface.h"
#include "ScriptedProcessPythonInterface.h"
+#include "ScriptedStackFrameRecognizerPythonInterface.h"
#include "ScriptedThreadPlanPythonInterface.h"
#include "ScriptedThreadPythonInterface.h"
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index 4612b1939b5db..cc4ae22b265a3 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -305,22 +305,38 @@ template <>
lldb::ValueObjectListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
python::PythonObject &p, Status &error) {
- lldb::SBValueList *sb_value_list = reinterpret_cast<lldb::SBValueList *>(
- python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()));
-
- if (!sb_value_list) {
- error = Status::FromErrorStringWithFormat(
- "couldn't cast lldb::SBValueList to lldb::ValueObjectListSP");
- return {};
+ // Two Python return shapes are accepted here so callers can go through
+ // Dispatch<ValueObjectListSP>() uniformly: an `SBValueList` wrapper
+ // (what most extension methods return) and a plain Python `list` of
+ // `SBValue` (what `get_recognized_arguments` is documented to return).
+ lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>();
+ if (auto *sb_value_list = reinterpret_cast<lldb::SBValueList *>(
+ python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()))) {
+ for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) {
+ SBValue value = sb_value_list->GetValueAtIndex(i);
+ out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value));
+ }
+ return out;
}
- lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>();
- for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) {
- SBValue value = sb_value_list->GetValueAtIndex(i);
- out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value));
+ if (PyList_Check(p.get())) {
+ python::PythonList result_list(python::PyRefType::Borrowed, p.get());
+ for (size_t i = 0, e = result_list.GetSize(); i < e; ++i) {
+ PyObject *item = result_list.GetItemAtIndex(i).get();
+ auto *sb_value = reinterpret_cast<lldb::SBValue *>(
+ python::LLDBSWIGPython_CastPyObjectToSBValue(item));
+ if (!sb_value)
+ continue;
+ if (auto valobj_sp =
+ m_interpreter.GetOpaqueTypeFromSBValue(*sb_value))
+ out->Append(valobj_sp);
+ }
+ return out;
}
- return out;
+ error = Status::FromErrorStringWithFormat(
+ "couldn't extract ValueObjectList from Python return value");
+ return {};
}
template <>
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
new file mode 100644
index 0000000000000..cbe577a33b1fe
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "../lldb-python.h"
+
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Target/StackFrame.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "ScriptedStackFrameRecognizerPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+
+ScriptedStackFrameRecognizerPythonInterface::
+ ScriptedStackFrameRecognizerPythonInterface(
+ ScriptInterpreterPythonImpl &interpreter)
+ : ScriptedStackFrameRecognizerInterface(), ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedStackFrameRecognizerPythonInterface::CreatePluginObject(
+ const ScriptedMetadata &scripted_metadata) {
+ return ScriptedPythonInterface::CreatePluginObject(scripted_metadata,
+ nullptr);
+}
+
+lldb::ValueObjectListSP
+ScriptedStackFrameRecognizerPythonInterface::GetRecognizedArguments(
+ lldb::StackFrameSP frame_sp) {
+ Status error;
+ return Dispatch<lldb::ValueObjectListSP>("get_recognized_arguments", error,
+ frame_sp);
+}
+
+bool ScriptedStackFrameRecognizerPythonInterface::ShouldHide(
+ lldb::StackFrameSP frame_sp) {
+ Status error;
+ StructuredData::ObjectSP obj = Dispatch("should_hide", error, frame_sp);
+
+ // should_hide is optional on the Python side; a missing/failed method
+ // call is not an error, it just means "don't hide" (matches the default
+ // in the interface and in the ABC template).
+ if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+ error))
+ return false;
+
+ return obj->GetBooleanValue();
+}
+
+void ScriptedStackFrameRecognizerPythonInterface::Initialize() {
+ PluginManager::RegisterPlugin(
+ GetPluginNameStatic(),
+ "Recognize a stack frame and provide extra information about it "
+ "(e.g. recognized arguments), used by 'frame recognizer add -l'",
+ CreateInstance, eScriptedExtensionScriptedStackFrameRecognizer,
+ eScriptLanguagePython, {});
+}
+
+void ScriptedStackFrameRecognizerPythonInterface::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
new file mode 100644
index 0000000000000..bf7d86a2007d7
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
+#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
+
+#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h"
+
+#include "ScriptedPythonInterface.h"
+namespace lldb_private {
+
+class ScriptedStackFrameRecognizerPythonInterface
+ : public ScriptedStackFrameRecognizerInterface,
+ public ScriptedPythonInterface,
+ public PluginInterface {
+public:
+ ScriptedStackFrameRecognizerPythonInterface(
+ ScriptInterpreterPythonImpl &interpreter);
+
+ llvm::Expected<StructuredData::GenericSP>
+ CreatePluginObject(const ScriptedMetadata &scripted_metadata) override;
+
+ llvm::SmallVector<AbstractMethodRequirement>
+ GetAbstractMethodRequirements() const override {
+ return llvm::SmallVector<AbstractMethodRequirement>(
+ {{"get_recognized_arguments", 2}});
+ }
+
+ lldb::ValueObjectListSP
+ GetRecognizedArguments(lldb::StackFrameSP frame_sp) override;
+
+ bool ShouldHide(lldb::StackFrameSP frame_sp) override;
+
+ static void Initialize();
+
+ static void Terminate();
+
+ static llvm::StringRef GetPluginNameStatic() {
+ return "ScriptedStackFrameRecognizerPythonInterface";
+ }
+
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index ce9e12dbf7d3d..07e0da1dcf70d 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -214,17 +214,6 @@ class SWIGBridge {
const char *session_dictionary_name,
const lldb::ProcessSP &process_sp);
- static python::PythonObject
- LLDBSWIGPython_CreateFrameRecognizer(const char *python_class_name,
- const char *session_dictionary_name);
-
- static PyObject *
- LLDBSwigPython_GetRecognizedArguments(PyObject *implementor,
- const lldb::StackFrameSP &frame_sp);
-
- static bool LLDBSwigPython_ShouldHide(PyObject *implementor,
- const lldb::StackFrameSP &frame_sp);
-
static bool LLDBSWIGPythonRunScriptKeywordProcess(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ProcessSP &process, std::string &output);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 9d5d8f106a10e..f172123a3d5ac 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -287,6 +287,8 @@ llvm::Expected<std::string> ScriptInterpreterPython::ExtensionToImportPath(
case eScriptedExtensionScriptedThread:
case eScriptedExtensionScriptedFrame:
return "lldb.plugins.scripted_process";
+ case eScriptedExtensionScriptedStackFrameRecognizer:
+ return "lldb.plugins.scripted_stackframe_recognizer";
case eScriptedExtensionInvalid:
return llvm::createStringError("invalid extension name");
}
@@ -1969,92 +1971,6 @@ bool ScriptInterpreterPythonImpl::GenerateTypeSynthClass(
return true;
}
-StructuredData::GenericSP
-ScriptInterpreterPythonImpl::CreateFrameRecognizer(const char *class_name) {
- if (class_name == nullptr || class_name[0] == '\0')
- return StructuredData::GenericSP();
-
- Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
- PythonObject ret_val = SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
- class_name, m_dictionary_name.c_str());
-
- return StructuredData::GenericSP(
- new StructuredPythonObject(std::move(ret_val)));
-}
-
-lldb::ValueObjectListSP ScriptInterpreterPythonImpl::GetRecognizedArguments(
- const StructuredData::ObjectSP &os_plugin_object_sp,
- lldb::StackFrameSP frame_sp) {
- Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
-
- if (!os_plugin_object_sp)
- return ValueObjectListSP();
-
- StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric();
- if (!generic)
- return nullptr;
-
- PythonObject implementor(PyRefType::Borrowed,
- (PyObject *)generic->GetValue());
-
- if (!implementor.IsAllocated())
- return ValueObjectListSP();
-
- PythonObject py_return(PyRefType::Owned,
- SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
- implementor.get(), frame_sp));
-
- // if it fails, print the error but otherwise go on
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
- if (py_return.get()) {
- PythonList result_list(PyRefType::Borrowed, py_return.get());
- ValueObjectListSP result = std::make_shared<ValueObjectList>();
- for (size_t i = 0; i < result_list.GetSize(); i++) {
- PyObject *item = result_list.GetItemAtIndex(i).get();
- lldb::SBValue *sb_value_ptr =
- (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(item);
- auto valobj_sp =
- SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(sb_value_ptr);
- if (valobj_sp)
- result->Append(valobj_sp);
- }
- return result;
- }
- return ValueObjectListSP();
-}
-
-bool ScriptInterpreterPythonImpl::ShouldHide(
- const StructuredData::ObjectSP &os_plugin_object_sp,
- lldb::StackFrameSP frame_sp) {
- Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
-
- if (!os_plugin_object_sp)
- return false;
-
- StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric();
- if (!generic)
- return false;
-
- PythonObject implementor(PyRefType::Borrowed,
- (PyObject *)generic->GetValue());
-
- if (!implementor.IsAllocated())
- return false;
-
- bool result =
- SWIGBridge::LLDBSwigPython_ShouldHide(implementor.get(), frame_sp);
-
- // if it fails, print the error but otherwise go on
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
- return result;
-}
-
ScriptedProcessInterfaceUP
ScriptInterpreterPythonImpl::CreateScriptedProcessInterface() {
return std::make_unique<ScriptedProcessPythonInterface>(*this);
@@ -2070,6 +1986,11 @@ ScriptInterpreterPythonImpl::CreateScriptedBreakpointInterface() {
return std::make_shared<ScriptedBreakpointPythonInterface>(*this);
}
+ScriptedStackFrameRecognizerInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedStackFrameRecognizerInterface() {
+ return std::make_shared<ScriptedStackFrameRecognizerPythonInterface>(*this);
+}
+
ScriptedThreadInterfaceSP
ScriptInterpreterPythonImpl::CreateScriptedThreadInterface() {
return std::make_shared<ScriptedThreadPythonInterface>(*this);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index 353332a784e60..d8a817198253f 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -76,16 +76,6 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
StructuredData::ObjectSP
CreateStructuredDataFromScriptObject(ScriptObject obj) override;
- StructuredData::GenericSP
- CreateFrameRecognizer(const char *class_name) override;
-
- lldb::ValueObjectListSP
- GetRecognizedArguments(const StructuredData::ObjectSP &implementor,
- lldb::StackFrameSP frame_sp) override;
-
- bool ShouldHide(const StructuredData::ObjectSP &implementor,
- lldb::StackFrameSP frame_sp) override;
-
lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override;
lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override;
@@ -93,6 +83,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
lldb::ScriptedBreakpointInterfaceSP
CreateScriptedBreakpointInterface() override;
+ lldb::ScriptedStackFrameRecognizerInterfaceSP
+ CreateScriptedStackFrameRecognizerInterface() override;
+
lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override;
diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp
index f297c71ff44d7..35cca41a694ea 100644
--- a/lldb/source/Target/StackFrameRecognizer.cpp
+++ b/lldb/source/Target/StackFrameRecognizer.cpp
@@ -8,10 +8,12 @@
#include "lldb/Target/StackFrameRecognizer.h"
#include "lldb/Core/Module.h"
+#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Utility/RegularExpression.h"
+#include "lldb/Utility/ScriptedMetadata.h"
using namespace lldb;
using namespace lldb_private;
@@ -29,18 +31,28 @@ class ScriptedRecognizedStackFrame : public RecognizedStackFrame {
ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer(
ScriptInterpreter *interpreter, const char *pclass)
- : m_interpreter(interpreter), m_python_class(pclass) {
- m_python_object_sp =
- m_interpreter->CreateFrameRecognizer(m_python_class.c_str());
+ : m_python_class(pclass) {
+ if (!interpreter)
+ return;
+
+ m_interface_sp = interpreter->CreateScriptedStackFrameRecognizerInterface();
+ if (!m_interface_sp)
+ return;
+
+ ScriptedMetadata scripted_metadata(m_python_class, nullptr);
+ auto obj_or_err = m_interface_sp->CreatePluginObject(scripted_metadata);
+ if (!obj_or_err) {
+ llvm::consumeError(obj_or_err.takeError());
+ m_interface_sp.reset();
+ }
}
RecognizedStackFrameSP
ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
- if (!m_python_object_sp || !m_interpreter)
+ if (!m_interface_sp)
return RecognizedStackFrameSP();
- ValueObjectListSP args =
- m_interpreter->GetRecognizedArguments(m_python_object_sp, frame);
+ ValueObjectListSP args = m_interface_sp->GetRecognizedArguments(frame);
auto args_synthesized = std::make_shared<ValueObjectList>();
if (args) {
for (const auto &o : args->GetObjects())
@@ -48,7 +60,7 @@ ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
*o, eValueTypeVariableArgument));
}
- bool hidden = m_interpreter->ShouldHide(m_python_object_sp, frame);
+ bool hidden = m_interface_sp->ShouldHide(frame);
return RecognizedStackFrameSP(
new ScriptedRecognizedStackFrame(args_synthesized, hidden));
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index 1ed6bee384a84..c9298191ec3c1 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -260,18 +260,6 @@ lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
return python::PythonObject();
}
-python::PythonObject
-lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
- const char *python_class_name, const char *session_dictionary_name) {
- return python::PythonObject();
-}
-
-PyObject *
-lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
- PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
- return nullptr;
-}
-
bool lldb_private::python::SWIGBridge::LLDBSWIGPythonRunScriptKeywordProcess(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ProcessSP &process, std::string &output) {
More information about the lldb-commits
mailing list