https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/195351
>From e5d73eaaaef661f373a3959b34a5b78d9862e0c8 Mon Sep 17 00:00:00 2001 From: Dave Lee <[email protected]> Date: Fri, 1 May 2026 13:45:19 -0700 Subject: [PATCH 1/2] [lldb] Add lldb.summary and lldb.synthetic decorators --- lldb/bindings/python/python-extensions.swig | 44 +++++++++++++++++++ lldb/bindings/python/python-wrapper.swig | 21 +++++++-- .../decorator-formatters/Makefile | 2 + .../TestDecoratorFormatters.py | 31 +++++++++++++ .../decorator-formatters/formatters.py | 29 ++++++++++++ .../decorator-formatters/main.cpp | 15 +++++++ 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py create mode 100644 lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp diff --git a/lldb/bindings/python/python-extensions.swig b/lldb/bindings/python/python-extensions.swig index 40fa76872ee96..7f5cf520223f0 100644 --- a/lldb/bindings/python/python-extensions.swig +++ b/lldb/bindings/python/python-extensions.swig @@ -606,3 +606,47 @@ def is_numeric_type(basic_type): return (False,False) %} + +%pythoncode %{ + +def _type_spec(type_name, regex): + type_spec = f"'{type_name}'" + if regex: + type_spec = f"-x {type_spec}" + return type_spec + +def summary(type_name, regex=False): + """A decorator that registers a function as an LLDB type summary provider. + + @lldb.summary("MyType") + def MyTypeSummary(valobj, internal_dict): + return "summary string" + """ + type_spec = _type_spec(type_name, regex) + def decorator(func): + qualified = f"{func.__module__}.{func.__qualname__}" + func._lldb_register_command = f"type summary add -F {qualified} {type_spec}" + return func + return decorator + +def synthetic(type_name, regex=False): + """A decorator that registers a class as an LLDB synthetic child provider. + + @lldb.synthetic("MyType") + class MyTypeSynthetic: + def __init__(self, valobj, internal_dict): + ... + """ + type_spec = _type_spec(type_name, regex) + def decorator(cls): + qualified = f"{cls.__module__}.{cls.__qualname__}" + cls._lldb_register_command = f"type synthetic add -l {qualified} {type_spec}" + return cls + return decorator + +def _register_lldb_decorators(module, debugger): + for obj in vars(module).values(): + if cmd := getattr(obj, '_lldb_register_command', None): + debugger.HandleCommand(cmd) + +%} diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 72f90f1b23c29..db19604ca51c4 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -1007,10 +1007,23 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit( // This method is optional and need not exist. So if we don't find it, // it's actually a success, not a failure. - if (!pfunc.IsAllocated()) - return true; - - pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), dict); + if (pfunc.IsAllocated()) + pfunc(SWIGBridge::ToSWIGWrapper(debugger), dict); + + // Scan the module for @lldb.summary and @lldb.synthetic decorators. + auto lldb_module = + unwrapIgnoringErrors(PythonModule::Import("lldb")); + if (lldb_module.IsAllocated()) { + auto register_func = + lldb_module.ResolveName<PythonCallable>("_register_lldb_decorators"); + if (register_func.IsAllocated()) { + auto user_module = + unwrapIgnoringErrors(PythonModule::Import(python_module_name)); + if (user_module.IsAllocated()) + register_func(std::move(user_module), + SWIGBridge::ToSWIGWrapper(std::move(debugger))); + } + } return true; } diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile b/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile new file mode 100644 index 0000000000000..3d0b98f13f3d7 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/Makefile @@ -0,0 +1,2 @@ +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py b/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py new file mode 100644 index 0000000000000..14c5584d02f81 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/TestDecoratorFormatters.py @@ -0,0 +1,31 @@ +""" +Test @lldb.summary and @lldb.synthetic decorators lead to automatic formatter +registration, when using `command script import`. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_summary(self): + self.build() + self.runCmd("command script import formatters.py") + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.cpp") + ) + frame = thread.selected_frame + p = frame.var("p") + self.assertEqual(p.summary, "(1, 2)") + + def test_synthetic(self): + self.build() + self.runCmd("command script import formatters.py") + lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.cpp") + ) + self.expect("v c", substrs=["[0] = 10", "[1] = 20", "[2] = 30"]) diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py b/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py new file mode 100644 index 0000000000000..f45f96a27f3ae --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/formatters.py @@ -0,0 +1,29 @@ +import lldb + + [email protected]("Pair") +def pair_summary(valobj, _): + first = valobj.GetChildMemberWithName("first").GetValueAsSigned() + second = valobj.GetChildMemberWithName("second").GetValueAsSigned() + return f"({first}, {second})" + + [email protected]("Container") +class ContainerSyntheticProvider: + valobj: lldb.SBValue + count: int + items: lldb.SBValue + + def __init__(self, valobj: lldb.SBValue, _) -> None: + self.valobj = valobj + + def update(self) -> bool: + self.count = self.valobj.GetChildMemberWithName("size").GetValueAsSigned() + self.items = self.valobj.GetChildMemberWithName("items") + return True + + def num_children(self) -> int: + return self.count + + def get_child_at_index(self, index: int) -> lldb.SBValue: + return self.items.GetChildAtIndex(index) diff --git a/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp b/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp new file mode 100644 index 0000000000000..ccd43df0aafaa --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/decorator-formatters/main.cpp @@ -0,0 +1,15 @@ +struct Pair { + int first; + int second; +}; + +struct Container { + int items[3]; + int size; +}; + +int main() { + Pair p = {1, 2}; + Container c = {{10, 20, 30}, 3}; + return 0; // break here +} >From 307fe8533c96fea2a2bab2c97dbee7a45a298776 Mon Sep 17 00:00:00 2001 From: Dave Lee <[email protected]> Date: Thu, 7 May 2026 15:46:11 -0700 Subject: [PATCH 2/2] Refactor to call HandleCommand immediately, via CommandInterpreter --- lldb/bindings/python/python-extensions.swig | 94 ++++++++++++--------- lldb/bindings/python/python-wrapper.swig | 21 +---- 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/lldb/bindings/python/python-extensions.swig b/lldb/bindings/python/python-extensions.swig index 7f5cf520223f0..a1a923714a3f9 100644 --- a/lldb/bindings/python/python-extensions.swig +++ b/lldb/bindings/python/python-extensions.swig @@ -282,6 +282,60 @@ def command(command_name=None, doc=None): return callable + +def _add_formatter(kind: str, type_name: str, options: dict): + import shlex + + type_name = shlex.quote(type_name) + + flag_list = [] + # Convert `option=True` to "--option", all other cases convert to "--option value". + for key, value in options.values(): + flag = key.replace("_", "-") + if value is True: + flag_list.append(f"--{flag}") + else: + flag_list.extend((f"--{flag}", value)) + + def decorator(obj): + qualified = f"{obj.__module__}.{obj.__qualname__}" + if isinstance(obj, type): + flag_list.extend(("--python-class", qualified)) + elif callable(obj): + flag_list.extend(("--python-function", qualified)) + + result = SBCommandReturnObject() + flags = " ".join(flag_list) + interp = debugger.GetCommandInterpreter() + interp.HandleCommand(f"type {kind} add {flags} {type_name}", result) + if not result.Succeeded(): + raise RuntimeError(result.GetError()) + return obj + + return decorator + + +def summary(type_name, **kwargs): + """A decorator that registers a function as an LLDB type summary provider. + + @lldb.summary("MyType") + def MyTypeSummary(valobj, _): + return "summary string" + """ + return _add_formatter("summary", type_name, kwargs) + + +def synthetic(type_name, **kwargs): + """A decorator that registers a class as an LLDB synthetic child provider. + + @lldb.synthetic("MyType") + class MyTypeSynthetic: + def __init__(self, valobj, _): + ... + """ + return _add_formatter("synthetic", type_name, kwargs) + + class declaration(object): '''A class that represents a source declaration location with file, line and column.''' def __init__(self, file, line, col): @@ -609,44 +663,4 @@ def is_numeric_type(basic_type): %pythoncode %{ -def _type_spec(type_name, regex): - type_spec = f"'{type_name}'" - if regex: - type_spec = f"-x {type_spec}" - return type_spec - -def summary(type_name, regex=False): - """A decorator that registers a function as an LLDB type summary provider. - - @lldb.summary("MyType") - def MyTypeSummary(valobj, internal_dict): - return "summary string" - """ - type_spec = _type_spec(type_name, regex) - def decorator(func): - qualified = f"{func.__module__}.{func.__qualname__}" - func._lldb_register_command = f"type summary add -F {qualified} {type_spec}" - return func - return decorator - -def synthetic(type_name, regex=False): - """A decorator that registers a class as an LLDB synthetic child provider. - - @lldb.synthetic("MyType") - class MyTypeSynthetic: - def __init__(self, valobj, internal_dict): - ... - """ - type_spec = _type_spec(type_name, regex) - def decorator(cls): - qualified = f"{cls.__module__}.{cls.__qualname__}" - cls._lldb_register_command = f"type synthetic add -l {qualified} {type_spec}" - return cls - return decorator - -def _register_lldb_decorators(module, debugger): - for obj in vars(module).values(): - if cmd := getattr(obj, '_lldb_register_command', None): - debugger.HandleCommand(cmd) - %} diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index db19604ca51c4..72f90f1b23c29 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -1007,23 +1007,10 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit( // This method is optional and need not exist. So if we don't find it, // it's actually a success, not a failure. - if (pfunc.IsAllocated()) - pfunc(SWIGBridge::ToSWIGWrapper(debugger), dict); - - // Scan the module for @lldb.summary and @lldb.synthetic decorators. - auto lldb_module = - unwrapIgnoringErrors(PythonModule::Import("lldb")); - if (lldb_module.IsAllocated()) { - auto register_func = - lldb_module.ResolveName<PythonCallable>("_register_lldb_decorators"); - if (register_func.IsAllocated()) { - auto user_module = - unwrapIgnoringErrors(PythonModule::Import(python_module_name)); - if (user_module.IsAllocated()) - register_func(std::move(user_module), - SWIGBridge::ToSWIGWrapper(std::move(debugger))); - } - } + if (!pfunc.IsAllocated()) + return true; + + pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), dict); return true; } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
