https://github.com/mentlerd updated https://github.com/llvm/llvm-project/pull/209056
>From 9cf04299d36c7e917d99214a0b8ac247de659a10 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Sun, 12 Jul 2026 22:14:17 +0200 Subject: [PATCH 01/15] SBValue::SetTypeSynthetic --- lldb/include/lldb/API/SBValue.h | 8 ++++++++ lldb/source/API/SBValue.cpp | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h index 16a9f0837454c..a2aef2434a01b 100644 --- a/lldb/include/lldb/API/SBValue.h +++ b/lldb/include/lldb/API/SBValue.h @@ -132,6 +132,14 @@ class LLDB_API SBValue { lldb::SBTypeSynthetic GetTypeSynthetic(); + /// Override the `SBTypeSynthetic` chosen by the DataFormatter system for this + /// instance. + /// + /// This can be used to great effect in scripted synthetic children providers + /// where a child's underlying type can only be figured out by inspecting the + /// containing object's other members. + void SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic); + lldb::SBValue GetChildAtIndex(uint32_t idx); lldb::SBValue CreateChildAtOffset(const char *name, uint32_t offset, diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index de660577b3a9b..1b7fdb9b09078 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -393,6 +393,17 @@ lldb::SBTypeSynthetic SBValue::GetTypeSynthetic() { return synthetic; } +void SBValue::SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic) { + LLDB_INSTRUMENT_VA(this); + + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + lldb::ScriptedSyntheticChildrenSP synthetic_sp(synthetic.GetSP()); + if (value_sp && synthetic_sp) { + value_sp->SetSyntheticChildren(synthetic_sp); + } +} + lldb::SBValue SBValue::CreateChildAtOffset(const char *name, uint32_t offset, SBType type) { LLDB_INSTRUMENT_VA(this, name, offset, type); >From 37cce473899be58b336a6b17dd6b604ad11e4160 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Sun, 12 Jul 2026 22:16:18 +0200 Subject: [PATCH 02/15] SBValue::GetTypeSyntheticImplementation --- lldb/include/lldb/API/SBValue.h | 8 ++++++++ lldb/include/lldb/DataFormatters/TypeSynthetic.h | 4 ++++ lldb/include/lldb/ValueObject/ValueObject.h | 6 ++++++ .../lldb/ValueObject/ValueObjectSynthetic.h | 2 ++ lldb/source/API/SBValue.cpp | 14 ++++++++++++++ lldb/source/DataFormatters/TypeSynthetic.cpp | 10 ++++++++++ lldb/source/ValueObject/ValueObjectSynthetic.cpp | 5 +++++ 7 files changed, 49 insertions(+) diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h index a2aef2434a01b..9d746f74c9b09 100644 --- a/lldb/include/lldb/API/SBValue.h +++ b/lldb/include/lldb/API/SBValue.h @@ -140,6 +140,14 @@ class LLDB_API SBValue { /// containing object's other members. void SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic); + /// This function's primary use is to ease inspecting the internal state of + /// scripted synthetic children providers for debugging purposes. + /// + /// An other alternative usecase is for parent synthetic children providers to + /// imbue their children's synthetic children providers with additional + /// context after creation. + lldb::SBScriptObject GetTypeSyntheticImplementation(); + lldb::SBValue GetChildAtIndex(uint32_t idx); lldb::SBValue CreateChildAtOffset(const char *name, uint32_t offset, diff --git a/lldb/include/lldb/DataFormatters/TypeSynthetic.h b/lldb/include/lldb/DataFormatters/TypeSynthetic.h index ae44d7e8f96eb..9c17adbde7465 100644 --- a/lldb/include/lldb/DataFormatters/TypeSynthetic.h +++ b/lldb/include/lldb/DataFormatters/TypeSynthetic.h @@ -83,6 +83,8 @@ class SyntheticChildrenFrontEnd { // display purposes virtual ConstString GetSyntheticTypeName() { return ConstString(); } + virtual void *GetImplementation() { return nullptr; } + typedef std::shared_ptr<SyntheticChildrenFrontEnd> SharedPointer; typedef std::unique_ptr<SyntheticChildrenFrontEnd> UniquePointer; @@ -469,6 +471,8 @@ class ScriptedSyntheticChildren : public SyntheticChildren { ConstString GetSyntheticTypeName() override; + void *GetImplementation() override; + typedef std::shared_ptr<SyntheticChildrenFrontEnd> SharedPointer; private: diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h index 6e4d93b815761..583dd3faac482 100644 --- a/lldb/include/lldb/ValueObject/ValueObject.h +++ b/lldb/include/lldb/ValueObject/ValueObject.h @@ -917,6 +917,10 @@ class ValueObject { return m_synthetic_children_sp; } + virtual SyntheticChildrenFrontEnd *GetSyntheticChildrenFrontEnd() { + return nullptr; + } + // Use GetParent for display purposes, but if you want to tell the parent to // update itself then use m_parent. The ValueObjectDynamicValue's parent is // not the correct parent for displaying, they are really siblings, so for @@ -982,6 +986,8 @@ class ValueObject { lldb::ValueObjectSP CheckValueObjectOwnership(ValueObject *child); + virtual void *GetImplementation() { return nullptr; } + protected: typedef ClusterManager<ValueObject> ValueObjectManager; diff --git a/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h b/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h index 60803697ee020..32475aa02a5e8 100644 --- a/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h +++ b/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h @@ -128,6 +128,8 @@ class ValueObjectSynthetic : public ValueObject { GetExpressionPathFormat epformat = eGetExpressionPathFormatDereferencePointers) override; + SyntheticChildrenFrontEnd *GetSyntheticChildrenFrontEnd() override; + protected: bool UpdateValue() override; diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index 1b7fdb9b09078..c2509e89633df 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -404,6 +404,20 @@ void SBValue::SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic) { } } +lldb::SBScriptObject SBValue::GetTypeSyntheticImplementation() { + LLDB_INSTRUMENT_VA(this); + + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + + auto frontend = value_sp->GetSyntheticChildrenFrontEnd(); + if (!frontend) { + return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); + } + return lldb::SBScriptObject(frontend->GetImplementation(), + eScriptLanguageDefault); +} + lldb::SBValue SBValue::CreateChildAtOffset(const char *name, uint32_t offset, SBType type) { LLDB_INSTRUMENT_VA(this, name, offset, type); diff --git a/lldb/source/DataFormatters/TypeSynthetic.cpp b/lldb/source/DataFormatters/TypeSynthetic.cpp index 76eb61b92e446..53852fc291980 100644 --- a/lldb/source/DataFormatters/TypeSynthetic.cpp +++ b/lldb/source/DataFormatters/TypeSynthetic.cpp @@ -247,6 +247,16 @@ ConstString ScriptedSyntheticChildren::FrontEnd::GetSyntheticTypeName() { return m_interpreter->GetSyntheticTypeName(m_wrapper_sp); } +void *ScriptedSyntheticChildren::FrontEnd::GetImplementation() { + if (!m_wrapper_sp || m_interpreter == nullptr) + return nullptr; + + if (m_wrapper_sp->GetType() != eStructuredDataTypeGeneric) + return nullptr; + + return m_wrapper_sp->GetAsGeneric()->GetValue(); +} + std::string ScriptedSyntheticChildren::GetDescription() { StreamString sstr; sstr.Printf("%s%s%s Python class %s", Cascades() ? "" : " (not cascading)", diff --git a/lldb/source/ValueObject/ValueObjectSynthetic.cpp b/lldb/source/ValueObject/ValueObjectSynthetic.cpp index 1485dc1827480..74a3cfa4cc0f5 100644 --- a/lldb/source/ValueObject/ValueObjectSynthetic.cpp +++ b/lldb/source/ValueObject/ValueObjectSynthetic.cpp @@ -484,3 +484,8 @@ void ValueObjectSynthetic::GetExpressionPath(Stream &stream, } return ValueObject::GetExpressionPath(stream, epformat); } + +SyntheticChildrenFrontEnd * +ValueObjectSynthetic::GetSyntheticChildrenFrontEnd() { + return m_synth_filter_up.get(); +} >From 13da8a1b8dd449e0bf226cca4ab58bb468357c15 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Mon, 13 Jul 2026 22:11:40 +0200 Subject: [PATCH 03/15] Initial test coverage, found a couple of bugs --- .../sbvalue_set_type_synthetic/Makefile | 2 + .../TestSBValueSetTypeSynthetic.py | 52 +++++++++++ .../library_support.py | 85 +++++++++++++++++ .../sbvalue_set_type_synthetic/main.cpp | 91 +++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic/Makefile create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/Makefile b/lldb/test/API/python_api/sbvalue_set_type_synthetic/Makefile new file mode 100644 index 0000000000000..3d0b98f13f3d7 --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/Makefile @@ -0,0 +1,2 @@ +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py new file mode 100644 index 0000000000000..5bda14b9b4d8f --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py @@ -0,0 +1,52 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +from typing import Union + + +class TestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test(self): + self.build() + target, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.cpp") + ) + self.runCmd("command script import library_support.py") + + info = thread.GetFrameAtIndex(0).FindVariable("info") + self.assertTrue(info.IsValid()) + self.assertTrue(info.IsSynthetic()) + + # Synthetic attached to "info" can be interrogated + impl = info.GetTypeSyntheticImplementation() + self.assertEqual(type(impl).__name__, "SessionInfoSynthetic") + + # A synthetic was manually attached to "foos" + foos = info.GetChildMemberWithName("foos") + self.assertTrue(foos.IsValid()) + self.assertTrue(foos.IsSynthetic()) + self.assertEqual(foos.GetNumChildren(), 2) + + foos_impl = foos.GetTypeSyntheticImplementation() + self.assertEqual(type(foos_impl).__name__, "FooHandleArraySynthetic") + + # And it correcly converts to typed children + foos0 = foos.GetChildAtIndex(0) + self.assertTrue(foos0.IsValid()) + self.assertEqual(foos0.GetType(), target.FindFirstType("Foo").GetPointerType()) + + # Same with "bars" + bars = info.GetChildMemberWithName("bars") + self.assertTrue(bars.IsValid()) + self.assertTrue(bars.IsSynthetic()) + self.assertEqual(bars.GetNumChildren(), 1) + + bars_impl = bars.GetTypeSyntheticImplementation() + self.assertEqual(type(bars_impl).__name__, "BarHandleArraySynthetic") + + bars0 = bars.GetChildAtIndex(0) + self.assertTrue(bars0.IsValid()) + self.assertEqual(bars0.GetType(), target.FindFirstType("Bar").GetPointerType()) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py new file mode 100644 index 0000000000000..00738ad767061 --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py @@ -0,0 +1,85 @@ +import lldb + + [email protected]("SessionInfo") +class SessionInfoSynthetic: + """Type synthetic with additional information about the underlying object that isn't represented in the debug info""" + + valobj: lldb.SBValue + foos: lldb.SBValue + bars: lldb.SBValue + + def __init__(self, valobj: lldb.SBValue, _) -> None: + self.valobj = valobj + + def update(self) -> bool: + self.foos = self.valobj.GetChildMemberWithName("foos") + self.foos.GetTypeSynthetic() # TODO: BUG in my PR - Puzzlingly this needs to be called for Set to take effect? + self.foos.SetTypeSynthetic( + lldb.SBTypeSynthetic.CreateWithClassName( + "library_support.FooHandleArraySynthetic" + ) + ) + + # TODO: BUG in my PR - this crashes + # print(self.foos.GetTypeSyntheticImplementation()) + + self.bars = self.valobj.GetChildMemberWithName("bars") + self.bars.GetTypeSynthetic() # TODO: Remove + self.bars.SetTypeSynthetic( + lldb.SBTypeSynthetic.CreateWithClassName( + "library_support.BarHandleArraySynthetic" + ) + ) + return True + + def num_children(self) -> int: + return 2 + + def get_child_at_index(self, index: int) -> lldb.SBValue: + if index == 0: + return self.foos + if index == 1: + return self.bars + + def get_child_index(self, name: str) -> int: + if name == "foos": + return 0 + if name == "bars": + return 1 + + +class HandleArraySyntheticBase: + valobj: lldb.SBValue + valtype: lldb.SBType + array: lldb.SBValue + + def __init__(self, valobj: lldb.SBValue, valtype: lldb.SBType) -> None: + self.valobj = valobj + self.valtype = valtype + + self.size = 0 + self.array = lldb.SBValue() + + def update(self) -> bool: + self.size = self.valobj.GetChildMemberWithName("size").GetValueAsUnsigned(0) + + array_t = self.valtype.GetPointerType().GetArrayType(self.size).GetPointerType() + self.array = self.valobj.GetChildMemberWithName("data").Cast(array_t) + return True + + def num_children(self) -> int: + return self.size + + def get_child_at_index(self, index: int) -> lldb.SBValue: + return self.array.GetChildAtIndex(index) + + +class FooHandleArraySynthetic(HandleArraySyntheticBase): + def __init__(self, valobj: lldb.SBValue, _) -> None: + super().__init__(valobj, valobj.target.FindFirstType("Foo")) + + +class BarHandleArraySynthetic(HandleArraySyntheticBase): + def __init__(self, valobj: lldb.SBValue, _) -> None: + super().__init__(valobj, valobj.target.FindFirstType("Bar")) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp b/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp new file mode 100644 index 0000000000000..150eca4c5feec --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp @@ -0,0 +1,91 @@ + +// library_c_api.h - Mock type erased C API for library +#include <cstddef> + +typedef void* Handle; + +typedef Handle FooHandle; +typedef Handle BarHandle; + +// To minimize API surface, the C API has a single type for arrays of handles +struct HandleArray { + size_t size; + Handle* data; +}; + +struct SessionInfo { + // .. as a consequence we end up with cases where type information is only + // encoded in either variable names, or documentation + HandleArray foos; + HandleArray bars; +}; + +extern "C" { + void InitSession(); + void StopSession(); + + void ReadSessionInfo(SessionInfo* into); + void FreeSessionInfo(SessionInfo* info); +} + +// library.cpp +#include <memory> +#include <vector> + +struct Foo { + int foo; +}; +struct Bar { + bool bar; +}; + +static std::vector<std::unique_ptr<Foo>> g_foos; +static std::vector<std::unique_ptr<Bar>> g_bars; + +extern "C" { + void InitSession() { + g_foos.emplace_back(new Foo{ .foo = 10 }); + g_foos.emplace_back(new Foo{ .foo = 20 }); + g_bars.emplace_back(new Bar{ .bar = false }); + } + void StopSession() { + g_foos.clear(); + g_bars.clear(); + } + + void ReadSessionInfo(SessionInfo* into) { + into->foos.size = g_foos.size(); + into->foos.data = (Handle*) malloc(sizeof(Handle) * g_foos.size()); + for (size_t i = 0; i < g_foos.size(); i++) { + into->foos.data[i] = g_foos[i].get(); + } + + into->bars.size = g_bars.size(); + into->bars.data = (Handle*) malloc(sizeof(Handle) * g_bars.size()); + for (size_t i = 0; i < g_bars.size(); i++) { + into->bars.data[i] = g_bars[i].get(); + } + } + void FreeSessionInfo(SessionInfo* info) { + free(info->foos.data); + free(info->bars.data); + + info->foos.size = 0; + info->foos.data = NULL; + info->bars.size = 0; + info->bars.data = NULL; + } +} + +// app.cpp - being debugged by library.cpp authors + +int main() { + InitSession(); + + SessionInfo info; + ReadSessionInfo(&info); + FreeSessionInfo(&info); // break here + + StopSession(); + return 0; +} >From 0d6d20fdbe6e0bd7a83ad83f9ee5e152fbffd10e Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Mon, 13 Jul 2026 22:37:06 +0200 Subject: [PATCH 04/15] Adjust test to better demonstrate tricky case The interesting case occurs when SessionInfoSynthetic can't propagate the array's element type through coming up with an appropriate SBType. --- .../TestSBValueSetTypeSynthetic.py | 31 +++++++++++++------ .../library_support.py | 13 ++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py index 5bda14b9b4d8f..d2123a68459e4 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/TestSBValueSetTypeSynthetic.py @@ -16,6 +16,9 @@ def test(self): ) self.runCmd("command script import library_support.py") + foo_t = target.FindFirstType("Foo") + bar_t = target.FindFirstType("Bar") + info = thread.GetFrameAtIndex(0).FindVariable("info") self.assertTrue(info.IsValid()) self.assertTrue(info.IsSynthetic()) @@ -28,25 +31,35 @@ def test(self): foos = info.GetChildMemberWithName("foos") self.assertTrue(foos.IsValid()) self.assertTrue(foos.IsSynthetic()) - self.assertEqual(foos.GetNumChildren(), 2) foos_impl = foos.GetTypeSyntheticImplementation() self.assertEqual(type(foos_impl).__name__, "FooHandleArraySynthetic") - # And it correcly converts to typed children - foos0 = foos.GetChildAtIndex(0) - self.assertTrue(foos0.IsValid()) - self.assertEqual(foos0.GetType(), target.FindFirstType("Foo").GetPointerType()) + # And it correcly imbues it's "data" member with type information + foos_data = foos.GetChildMemberWithName("data") + self.assertTrue(foos_data.IsValid()) + self.assertEqual( + foos_data.GetType(), foo_t.GetPointerType().GetArrayType(2).GetPointerType() + ) + + foos_data0 = foos_data.Dereference().GetChildAtIndex(0).Dereference() + self.assertTrue(foos_data0.IsValid()) + self.assertEqual(foos_data0.GetType(), foo_t) # Same with "bars" bars = info.GetChildMemberWithName("bars") self.assertTrue(bars.IsValid()) self.assertTrue(bars.IsSynthetic()) - self.assertEqual(bars.GetNumChildren(), 1) bars_impl = bars.GetTypeSyntheticImplementation() self.assertEqual(type(bars_impl).__name__, "BarHandleArraySynthetic") - bars0 = bars.GetChildAtIndex(0) - self.assertTrue(bars0.IsValid()) - self.assertEqual(bars0.GetType(), target.FindFirstType("Bar").GetPointerType()) + bars_data = bars.GetChildMemberWithName("data") + self.assertTrue(bars_data.IsValid()) + self.assertEqual( + bars_data.GetType(), bar_t.GetPointerType().GetArrayType(1).GetPointerType() + ) + + bars_data0 = bars_data.Dereference().GetChildAtIndex(0).Dereference() + self.assertTrue(bars_data0.IsValid()) + self.assertTrue(bars_data0.GetType(), bar_t) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py index 00738ad767061..21821c5574917 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py @@ -69,10 +69,19 @@ def update(self) -> bool: return True def num_children(self) -> int: - return self.size + return 2 def get_child_at_index(self, index: int) -> lldb.SBValue: - return self.array.GetChildAtIndex(index) + if index == 0: + return self.valobj.GetChildMemberWithName("size") + if index == 1: + return self.array + + def get_child_index(self, name: str) -> int: + if name == "size": + return 0 + if name == "data": + return 1 class FooHandleArraySynthetic(HandleArraySyntheticBase): >From 75a256f60a27f11add86ec9095bf308e7f247245 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Tue, 14 Jul 2026 19:17:40 +0200 Subject: [PATCH 05/15] Allow dotest.py to call for Xcode to attach This makes debugging API tests much simpler. --- .../Python/lldbsuite/support/xcode.py | 24 +++++++++++++++++++ lldb/packages/Python/lldbsuite/test/dotest.py | 8 +++++-- .../Python/lldbsuite/test/dotest_args.py | 4 ++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 lldb/packages/Python/lldbsuite/support/xcode.py diff --git a/lldb/packages/Python/lldbsuite/support/xcode.py b/lldb/packages/Python/lldbsuite/support/xcode.py new file mode 100644 index 0000000000000..278e138d99cf5 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/support/xcode.py @@ -0,0 +1,24 @@ +import subprocess + + +def attach(pid: int, suspended: bool = False) -> None: + script = """ + on run argv + set targetPID to item 1 of argv as integer + set shouldSuspend to item 2 of argv as boolean + + tell application "Xcode" + activate + + if (count of workspace documents) is greater than 0 then + set debuggingWorkspace to workspace document 1 + else + set debuggingWorkspace to create temporary debugging workspace + end if + + attach debuggingWorkspace to process identifier targetPID suspended shouldSuspend + end tell + end run + """ + + subprocess.run(["osascript", "-e", script, str(pid), str(suspended)], check=True) diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py index b1145f8f96078..c6c1e4e8d4b54 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -44,7 +44,7 @@ from . import test_result from ..support import seven from ..support import temp_file - +from ..support import xcode def is_exe(fpath): """Returns true if fpath is an executable.""" @@ -381,12 +381,16 @@ def parseOptionsAndInitTestdirs(): setting_list = setting[0].split("=", 1) configuration.settings.append((setting_list[0], setting_list[1])) - if args.d: + if args.d or args.attach_xcode: sys.stdout.write( "Suspending the process %d to wait for debugger to attach...\n" % os.getpid() ) sys.stdout.flush() + + if args.attach_xcode: + xcode.attach(os.getpid()) + os.kill(os.getpid(), signal.SIGSTOP) if args.f: diff --git a/lldb/packages/Python/lldbsuite/test/dotest_args.py b/lldb/packages/Python/lldbsuite/test/dotest_args.py index 13b3d85628dc2..5d02b59b0f11e 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest_args.py +++ b/lldb/packages/Python/lldbsuite/test/dotest_args.py @@ -346,6 +346,10 @@ def create_parser(): "-d", "Suspend the process after launch to wait indefinitely for a debugger to attach", ) + X( + "--attach-xcode", + "Suspend the process after launch, and instruct Xcode to attach to it", + ) X("-t", "Turn on tracing of lldb command and other detailed test executions") group.add_argument( "-u", >From 8b413f8538abf927754df42243d554ff84988dec Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Tue, 14 Jul 2026 20:13:06 +0200 Subject: [PATCH 06/15] ValueObject::SetSyntheticChildrenOverride(...) The already existing function ValueObject::SetSyntheticChildren is driven by the DataVisualization system, and is designed to react to changes in the dynamic type and available DataFormatters. A new field and function were added that holds the "sticky" choice which overrules the default selection. This fixes the bug that necessitated calling SBValue::GetSyntheticChildren(), which disarmed the pending format update dictated by `m_last_format_mgr_revision`. --- lldb/include/lldb/ValueObject/ValueObject.h | 15 +++++++++++++++ lldb/source/API/SBValue.cpp | 4 ++-- lldb/source/ValueObject/ValueObject.cpp | 10 ++++++---- .../sbvalue_set_type_synthetic/library_support.py | 2 -- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h index 583dd3faac482..6032e56ee5c6a 100644 --- a/lldb/include/lldb/ValueObject/ValueObject.h +++ b/lldb/include/lldb/ValueObject/ValueObject.h @@ -912,8 +912,17 @@ class ValueObject { m_synthetic_children_sp = synth_sp; } + void SetSyntheticChildrenOverride(const lldb::SyntheticChildrenSP &synth_sp) { + if (synth_sp.get() == m_synthetic_children_override_sp.get()) + return; + ClearUserVisibleData(eClearUserVisibleDataItemsSyntheticChildren); + m_synthetic_children_override_sp = synth_sp; + } + lldb::SyntheticChildrenSP GetSyntheticChildren() { UpdateFormatsIfNeeded(); + if (m_synthetic_children_override_sp) + return m_synthetic_children_override_sp; return m_synthetic_children_sp; } @@ -1122,7 +1131,13 @@ class ValueObject { uint32_t m_last_format_mgr_revision = 0; lldb::TypeSummaryImplSP m_type_summary_sp; lldb::TypeFormatImplSP m_type_format_sp; + + /// As determined by `DataVisualization` - may be overridden lldb::SyntheticChildrenSP m_synthetic_children_sp; + + /// Sticky override of `m_synthetic_children_sp` + lldb::SyntheticChildrenSP m_synthetic_children_override_sp; + ProcessModID m_user_id_of_forced_summary; AddressType m_address_type_of_ptr_or_ref_children = eAddressTypeInvalid; diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index c2509e89633df..bec22dab689ec 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -399,8 +399,8 @@ void SBValue::SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic) { ValueLocker locker; lldb::ValueObjectSP value_sp(GetSP(locker)); lldb::ScriptedSyntheticChildrenSP synthetic_sp(synthetic.GetSP()); - if (value_sp && synthetic_sp) { - value_sp->SetSyntheticChildren(synthetic_sp); + if (value_sp) { + value_sp->SetSyntheticChildrenOverride(synthetic_sp); } } diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp index cac4933c64325..ac41a8b2a5ed6 100644 --- a/lldb/source/ValueObject/ValueObject.cpp +++ b/lldb/source/ValueObject/ValueObject.cpp @@ -2054,18 +2054,20 @@ void ValueObject::CalculateSyntheticValue() { return; } - lldb::SyntheticChildrenSP current_synth_sp(m_synthetic_children_sp); + lldb::SyntheticChildrenSP prev_synth_sp(GetSyntheticChildren()); if (!UpdateFormatsIfNeeded() && m_synthetic_value) return; - if (m_synthetic_children_sp.get() == nullptr) + lldb::SyntheticChildrenSP curr_synth_sp(GetSyntheticChildren()); + + if (curr_synth_sp.get() == nullptr) return; - if (current_synth_sp == m_synthetic_children_sp && m_synthetic_value) + if (curr_synth_sp == prev_synth_sp && m_synthetic_value) return; - m_synthetic_value = new ValueObjectSynthetic(*this, m_synthetic_children_sp); + m_synthetic_value = new ValueObjectSynthetic(*this, curr_synth_sp); } void ValueObject::CalculateDynamicValue(DynamicValueType use_dynamic) { diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py index 21821c5574917..b3656896b7509 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py @@ -14,7 +14,6 @@ def __init__(self, valobj: lldb.SBValue, _) -> None: def update(self) -> bool: self.foos = self.valobj.GetChildMemberWithName("foos") - self.foos.GetTypeSynthetic() # TODO: BUG in my PR - Puzzlingly this needs to be called for Set to take effect? self.foos.SetTypeSynthetic( lldb.SBTypeSynthetic.CreateWithClassName( "library_support.FooHandleArraySynthetic" @@ -25,7 +24,6 @@ def update(self) -> bool: # print(self.foos.GetTypeSyntheticImplementation()) self.bars = self.valobj.GetChildMemberWithName("bars") - self.bars.GetTypeSynthetic() # TODO: Remove self.bars.SetTypeSynthetic( lldb.SBTypeSynthetic.CreateWithClassName( "library_support.BarHandleArraySynthetic" >From dfdc48ba43b1de34bc7b7b74c7fc23c661febe9e Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Tue, 14 Jul 2026 20:41:48 +0200 Subject: [PATCH 07/15] Fix crash in SBValue::GetTypeSyntheticImplementation --- lldb/source/API/SBValue.cpp | 6 ++++-- .../sbvalue_set_type_synthetic/library_support.py | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index bec22dab689ec..61a4a1a6b1835 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -409,11 +409,13 @@ lldb::SBScriptObject SBValue::GetTypeSyntheticImplementation() { ValueLocker locker; lldb::ValueObjectSP value_sp(GetSP(locker)); + if (!value_sp) + return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); auto frontend = value_sp->GetSyntheticChildrenFrontEnd(); - if (!frontend) { + if (!frontend) return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); - } + return lldb::SBScriptObject(frontend->GetImplementation(), eScriptLanguageDefault); } diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py index b3656896b7509..a983d64b6c965 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/library_support.py @@ -20,9 +20,6 @@ def update(self) -> bool: ) ) - # TODO: BUG in my PR - this crashes - # print(self.foos.GetTypeSyntheticImplementation()) - self.bars = self.valobj.GetChildMemberWithName("bars") self.bars.SetTypeSynthetic( lldb.SBTypeSynthetic.CreateWithClassName( >From aa224d2442ccf0164d018208e21a97eda1ad4c1b Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Tue, 14 Jul 2026 21:56:58 +0200 Subject: [PATCH 08/15] Revert "Allow dotest.py to call for Xcode to attach" This reverts commit 75a256f60a27f11add86ec9095bf308e7f247245. Will raise the added convenience flag in a separate PR. --- .../Python/lldbsuite/support/xcode.py | 24 ------------------- lldb/packages/Python/lldbsuite/test/dotest.py | 8 ++----- .../Python/lldbsuite/test/dotest_args.py | 4 ---- 3 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 lldb/packages/Python/lldbsuite/support/xcode.py diff --git a/lldb/packages/Python/lldbsuite/support/xcode.py b/lldb/packages/Python/lldbsuite/support/xcode.py deleted file mode 100644 index 278e138d99cf5..0000000000000 --- a/lldb/packages/Python/lldbsuite/support/xcode.py +++ /dev/null @@ -1,24 +0,0 @@ -import subprocess - - -def attach(pid: int, suspended: bool = False) -> None: - script = """ - on run argv - set targetPID to item 1 of argv as integer - set shouldSuspend to item 2 of argv as boolean - - tell application "Xcode" - activate - - if (count of workspace documents) is greater than 0 then - set debuggingWorkspace to workspace document 1 - else - set debuggingWorkspace to create temporary debugging workspace - end if - - attach debuggingWorkspace to process identifier targetPID suspended shouldSuspend - end tell - end run - """ - - subprocess.run(["osascript", "-e", script, str(pid), str(suspended)], check=True) diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py index c6c1e4e8d4b54..b1145f8f96078 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -44,7 +44,7 @@ from . import test_result from ..support import seven from ..support import temp_file -from ..support import xcode + def is_exe(fpath): """Returns true if fpath is an executable.""" @@ -381,16 +381,12 @@ def parseOptionsAndInitTestdirs(): setting_list = setting[0].split("=", 1) configuration.settings.append((setting_list[0], setting_list[1])) - if args.d or args.attach_xcode: + if args.d: sys.stdout.write( "Suspending the process %d to wait for debugger to attach...\n" % os.getpid() ) sys.stdout.flush() - - if args.attach_xcode: - xcode.attach(os.getpid()) - os.kill(os.getpid(), signal.SIGSTOP) if args.f: diff --git a/lldb/packages/Python/lldbsuite/test/dotest_args.py b/lldb/packages/Python/lldbsuite/test/dotest_args.py index 5d02b59b0f11e..13b3d85628dc2 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest_args.py +++ b/lldb/packages/Python/lldbsuite/test/dotest_args.py @@ -346,10 +346,6 @@ def create_parser(): "-d", "Suspend the process after launch to wait indefinitely for a debugger to attach", ) - X( - "--attach-xcode", - "Suspend the process after launch, and instruct Xcode to attach to it", - ) X("-t", "Turn on tracing of lldb command and other detailed test executions") group.add_argument( "-u", >From e0b09970b7e8807787eeaa9267262c49c137ddb2 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Tue, 14 Jul 2026 22:44:03 +0200 Subject: [PATCH 09/15] Documentation --- lldb/docs/use/variable.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lldb/docs/use/variable.md b/lldb/docs/use/variable.md index 35096cf2f2cc7..b89af2fb81fcc 100644 --- a/lldb/docs/use/variable.md +++ b/lldb/docs/use/variable.md @@ -1341,6 +1341,43 @@ the flag `--recognizer-function`. (lldb) type synthetic add --python-class my_child_provider --recognizer-function is_generated_object ``` +## API-attached synthetic children class + +In some situations, a value's type or a program's debug information do not contain +sufficient information to determine the appropriate synthetic children provider. + +In these cases, you may use {any}`SBValue.SetTypeSynthetic` to manually attach +(or override the system's choice of) a synthetic children provider for a value. + +This can be done even without a corresponding registration with the `type synthetic add` +command. + +The attached provider instance can be retrieved with {any}`SBValue.GetTypeSyntheticImplementation` +for further customization or inspection. + +```python +import lldb + +class FooSyntheticChildren: + def __init__(self, valobj: lldb.SBValue, _) -> None: + self.valobj = valobj + self.bar = None + + ... + +value = lldb.frame.FindVariable("foo") +value.SetTypeSynthetic( + lldb.SBTypeSynthetic.CreateWithClassName( + f"{__name__}.FooSyntheticChildren" + ) +) + +impl = value.GetTypeSyntheticImplementation() +assert isinstance(impl, FooSyntheticChildren) + +impl.bar = 42 +``` + ## Objective-C Dynamic Type Discovery When doing Objective-C development, you may notice that some of your variables >From c2026ff10ff6f12672332a29cf6f043b64229ffe Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Wed, 15 Jul 2026 19:33:52 +0200 Subject: [PATCH 10/15] Documentation: synthetic children provider -> synthetic child provider --- lldb/docs/use/variable.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/docs/use/variable.md b/lldb/docs/use/variable.md index b89af2fb81fcc..5d75c58e6fa60 100644 --- a/lldb/docs/use/variable.md +++ b/lldb/docs/use/variable.md @@ -1341,13 +1341,13 @@ the flag `--recognizer-function`. (lldb) type synthetic add --python-class my_child_provider --recognizer-function is_generated_object ``` -## API-attached synthetic children class +## API-attached synthetic child provider In some situations, a value's type or a program's debug information do not contain -sufficient information to determine the appropriate synthetic children provider. +sufficient information to determine the appropriate synthetic child provider. In these cases, you may use {any}`SBValue.SetTypeSynthetic` to manually attach -(or override the system's choice of) a synthetic children provider for a value. +(or override the system's choice of) a synthetic child provider for a value. This can be done even without a corresponding registration with the `type synthetic add` command. >From 0cf1fb63117b444b45f4fac93d28d2e705ce8a29 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Wed, 15 Jul 2026 19:21:18 +0200 Subject: [PATCH 11/15] Add test for SCP overriding --- .../Makefile | 2 + .../TestSBValueSetTypeSyntheticOverride.py | 50 +++++++++++++++++++ .../foo_bar_synths.py | 16 ++++++ .../main.cpp | 17 +++++++ 4 files changed, 85 insertions(+) create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic_override/Makefile create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic_override/foo_bar_synths.py create mode 100644 lldb/test/API/python_api/sbvalue_set_type_synthetic_override/main.cpp diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/Makefile b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/Makefile new file mode 100644 index 0000000000000..3d0b98f13f3d7 --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/Makefile @@ -0,0 +1,2 @@ +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py new file mode 100644 index 0000000000000..e147b73aee656 --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py @@ -0,0 +1,50 @@ +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(self): + self.build() + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.cpp") + ) + self.runCmd("command script import foo_bar_synths.py") + + frame = thread.GetFrameAtIndex(0) + + # CXX runtime synthetic can be overridden + vec = frame.FindVariable("vec") + self.assertTrue(vec.IsSynthetic()) + self.checkOverride(vec, before=None) + + # Python synthetic can be overridden + foo = frame.FindVariable("foo") + self.assertTrue(foo.IsSynthetic()) + self.checkOverride(foo, before="FooSynthetic") + + # No synthetic can be overridden + bar = frame.FindVariable("bar") + self.assertFalse(bar.IsSynthetic()) + self.checkOverride(bar, before=None) + + def checkOverride(self, value, before): + bar = lldb.SBTypeSynthetic.CreateWithClassName(f"foo_bar_synths.BarSynthetic") + + impl_before = value.GetTypeSyntheticImplementation() + + if not before: + self.assertIsNone(impl_before) + else: + self.assertIsNotNone(impl_before) + self.assertEqual(type(impl_before).__name__, before) + + value.SetTypeSynthetic(bar) + self.assertEqual(value.GetTypeSynthetic(), bar) + + impl_after = value.GetTypeSyntheticImplementation() + self.assertIsNotNone(impl_after) + self.assertEqual(type(impl_after).__name__, "BarSynthetic") diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/foo_bar_synths.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/foo_bar_synths.py new file mode 100644 index 0000000000000..ac0f85490be0f --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/foo_bar_synths.py @@ -0,0 +1,16 @@ +import lldb + + [email protected]("Foo") +class FooSynthetic: + """Dummy synthetic to demonstrate override capabilities""" + + def __init__(self, valobj: lldb.SBValue, _) -> None: + pass + + +class BarSynthetic: + """Dummy synthetic to demonstrate override capabilities""" + + def __init__(self, valobj: lldb.SBValue, _) -> None: + pass diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/main.cpp b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/main.cpp new file mode 100644 index 0000000000000..6a178cd8a7718 --- /dev/null +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/main.cpp @@ -0,0 +1,17 @@ +#include <vector> + +struct Foo { + int foo; +}; +struct Bar { + bool bar; +}; + +int main() { + std::vector<int> vec = {0, 1, 2, 3, 4}; + + Foo foo{.foo = 10}; + Bar bar{.bar = false}; + + return 0; // break here +} >From d1ddf93e31d5f29ea1a9675e8eab714aa3fdb3af Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Wed, 15 Jul 2026 19:24:52 +0200 Subject: [PATCH 12/15] Unsustainable (but working) hack for Set/GetTypeSynthetic issues SBValue::GetSP takes the dynamic/synthetic value preference into account, which resulted in these methods being called on the ValueObjectSynthetic, rather than the ValueObjectVariable it was related to. This 'fix' doesn't sit right with me. In general it looks like ValueObjectSynthetic inherits too much machinery from ValueObject, as such it has it's own instances of m_synthetic_children_(override)_sp. At the same lots of functions on the interface are overridden to delegate to m_parent, but the setter/getter functions are understandably not made virtual. --- lldb/source/API/SBValue.cpp | 46 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index 61a4a1a6b1835..bc1a6472d1ced 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -377,9 +377,12 @@ lldb::SBTypeSynthetic SBValue::GetTypeSynthetic() { LLDB_INSTRUMENT_VA(this); lldb::SBTypeSynthetic synthetic; - ValueLocker locker; - lldb::ValueObjectSP value_sp(GetSP(locker)); - if (value_sp) { + if (IsValid()) { + ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), + m_opaque_sp->GetUseDynamic(), false); + + ValueLocker locker; + lldb::ValueObjectSP value_sp(locker.GetLockedSP(non_synthetic_root)); if (value_sp->UpdateValueIfNeeded(true)) { lldb::SyntheticChildrenSP children_sp = value_sp->GetSyntheticChildren(); @@ -396,28 +399,37 @@ lldb::SBTypeSynthetic SBValue::GetTypeSynthetic() { void SBValue::SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic) { LLDB_INSTRUMENT_VA(this); - ValueLocker locker; - lldb::ValueObjectSP value_sp(GetSP(locker)); - lldb::ScriptedSyntheticChildrenSP synthetic_sp(synthetic.GetSP()); - if (value_sp) { - value_sp->SetSyntheticChildrenOverride(synthetic_sp); + if (IsValid()) { + ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), + m_opaque_sp->GetUseDynamic(), false); + + ValueLocker locker; + lldb::ValueObjectSP root_sp(locker.GetLockedSP(non_synthetic_root)); + + root_sp->SetSyntheticChildrenOverride(synthetic.GetSP()); } } lldb::SBScriptObject SBValue::GetTypeSyntheticImplementation() { LLDB_INSTRUMENT_VA(this); - ValueLocker locker; - lldb::ValueObjectSP value_sp(GetSP(locker)); - if (!value_sp) - return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); + ScriptObjectPtr ptr = nullptr; + if (IsValid()) { + ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), + m_opaque_sp->GetUseDynamic(), false); - auto frontend = value_sp->GetSyntheticChildrenFrontEnd(); - if (!frontend) - return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); + ValueLocker locker; + lldb::ValueObjectSP root_sp(locker.GetLockedSP(non_synthetic_root)); - return lldb::SBScriptObject(frontend->GetImplementation(), - eScriptLanguageDefault); + if (root_sp->UpdateValueIfNeeded(true)) { + if (auto synth = root_sp->GetSyntheticValue()) { + if (auto frontend = synth->GetSyntheticChildrenFrontEnd()) { + ptr = frontend->GetImplementation(); + } + } + } + } + return lldb::SBScriptObject(ptr, eScriptLanguageDefault); } lldb::SBValue SBValue::CreateChildAtOffset(const char *name, uint32_t offset, >From 8541a8e07187da6abc1d5176836e6211bdd7a2e4 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Thu, 16 Jul 2026 18:14:17 +0200 Subject: [PATCH 13/15] Revert "Unsustainable (but working) hack for Set/GetTypeSynthetic issues" This reverts commit d1ddf93e31d5f29ea1a9675e8eab714aa3fdb3af. --- lldb/source/API/SBValue.cpp | 46 ++++++++++++++----------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index bc1a6472d1ced..61a4a1a6b1835 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -377,12 +377,9 @@ lldb::SBTypeSynthetic SBValue::GetTypeSynthetic() { LLDB_INSTRUMENT_VA(this); lldb::SBTypeSynthetic synthetic; - if (IsValid()) { - ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), - m_opaque_sp->GetUseDynamic(), false); - - ValueLocker locker; - lldb::ValueObjectSP value_sp(locker.GetLockedSP(non_synthetic_root)); + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + if (value_sp) { if (value_sp->UpdateValueIfNeeded(true)) { lldb::SyntheticChildrenSP children_sp = value_sp->GetSyntheticChildren(); @@ -399,37 +396,28 @@ lldb::SBTypeSynthetic SBValue::GetTypeSynthetic() { void SBValue::SetTypeSynthetic(lldb::SBTypeSynthetic &synthetic) { LLDB_INSTRUMENT_VA(this); - if (IsValid()) { - ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), - m_opaque_sp->GetUseDynamic(), false); - - ValueLocker locker; - lldb::ValueObjectSP root_sp(locker.GetLockedSP(non_synthetic_root)); - - root_sp->SetSyntheticChildrenOverride(synthetic.GetSP()); + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + lldb::ScriptedSyntheticChildrenSP synthetic_sp(synthetic.GetSP()); + if (value_sp) { + value_sp->SetSyntheticChildrenOverride(synthetic_sp); } } lldb::SBScriptObject SBValue::GetTypeSyntheticImplementation() { LLDB_INSTRUMENT_VA(this); - ScriptObjectPtr ptr = nullptr; - if (IsValid()) { - ValueImpl non_synthetic_root(m_opaque_sp->GetRootSP(), - m_opaque_sp->GetUseDynamic(), false); + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + if (!value_sp) + return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); - ValueLocker locker; - lldb::ValueObjectSP root_sp(locker.GetLockedSP(non_synthetic_root)); + auto frontend = value_sp->GetSyntheticChildrenFrontEnd(); + if (!frontend) + return lldb::SBScriptObject(nullptr, eScriptLanguageDefault); - if (root_sp->UpdateValueIfNeeded(true)) { - if (auto synth = root_sp->GetSyntheticValue()) { - if (auto frontend = synth->GetSyntheticChildrenFrontEnd()) { - ptr = frontend->GetImplementation(); - } - } - } - } - return lldb::SBScriptObject(ptr, eScriptLanguageDefault); + return lldb::SBScriptObject(frontend->GetImplementation(), + eScriptLanguageDefault); } lldb::SBValue SBValue::CreateChildAtOffset(const char *name, uint32_t offset, >From 8bda29a12fef7d86f53144f6fa48cf8de3cd8d2f Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Thu, 16 Jul 2026 18:25:08 +0200 Subject: [PATCH 14/15] Update test with explicit VO targeting --- .../TestSBValueSetTypeSyntheticOverride.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py index e147b73aee656..e5e8f29ba0e75 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic_override/TestSBValueSetTypeSyntheticOverride.py @@ -32,19 +32,35 @@ def test(self): self.checkOverride(bar, before=None) def checkOverride(self, value, before): + foo = lldb.SBTypeSynthetic.CreateWithClassName(f"foo_bar_synths.FooSynthetic") bar = lldb.SBTypeSynthetic.CreateWithClassName(f"foo_bar_synths.BarSynthetic") - impl_before = value.GetTypeSyntheticImplementation() + # Target the static (non synthetic) ValueObject + static = value.GetNonSyntheticValue() + impl_before = static.GetSyntheticValue().GetTypeSyntheticImplementation() if not before: self.assertIsNone(impl_before) else: self.assertIsNotNone(impl_before) self.assertEqual(type(impl_before).__name__, before) - value.SetTypeSynthetic(bar) - self.assertEqual(value.GetTypeSynthetic(), bar) + static.SetTypeSynthetic(bar) + self.assertEqual(static.GetTypeSynthetic(), bar) - impl_after = value.GetTypeSyntheticImplementation() + impl_after = static.GetSyntheticValue().GetTypeSyntheticImplementation() + self.assertIsNotNone(impl_after) + self.assertEqual(type(impl_after).__name__, "BarSynthetic") + + # Target the ValueObjectSynthetic of the original ValueObject + synth = value.GetSyntheticValue() + + synth.SetTypeSynthetic(foo) + self.assertEqual(synth.GetTypeSynthetic(), foo) + + # Even though the synthetic child provider choice of 'synth' was changed + # that does not retroactively alter the frontend it was created with when + # its parent's synthetic child provider choice was overridden above. + impl_after = synth.GetSyntheticValue().GetTypeSyntheticImplementation() self.assertIsNotNone(impl_after) self.assertEqual(type(impl_after).__name__, "BarSynthetic") >From dade5168142ac38564327850570f86e829e4f236 Mon Sep 17 00:00:00 2001 From: mentlerd <[email protected]> Date: Fri, 17 Jul 2026 18:18:47 +0200 Subject: [PATCH 15/15] clang-format, include <stdlib.h> --- .../sbvalue_set_type_synthetic/main.cpp | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp b/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp index 150eca4c5feec..2c8398411f672 100644 --- a/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp +++ b/lldb/test/API/python_api/sbvalue_set_type_synthetic/main.cpp @@ -2,7 +2,7 @@ // library_c_api.h - Mock type erased C API for library #include <cstddef> -typedef void* Handle; +typedef void *Handle; typedef Handle FooHandle; typedef Handle BarHandle; @@ -10,7 +10,7 @@ typedef Handle BarHandle; // To minimize API surface, the C API has a single type for arrays of handles struct HandleArray { size_t size; - Handle* data; + Handle *data; }; struct SessionInfo { @@ -21,17 +21,19 @@ struct SessionInfo { }; extern "C" { - void InitSession(); - void StopSession(); +void InitSession(); +void StopSession(); - void ReadSessionInfo(SessionInfo* into); - void FreeSessionInfo(SessionInfo* info); +void ReadSessionInfo(SessionInfo *into); +void FreeSessionInfo(SessionInfo *info); } // library.cpp #include <memory> #include <vector> +#include <stdlib.h> + struct Foo { int foo; }; @@ -43,45 +45,45 @@ static std::vector<std::unique_ptr<Foo>> g_foos; static std::vector<std::unique_ptr<Bar>> g_bars; extern "C" { - void InitSession() { - g_foos.emplace_back(new Foo{ .foo = 10 }); - g_foos.emplace_back(new Foo{ .foo = 20 }); - g_bars.emplace_back(new Bar{ .bar = false }); - } - void StopSession() { - g_foos.clear(); - g_bars.clear(); - } +void InitSession() { + g_foos.emplace_back(new Foo{.foo = 10}); + g_foos.emplace_back(new Foo{.foo = 20}); + g_bars.emplace_back(new Bar{.bar = false}); +} +void StopSession() { + g_foos.clear(); + g_bars.clear(); +} - void ReadSessionInfo(SessionInfo* into) { - into->foos.size = g_foos.size(); - into->foos.data = (Handle*) malloc(sizeof(Handle) * g_foos.size()); - for (size_t i = 0; i < g_foos.size(); i++) { - into->foos.data[i] = g_foos[i].get(); - } - - into->bars.size = g_bars.size(); - into->bars.data = (Handle*) malloc(sizeof(Handle) * g_bars.size()); - for (size_t i = 0; i < g_bars.size(); i++) { - into->bars.data[i] = g_bars[i].get(); - } +void ReadSessionInfo(SessionInfo *into) { + into->foos.size = g_foos.size(); + into->foos.data = (Handle *)malloc(sizeof(Handle) * g_foos.size()); + for (size_t i = 0; i < g_foos.size(); i++) { + into->foos.data[i] = g_foos[i].get(); } - void FreeSessionInfo(SessionInfo* info) { - free(info->foos.data); - free(info->bars.data); - info->foos.size = 0; - info->foos.data = NULL; - info->bars.size = 0; - info->bars.data = NULL; - } -} + into->bars.size = g_bars.size(); + into->bars.data = (Handle *)malloc(sizeof(Handle) * g_bars.size()); + for (size_t i = 0; i < g_bars.size(); i++) { + into->bars.data[i] = g_bars[i].get(); + } +} +void FreeSessionInfo(SessionInfo *info) { + free(info->foos.data); + free(info->bars.data); + + info->foos.size = 0; + info->foos.data = NULL; + info->bars.size = 0; + info->bars.data = NULL; +} +} // app.cpp - being debugged by library.cpp authors int main() { InitSession(); - + SessionInfo info; ReadSessionInfo(&info); FreeSessionInfo(&info); // break here _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
