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

Reply via email to