Skip to content

Commit ef3134c

Browse files
[lldb][swift] Mark async unwinding plans as SourcedFromCompiler
LLDB always attempts to check other unwinding plans when an unwind plan produces a CFA == 0. It does so because it doesn't distinguish the case where the plan truly produced the end of the stack versus the case where the plan was faulty. We need a mechanism to disable this for swift async plans: if the language runtime constructs such a plan, then that plan will correctly identify the bottom of the stack. Checking other plans is harmless in non-async settings (they would just produce the same backtrace), but it is harmful for async virtual backtraces, as a "regular" unwind plan would produce a real backtrace if it were asked to start from the top of the stack (which would happen in short backtraces). Until such mechanism exists, we can work around the issue by claiming swift async plans are "sourced from compiler", which seem to disable the fallback mechanism because plans sourced from the compiler are considered the most reliable plans. rdar://142683622
1 parent ad53c6a commit ef3134c

File tree

4 files changed

+50
-2
lines changed

4 files changed

+50
-2
lines changed

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,7 +2426,9 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
24262426
UnwindPlanSP plan = std::make_shared<UnwindPlan>(lldb::eRegisterKindDWARF);
24272427
plan->AppendRow(row);
24282428
plan->SetSourceName("Swift Transition-to-AsyncContext-Chain");
2429-
plan->SetSourcedFromCompiler(eLazyBoolNo);
2429+
// Make this plan more authoritative, so that the unwinding fallback
2430+
// mechanisms don't kick in and produce a physical backtrace instead.
2431+
plan->SetSourcedFromCompiler(eLazyBoolYes);
24302432
plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
24312433
plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);
24322434
return plan;
@@ -2466,7 +2468,9 @@ UnwindPlanSP SwiftLanguageRuntime::GetFollowAsyncContextUnwindPlan(
24662468
UnwindPlanSP plan = std::make_shared<UnwindPlan>(lldb::eRegisterKindDWARF);
24672469
plan->AppendRow(row);
24682470
plan->SetSourceName("Swift Following-AsyncContext-Chain");
2469-
plan->SetSourcedFromCompiler(eLazyBoolNo);
2471+
// Make this plan more authoritative, so that the unwinding fallback
2472+
// mechanisms don't kick in and produce a physical backtrace instead.
2473+
plan->SetSourcedFromCompiler(eLazyBoolYes);
24702474
plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
24712475
plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);
24722476
behaves_like_zeroth_frame = true;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
import lldbsuite.test.lldbtest as lldbtest
4+
import lldbsuite.test.lldbutil as lldbutil
5+
6+
7+
class TestSwiftAsyncUnwind(lldbtest.TestBase):
8+
9+
mydir = lldbtest.TestBase.compute_mydir(__file__)
10+
11+
@swiftTest
12+
@skipIf(oslist=["windows", "linux"])
13+
def test(self):
14+
"""Test async unwinding with short backtraces work properly"""
15+
self.build()
16+
src = lldb.SBFileSpec("main.swift")
17+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
18+
self, "BREAK HERE", src
19+
)
20+
21+
self.assertEqual(2, thread.GetNumFrames())
22+
self.assertIn("work", thread.GetFrameAtIndex(0).GetFunctionName())
23+
self.assertIn(
24+
"await resume partial function for implicit closure",
25+
thread.GetFrameAtIndex(1).GetFunctionName(),
26+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
func work() async -> Int {
2+
try? await Task.sleep(for: .seconds(3)) // BREAK HERE
3+
return 10
4+
}
5+
func foo_should_step_over() async -> Int {
6+
async let an_array = [work(), work(), work()]
7+
await an_array
8+
return 10
9+
}
10+
11+
@main struct Main {
12+
static func main() async {
13+
await foo_should_step_over()
14+
}
15+
}

0 commit comments

Comments
 (0)