From 77290cc3c24818056101e49ec4107bbc907bca9a Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Wed, 11 Jun 2025 10:29:51 -0700 Subject: [PATCH] [lldb][swift] Disable breakpoint filtering by default A previous patch had introduced the notion of Language breakpoint filtering when setting line breakpoints. While this is generally a good idea, the swift implementation was motivated by statements like this: ``` async let x = ... await foo() await x ``` All of these lines have many different breakpoint locations associated with them, creating many pauses while stepping; the intent behind the filtering was to provide a smoother stepping experience. The implementation filters breakpoints by "funclet" numbers, disabling all but the lowest such funclet. Unfortunately, this fails when the CFG of an async funclet needs to be cloned in non-trivial ways. For example: ``` func foo(_ argument: Int) async { do { switch argument { case 1: try await willthrow(1) case 2: try await willthrow(2) default: return } } catch { print("breakhere") } } ``` The breakpoint in "breakhere" has two funclets associated with it, one when we throw from case 1, one from case 2. Filtering is incorrect in this situation. This patch disables filtering until we can solve this at a compiler level. While we are disabling this through the TargetProperties setting, which could affect other languages, Swift is the only such language using that setting. --- lldb/source/Target/TargetProperties.td | 2 +- .../stepping/step-in/TestSwiftStepInAsync.py | 1 + .../step_over/TestSwiftAsyncStepOver.py | 1 + .../TestSwiftAsyncBacktraceLocals.py | 2 ++ .../unwind_in_all_instructions/main.swift | 3 +- .../TestSwiftAsyncBreakpoints.py | 1 + .../Makefile | 3 ++ ...stSwiftAsyncBreakpointsOverManyFunclets.py | 29 +++++++++++++++++++ .../main.swift | 27 +++++++++++++++++ .../TestSwiftSteppingThroughWitness.py | 8 ++++- .../stepping_through_witness/main.swift | 3 +- 11 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/Makefile create mode 100644 lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/TestSwiftAsyncBreakpointsOverManyFunclets.py create mode 100644 lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/main.swift diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index 79f9b7c356456..65cb62a9a8bc5 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -380,6 +380,6 @@ let Definition = "thread" in { let Definition = "language" in { def EnableFilterForLineBreakpoints: Property<"enable-filter-for-line-breakpoints", "Boolean">, - DefaultTrue, + DefaultFalse, Desc<"If true, allow Language plugins to filter locations when setting breakpoints by line number or regex.">; } diff --git a/lldb/test/API/lang/swift/async/stepping/step-in/TestSwiftStepInAsync.py b/lldb/test/API/lang/swift/async/stepping/step-in/TestSwiftStepInAsync.py index f9552522dec2f..5457470fb5251 100644 --- a/lldb/test/API/lang/swift/async/stepping/step-in/TestSwiftStepInAsync.py +++ b/lldb/test/API/lang/swift/async/stepping/step-in/TestSwiftStepInAsync.py @@ -13,6 +13,7 @@ class TestCase(lldbtest.TestBase): def test(self): """Test step-in to async functions""" self.build() + self.runCmd("settings set language.enable-filter-for-line-breakpoints true") src = lldb.SBFileSpec('main.swift') _, process, _, _ = lldbutil.run_to_source_breakpoint(self, 'await', src) diff --git a/lldb/test/API/lang/swift/async/stepping/step_over/TestSwiftAsyncStepOver.py b/lldb/test/API/lang/swift/async/stepping/step_over/TestSwiftAsyncStepOver.py index 11995fced6814..7b1e659d0b22f 100644 --- a/lldb/test/API/lang/swift/async/stepping/step_over/TestSwiftAsyncStepOver.py +++ b/lldb/test/API/lang/swift/async/stepping/step_over/TestSwiftAsyncStepOver.py @@ -22,6 +22,7 @@ def test(self): target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( self, "BREAK HERE", source_file ) + bkpt.SetEnabled(False) # avoid hitting multiple locations in async breakpoints expected_line_nums = [4] # print(x) expected_line_nums += [5, 6, 7, 5, 6, 7, 5] # two runs over the loop diff --git a/lldb/test/API/lang/swift/async/unwind/backtrace_locals/TestSwiftAsyncBacktraceLocals.py b/lldb/test/API/lang/swift/async/unwind/backtrace_locals/TestSwiftAsyncBacktraceLocals.py index 9c4c2aab8d303..928e6f91cda29 100644 --- a/lldb/test/API/lang/swift/async/unwind/backtrace_locals/TestSwiftAsyncBacktraceLocals.py +++ b/lldb/test/API/lang/swift/async/unwind/backtrace_locals/TestSwiftAsyncBacktraceLocals.py @@ -23,6 +23,7 @@ def test(self): self.build() target, process, thread, main_bkpt = lldbutil.run_to_source_breakpoint( self, 'main breakpoint', self.src) + main_bkpt.SetEnabled(False) # avoid hitting multiple locations in async breakpoints self.run_fibo_tests(target, process) @swiftTest @@ -34,6 +35,7 @@ def test_actor(self): self.build() target, process, thread, main_bkpt = lldbutil.run_to_source_breakpoint( self, 'main actor breakpoint', self.src) + main_bkpt.SetEnabled(False) # avoid hitting multiple locations in async breakpoints self.run_fibo_tests(target, process) diff --git a/lldb/test/API/lang/swift/async/unwind/unwind_in_all_instructions/main.swift b/lldb/test/API/lang/swift/async/unwind/unwind_in_all_instructions/main.swift index 4dc2496dfd65f..42676ca4d2677 100644 --- a/lldb/test/API/lang/swift/async/unwind/unwind_in_all_instructions/main.swift +++ b/lldb/test/API/lang/swift/async/unwind/unwind_in_all_instructions/main.swift @@ -43,7 +43,8 @@ func ASYNC___5___() async -> Int { @main struct Main { static func main() async { - let result = await ASYNC___5___() // BREAK HERE + print("BREAK HERE") + let result = await ASYNC___5___() print(result) } } diff --git a/lldb/test/API/lang/swift/async_breakpoints/TestSwiftAsyncBreakpoints.py b/lldb/test/API/lang/swift/async_breakpoints/TestSwiftAsyncBreakpoints.py index aefc20a7fc64a..7727da7c6b0f2 100644 --- a/lldb/test/API/lang/swift/async_breakpoints/TestSwiftAsyncBreakpoints.py +++ b/lldb/test/API/lang/swift/async_breakpoints/TestSwiftAsyncBreakpoints.py @@ -13,6 +13,7 @@ class TestSwiftAsyncBreakpoints(lldbtest.TestBase): def test(self): """Test async breakpoints""" self.build() + self.runCmd("settings set language.enable-filter-for-line-breakpoints true") filespec = lldb.SBFileSpec("main.swift") target, process, thread, breakpoint1 = lldbutil.run_to_source_breakpoint( self, "Breakpoint1", filespec diff --git a/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/Makefile b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/Makefile new file mode 100644 index 0000000000000..2a69023633b34 --- /dev/null +++ b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift + +include Makefile.rules diff --git a/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/TestSwiftAsyncBreakpointsOverManyFunclets.py b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/TestSwiftAsyncBreakpointsOverManyFunclets.py new file mode 100644 index 0000000000000..32d8e37fa31ac --- /dev/null +++ b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/TestSwiftAsyncBreakpointsOverManyFunclets.py @@ -0,0 +1,29 @@ +import lldb +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbtest as lldbtest +import lldbsuite.test.lldbutil as lldbutil + + +class TestSwiftAsyncBreakpoints(lldbtest.TestBase): + @swiftTest + @skipIfLinux + def test(self): + """Test async that async breakpoints are not filtered when the same + statement is present across multiple funclets""" + self.build() + filespec = lldb.SBFileSpec("main.swift") + target, process, thread, breakpoint1 = lldbutil.run_to_source_breakpoint( + self, "breakpoint_start", filespec + ) + breakpoint = target.BreakpointCreateBySourceRegex("breakhere", filespec) + self.assertEquals(breakpoint.GetNumLocations(), 2) + + process.Continue() + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) + self.assertEquals(thread.GetStopDescription(128), "breakpoint 2.1") + self.expect("expr argument", substrs=["1"]) + + process.Continue() + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) + self.assertEquals(thread.GetStopDescription(128), "breakpoint 2.2") + self.expect("expr argument", substrs=["2"]) diff --git a/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/main.swift b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/main.swift new file mode 100644 index 0000000000000..2174f9a524131 --- /dev/null +++ b/lldb/test/API/lang/swift/async_breakpoints_over_many_funclets/main.swift @@ -0,0 +1,27 @@ +enum MyError: Error { + case MyError1 + case MyError2 +} + +func willthrow(_ arg: Int) async throws { + if arg == 1 { throw MyError.MyError1 } else { throw MyError.MyError2 } +} + +func foo(_ argument: Int) async { + do { + switch argument { + case 1: + try await willthrow(1) + case 2: + try await willthrow(2) + default: + return + } + } catch { + print("breakhere") + } +} + +print("breakpoint_start") +await foo(1) +await foo(2) diff --git a/lldb/test/API/lang/swift/protocols/stepping_through_witness/TestSwiftSteppingThroughWitness.py b/lldb/test/API/lang/swift/protocols/stepping_through_witness/TestSwiftSteppingThroughWitness.py index 69724e8647711..c53cccdb77e31 100644 --- a/lldb/test/API/lang/swift/protocols/stepping_through_witness/TestSwiftSteppingThroughWitness.py +++ b/lldb/test/API/lang/swift/protocols/stepping_through_witness/TestSwiftSteppingThroughWitness.py @@ -22,6 +22,9 @@ def test_step_in_and_out(self): self, "break here", lldb.SBFileSpec("main.swift") ) + # First, get to the await call. + thread.StepOver() + thread.StepInto() stop_reason = thread.GetStopReason() self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete) @@ -48,6 +51,9 @@ def test_step_over(self): self, "break here", lldb.SBFileSpec("main.swift") ) + # First, get to the await call. + thread.StepOver() + thread.StepOver() stop_reason = thread.GetStopReason() self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete) @@ -55,4 +61,4 @@ def test_step_over(self): self.assertIn("doMath", frame0.GetFunctionName()) line_entry = frame0.GetLineEntry() - self.assertEqual(14, line_entry.GetLine()) + self.assertEqual(15, line_entry.GetLine()) diff --git a/lldb/test/API/lang/swift/protocols/stepping_through_witness/main.swift b/lldb/test/API/lang/swift/protocols/stepping_through_witness/main.swift index 3ce69af2702ce..3ef9a01c77fdb 100644 --- a/lldb/test/API/lang/swift/protocols/stepping_through_witness/main.swift +++ b/lldb/test/API/lang/swift/protocols/stepping_through_witness/main.swift @@ -10,7 +10,8 @@ class SlowRandomNumberGenerator: RandomNumberGenerator { } func doMath(with rng: RNG) async { - let y = await rng.random(in: 101...200) // break here + print("break here") + let y = await rng.random(in: 101...200) print("Y is \(y)") }