From d9d698644555512e330ae324a76a3c617e6ffa55 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Mon, 30 Jun 2025 17:15:03 -0700 Subject: [PATCH 1/4] [lldb] Add parent address to Task synthetic provider --- .../Language/Swift/SwiftFormatters.cpp | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp index f8231c8b9ee76..530a8493d3940 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp +++ b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp @@ -734,6 +734,13 @@ namespace lldb_private { namespace formatters { namespace swift { +namespace { +/// The size of Swift Tasks. Fragments are tail allocated. +static constexpr size_t AsyncTaskSize = sizeof(::swift::AsyncTask); +/// The offset of ChildFragment, which is the first fragment of an AsyncTask. +static constexpr offset_t ChildFragmentOffset = AsyncTaskSize; +} // namespace + class EnumSyntheticFrontEnd : public SyntheticChildrenFrontEnd { public: EnumSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); @@ -806,6 +813,7 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { "address", "id", "enqueuePriority", + "parent", "children", // Children below this point are hidden. @@ -873,6 +881,33 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { RETURN_CHILD(m_enqueue_priority_sp, enqueuePriority, priority_type); } case 3: { + if (!m_parent_task_sp) { + // TypeMangling for "Swift.UnsafeRawPointer" + CompilerType raw_pointer_type = + m_ts->GetTypeFromMangledTypename(ConstString("$sSVD")); + + addr_t parent_addr = 0; + if (m_task_info.isChildTask) { + if (auto process_sp = m_backend.GetProcessSP()) { + Status status; + // Read ChildFragment::Parent, the first field of the ChildFragment. + parent_addr = process_sp->ReadPointerFromMemory( + m_task_ptr + ChildFragmentOffset, status); + if (status.Fail() || parent_addr == LLDB_INVALID_ADDRESS) + parent_addr = 0; + } + } + addr_t value = parent_addr; + DataExtractor data{reinterpret_cast(&value), + sizeof(value), endian::InlHostByteOrder(), + sizeof(void *)}; + m_parent_task_sp = ValueObject::CreateValueObjectFromData( + "parent", data, m_backend.GetExecutionContextRef(), + raw_pointer_type); + } + return m_parent_task_sp; + } + case 4: { if (!m_child_tasks_sp) { using task_type = decltype(m_task_info.childTasks)::value_type; std::vector tasks = m_task_info.childTasks; @@ -901,26 +936,26 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { } return m_child_tasks_sp; } - case 4: - RETURN_CHILD(m_is_child_task_sp, isChildTask, bool_type); case 5: - RETURN_CHILD(m_is_future_sp, isFuture, bool_type); + RETURN_CHILD(m_is_child_task_sp, isChildTask, bool_type); case 6: - RETURN_CHILD(m_is_group_child_task_sp, isGroupChildTask, bool_type); + RETURN_CHILD(m_is_future_sp, isFuture, bool_type); case 7: - RETURN_CHILD(m_is_async_let_task_sp, isAsyncLetTask, bool_type); + RETURN_CHILD(m_is_group_child_task_sp, isGroupChildTask, bool_type); case 8: - RETURN_CHILD(m_is_cancelled_sp, isCancelled, bool_type); + RETURN_CHILD(m_is_async_let_task_sp, isAsyncLetTask, bool_type); case 9: + RETURN_CHILD(m_is_cancelled_sp, isCancelled, bool_type); + case 10: RETURN_CHILD(m_is_status_record_locked_sp, isStatusRecordLocked, bool_type); - case 10: - RETURN_CHILD(m_is_escalated_sp, isEscalated, bool_type); case 11: - RETURN_CHILD(m_is_enqueued_sp, isEnqueued, bool_type); + RETURN_CHILD(m_is_escalated_sp, isEscalated, bool_type); case 12: + RETURN_CHILD(m_is_enqueued_sp, isEnqueued, bool_type); + case 13: RETURN_CHILD(m_is_complete_sp, isComplete, bool_type); - case 13: { + case 14: { if (m_task_info.hasIsRunning) RETURN_CHILD(m_is_running_sp, isRunning, bool_type); return {}; @@ -953,8 +988,8 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { m_is_child_task_sp, m_is_future_sp, m_is_group_child_task_sp, m_is_async_let_task_sp, m_is_cancelled_sp, m_is_status_record_locked_sp, m_is_escalated_sp, - m_is_enqueued_sp, m_is_complete_sp, m_child_tasks_sp, - m_is_running_sp}) + m_is_enqueued_sp, m_is_complete_sp, m_parent_task_sp, + m_child_tasks_sp, m_is_running_sp}) child.reset(); } } @@ -990,6 +1025,7 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { ValueObjectSP m_is_escalated_sp; ValueObjectSP m_is_enqueued_sp; ValueObjectSP m_is_complete_sp; + ValueObjectSP m_parent_task_sp; ValueObjectSP m_child_tasks_sp; ValueObjectSP m_is_running_sp; }; @@ -1280,8 +1316,6 @@ class TaskGroupSyntheticFrontEnd : public SyntheticChildrenFrontEnd { bool operator==(const Task &other) const { return addr == other.addr; } bool operator!=(const Task &other) const { return !(*this == other); } - static constexpr offset_t AsyncTaskSize = sizeof(::swift::AsyncTask); - static constexpr offset_t ChildFragmentOffset = AsyncTaskSize; static constexpr offset_t NextChildOffset = ChildFragmentOffset + 0x8; Task getNextChild(Status &status) { From 5b5fd2ce71871069e0aadb190499b0374d849498 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 1 Jul 2025 12:32:36 -0700 Subject: [PATCH 2/4] Use UnsafeRawPointer?; Fix num children --- .../Language/Swift/SwiftFormatters.cpp | 30 ++++++++-------- .../async/formatters/task/parent/Makefile | 3 ++ .../parent/TestSwiftSyntheticTaskParent.py | 35 +++++++++++++++++++ .../async/formatters/task/parent/main.swift | 9 +++++ 4 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 lldb/test/API/lang/swift/async/formatters/task/parent/Makefile create mode 100644 lldb/test/API/lang/swift/async/formatters/task/parent/TestSwiftSyntheticTaskParent.py create mode 100644 lldb/test/API/lang/swift/async/formatters/task/parent/main.swift diff --git a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp index 530a8493d3940..2eaf88d0c7738 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp +++ b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp @@ -734,12 +734,10 @@ namespace lldb_private { namespace formatters { namespace swift { -namespace { /// The size of Swift Tasks. Fragments are tail allocated. static constexpr size_t AsyncTaskSize = sizeof(::swift::AsyncTask); /// The offset of ChildFragment, which is the first fragment of an AsyncTask. static constexpr offset_t ChildFragmentOffset = AsyncTaskSize; -} // namespace class EnumSyntheticFrontEnd : public SyntheticChildrenFrontEnd { public: @@ -831,8 +829,9 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { }; llvm::Expected CalculateNumChildren() override { - // Show only the first four children address/id/enqueuePriority/children. - return 4; + // Show only the first five children + // address/id/enqueuePriority/parent/children. + return 5; } lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { @@ -882,21 +881,24 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { } case 3: { if (!m_parent_task_sp) { - // TypeMangling for "Swift.UnsafeRawPointer" + auto process_sp = m_backend.GetProcessSP(); + if (!process_sp) + return {}; + + // TypeMangling for "Swift.Optional" CompilerType raw_pointer_type = - m_ts->GetTypeFromMangledTypename(ConstString("$sSVD")); + m_ts->GetTypeFromMangledTypename(ConstString("$sSVSgD")); addr_t parent_addr = 0; if (m_task_info.isChildTask) { - if (auto process_sp = m_backend.GetProcessSP()) { - Status status; - // Read ChildFragment::Parent, the first field of the ChildFragment. - parent_addr = process_sp->ReadPointerFromMemory( - m_task_ptr + ChildFragmentOffset, status); - if (status.Fail() || parent_addr == LLDB_INVALID_ADDRESS) - parent_addr = 0; - } + // Read ChildFragment::Parent, the first field of the ChildFragment. + Status status; + parent_addr = process_sp->ReadPointerFromMemory( + m_task_ptr + ChildFragmentOffset, status); + if (status.Fail() || parent_addr == LLDB_INVALID_ADDRESS) + parent_addr = 0; } + addr_t value = parent_addr; DataExtractor data{reinterpret_cast(&value), sizeof(value), endian::InlHostByteOrder(), diff --git a/lldb/test/API/lang/swift/async/formatters/task/parent/Makefile b/lldb/test/API/lang/swift/async/formatters/task/parent/Makefile new file mode 100644 index 0000000000000..cca30b939e652 --- /dev/null +++ b/lldb/test/API/lang/swift/async/formatters/task/parent/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift +SWIFTFLAGS_EXTRAS := -parse-as-library +include Makefile.rules diff --git a/lldb/test/API/lang/swift/async/formatters/task/parent/TestSwiftSyntheticTaskParent.py b/lldb/test/API/lang/swift/async/formatters/task/parent/TestSwiftSyntheticTaskParent.py new file mode 100644 index 0000000000000..fafa8ffc5cf07 --- /dev/null +++ b/lldb/test/API/lang/swift/async/formatters/task/parent/TestSwiftSyntheticTaskParent.py @@ -0,0 +1,35 @@ +import re +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +ADDR_PATTERN = "(0x[0-9a-f]{6,})" + +class TestCase(TestBase): + + @skipUnlessDarwin + @swiftTest + def test(self): + self.build() + _, process, _, _ = lldbutil.run_to_name_breakpoint(self, "breakHere") + + # First breakpoint hit occurrs in a root task, with no parent. + self.expect("task info", substrs=["parent = nil"]) + root_task = self._extract("task info", f"address = {ADDR_PATTERN}") + + # Continue to the next hit of the same breakpoint, which is called from + # an async let child task. + process.Continue() + parent_of_child_task = self._extract("task info", f"parent = {ADDR_PATTERN}") + + # Ensure the parent of the child is the same as the root task. + self.assertEqual(root_task, parent_of_child_task) + + def _extract(self, command: str, pattern: str) -> str: + ret = lldb.SBCommandReturnObject() + self.ci.HandleCommand(command, ret) + match = re.search(pattern, ret.GetOutput(), flags=re.I) + self.assertTrue(match) + return match.group(1) if match else "" diff --git a/lldb/test/API/lang/swift/async/formatters/task/parent/main.swift b/lldb/test/API/lang/swift/async/formatters/task/parent/main.swift new file mode 100644 index 0000000000000..caf0f7af367a0 --- /dev/null +++ b/lldb/test/API/lang/swift/async/formatters/task/parent/main.swift @@ -0,0 +1,9 @@ +func breakHere() {} + +@main struct Main { + static func main() async { + await breakHere() + async let x = breakHere() + await x + } +} From 5bb4cc619ab7de091f983696465037fcf48ad010 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 2 Jul 2025 13:12:38 -0700 Subject: [PATCH 3/4] Additional test updates --- .../async/continuations/TestSwiftContinuationSynthetic.py | 2 ++ .../formatters/task/TestSwiftTaskSyntheticProvider.py | 2 ++ .../task/children/TestSwiftSyntheticTaskChildren.py | 4 +++- .../swift/async/taskgroups/TestSwiftTaskGroupSynthetic.py | 7 +++++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/lang/swift/async/continuations/TestSwiftContinuationSynthetic.py b/lldb/test/API/lang/swift/async/continuations/TestSwiftContinuationSynthetic.py index 1adba03fd102d..c4247ac4de5a9 100644 --- a/lldb/test/API/lang/swift/async/continuations/TestSwiftContinuationSynthetic.py +++ b/lldb/test/API/lang/swift/async/continuations/TestSwiftContinuationSynthetic.py @@ -24,6 +24,7 @@ def test_unsafe_continuation_printing(self): address = 0x[0-9a-f]+ id = \1 enqueuePriority = 0 + parent = nil children = \{\} \} \} @@ -49,6 +50,7 @@ def test_checked_continuation_printing(self): address = 0x[0-9a-f]+ id = \1 enqueuePriority = 0 + parent = nil children = \{\} \} \} diff --git a/lldb/test/API/lang/swift/async/formatters/task/TestSwiftTaskSyntheticProvider.py b/lldb/test/API/lang/swift/async/formatters/task/TestSwiftTaskSyntheticProvider.py index cea29347686c2..9d50af2b473ad 100644 --- a/lldb/test/API/lang/swift/async/formatters/task/TestSwiftTaskSyntheticProvider.py +++ b/lldb/test/API/lang/swift/async/formatters/task/TestSwiftTaskSyntheticProvider.py @@ -25,6 +25,7 @@ def test_top_level_task(self): address = 0x[0-9a-f]+ id = \1 enqueuePriority = \.medium + parent = nil children = \{\} } """ @@ -49,6 +50,7 @@ def test_current_task(self): address = 0x[0-9a-f]+ id = \1 enqueuePriority = \.medium + parent = 0x[0-9a-f]+ \{\} children = \{\} \} """ diff --git a/lldb/test/API/lang/swift/async/formatters/task/children/TestSwiftSyntheticTaskChildren.py b/lldb/test/API/lang/swift/async/formatters/task/children/TestSwiftSyntheticTaskChildren.py index a8660c15692c0..f71f33c3e2705 100644 --- a/lldb/test/API/lang/swift/async/formatters/task/children/TestSwiftSyntheticTaskChildren.py +++ b/lldb/test/API/lang/swift/async/formatters/task/children/TestSwiftSyntheticTaskChildren.py @@ -21,14 +21,16 @@ def test(self): textwrap.dedent( r""" \(UnsafeCurrentTask\) current_task = id:1 flags:(?:running|enqueued) \{ - address = 0x[0-9a-f]+ + address = (0x[0-9a-f]+) id = 1 enqueuePriority = 0 + parent = nil children = \{ 0 = id:2 flags:(?:running\|)?(?:enqueued\|)?asyncLetTask \{ address = 0x[0-9a-f]+ id = 2 enqueuePriority = \.medium + parent = \1 \{\} children = \{\} \} \} diff --git a/lldb/test/API/lang/swift/async/taskgroups/TestSwiftTaskGroupSynthetic.py b/lldb/test/API/lang/swift/async/taskgroups/TestSwiftTaskGroupSynthetic.py index 053aed29c5959..605c7ef6f1b78 100644 --- a/lldb/test/API/lang/swift/async/taskgroups/TestSwiftTaskGroupSynthetic.py +++ b/lldb/test/API/lang/swift/async/taskgroups/TestSwiftTaskGroupSynthetic.py @@ -36,18 +36,21 @@ def do_test_print(self): address = 0x[0-9a-f]+ id = \1 enqueuePriority = \.medium + parent = (.+) children = \{\} \} \[1\] = id:([1-9]\d*) flags:(?:running\|)?(?:enqueued\|)?groupChildTask \{ address = 0x[0-9a-f]+ - id = \2 + id = \3 enqueuePriority = \.medium + parent = \2 children = \{\} \} \[2\] = id:([1-9]\d*) flags:(?:running\|)?(?:enqueued\|)?groupChildTask \{ address = 0x[0-9a-f]+ - id = \3 + id = \4 enqueuePriority = \.medium + parent = \2 children = \{\} \} \} From 5ef6378c4d10289851a7c363767c726170e3a2aa Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 2 Jul 2025 15:14:24 -0700 Subject: [PATCH 4/4] [lldb] Avoid mangling empty opaque types --- .../Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lldb/source/Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp b/lldb/source/Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp index 42a707d71102a..91f146fd4a378 100644 --- a/lldb/source/Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp +++ b/lldb/source/Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp @@ -2857,6 +2857,13 @@ TypeSystemSwiftTypeRef::RemangleAsType(swift::Demangle::Demangler &dem, if (!node) return {}; + // Guard against an empty opaque type. This can happen when demangling an + // OpaqueTypeRef (ex `$sBpD`). An empty opaque will assert when mangled. + if (auto *opaque_type = + swift_demangle::ChildAtPath(node, {Node::Kind::OpaqueType})) + if (!opaque_type->hasChildren()) + return {}; + using namespace swift::Demangle; if (node->getKind() != Node::Kind::Global) { auto global = dem.createNode(Node::Kind::Global);