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 <[email protected]> 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 <[email protected]> --- 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 <[email protected]> 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) { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
