https://github.com/alexey-gusarov updated 
https://github.com/llvm/llvm-project/pull/215522

>From ac0b0571d82e7855d021096ae27e967e132588ef Mon Sep 17 00:00:00 2001
From: Alexey Gusarov <[email protected]>
Date: Tue, 11 Aug 2026 09:49:17 +0200
Subject: [PATCH] [lldb] Don't consult the plan stack for a stop an
 auto-continue will discard

`Thread::ShouldStop()` walks the plan stack from the current plan. When a
plan's `ShouldAutoContinue()` returned true, the subsequent plans were still
asked `ShouldStop()`, and their answers were then discarded.

Asking is not free: a plan that reports `MischiefManaged()` is popped and tears
its state down. `ThreadPlanRunToAddress` deletes the breakpoint it was running
to, so a plan queued while the thread sat on a breakpoint site lost its target
on the very first stop, the single step off the site.

Tighten the `ShouldAutoContinue()` contract in `ThreadPlan.h`: an
auto-continuing plan short-circuits the `ShouldStop()` of the subsequent plans.
`Thread::ShouldStop()` pops it and resumes without consulting them.

With this change, from a stop on a breakpoint site:

    RunToAddress(next instruction)               before          after
    directly, or queued by a scripted plan       runs past it    stops there
    onto a user breakpoint                       hit reported    hit reported

Part (2) of #215189.

Assisted-by: Claude Code (Claude Opus 5)
---
 lldb/include/lldb/Target/ThreadPlan.h         |  4 +-
 lldb/source/Target/Thread.cpp                 |  3 +
 .../step_over_breakpoint_site/Makefile        |  3 +
 .../TestStepOverBreakpointSite.py             | 96 +++++++++++++++++++
 .../step_over_breakpoint_site/main.c          | 10 ++
 .../run_to_address_plan.py                    | 20 ++++
 6 files changed, 134 insertions(+), 2 deletions(-)
 create mode 100644 
lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/Makefile
 create mode 100644 
lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/TestStepOverBreakpointSite.py
 create mode 100644 
lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/main.c
 create mode 100644 
lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/run_to_address_plan.py

diff --git a/lldb/include/lldb/Target/ThreadPlan.h 
b/lldb/include/lldb/Target/ThreadPlan.h
index a7bac8cc5ecf6..39ac0ed7b841e 100644
--- a/lldb/include/lldb/Target/ThreadPlan.h
+++ b/lldb/include/lldb/Target/ThreadPlan.h
@@ -380,8 +380,8 @@ class ThreadPlan : public 
std::enable_shared_from_this<ThreadPlan>,
   /// subsequently processed plans.
   ///
   /// When processing the thread plan stack, this function gives plans the
-  /// ability to continue - even when subsequent plans return true from
-  /// `ShouldStop`. \see Thread::ShouldStop
+  /// ability to continue. If it returns true, the `ShouldStop` of
+  /// subsequently processed plans is not consulted. \see Thread::ShouldStop
   virtual bool ShouldAutoContinue(Event *event_ptr) { return false; }
 
   // Whether a "stop class" event should be reported to the "outside world".
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index b8bfc774b81b3..b6885285e4bd0 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -944,6 +944,9 @@ bool Thread::ShouldStop(Event *event_ptr) {
             override_stop = true;
             LLDB_LOGF(log, "Plan %s auto-continue: true.",
                       current_plan->GetName());
+            // The ShouldAutoContinue contract: subsequent plans are not asked.
+            PopPlan();
+            break;
           }
 
           // If a Controlling Plan wants to stop, we let it. Otherwise, see if
diff --git 
a/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/Makefile 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git 
a/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/TestStepOverBreakpointSite.py
 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/TestStepOverBreakpointSite.py
new file mode 100644
index 0000000000000..97f81ce07823a
--- /dev/null
+++ 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/TestStepOverBreakpointSite.py
@@ -0,0 +1,96 @@
+"""
+Test thread plans queued while the thread sits on an enabled breakpoint site.
+lldb first steps off the site with a ThreadPlanStepOverBreakpoint; that plan
+auto-continues without consulting the plans already on the stack.
+"""
+
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class StepOverBreakpointSiteTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def run_to_breakpoint(self):
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Set a breakpoint here", lldb.SBFileSpec("main.c")
+        )
+        return target, process, thread
+
+    def pc_after(self, target, thread, count):
+        """Return the load address `count` instructions after the current 
one."""
+        pc = thread.GetFrameAtIndex(0).GetPCAddress()
+        instructions = target.ReadInstructions(pc, count + 1)
+        self.assertEqual(instructions.GetSize(), count + 1)
+        return (
+            instructions.GetInstructionAtIndex(count)
+            .GetAddress()
+            .GetLoadAddress(target)
+        )
+
+    def run_to_next_instruction(self, target, process, thread):
+        next_pc = self.pc_after(target, thread, 1)
+        thread.RunToAddress(next_pc)
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.assertEqual(thread.GetFrameAtIndex(0).GetPC(), next_pc)
+
+    def test_run_to_address_from_a_breakpoint_site(self):
+        """RunToAddress with the PC on a breakpoint site stops at the 
target."""
+        target, process, thread = self.run_to_breakpoint()
+        self.run_to_next_instruction(target, process, thread)
+
+    def test_run_to_address_off_a_breakpoint_site(self):
+        """RunToAddress with the PC off the breakpoint site stops at the 
target."""
+        target, process, thread = self.run_to_breakpoint()
+        thread.StepInstruction(False)
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.run_to_next_instruction(target, process, thread)
+
+    def test_run_to_address_two_instructions_from_a_breakpoint_site(self):
+        """RunToAddress reaches a target beyond the single step off the 
site."""
+        target, process, thread = self.run_to_breakpoint()
+        target_pc = self.pc_after(target, thread, 2)
+        thread.RunToAddress(target_pc)
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.assertEqual(thread.GetFrameAtIndex(0).GetPC(), target_pc)
+
+    def test_user_breakpoint_at_the_target_is_still_reported(self):
+        """A user breakpoint at the RunToAddress target is reported as hit."""
+        target, process, thread = self.run_to_breakpoint()
+        next_pc = self.pc_after(target, thread, 1)
+        user_bp = target.BreakpointCreateByAddress(next_pc)
+        self.assertTrue(user_bp.GetNumLocations() > 0, VALID_BREAKPOINT)
+        thread.RunToAddress(next_pc)
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.assertEqual(thread.GetFrameAtIndex(0).GetPC(), next_pc)
+        self.assertStopReason(thread.GetStopReason(), 
lldb.eStopReasonBreakpoint)
+        self.assertEqual(user_bp.GetHitCount(), 1)
+
+    def test_the_stepped_over_breakpoint_is_hit_again(self):
+        """The breakpoint stepped over before RunToAddress resumes is hit 
again."""
+        target, process, thread = self.run_to_breakpoint()
+        breakpoint = target.GetBreakpointAtIndex(0)
+        self.assertEqual(breakpoint.GetHitCount(), 1)
+        self.run_to_next_instruction(target, process, thread)
+        process.Continue()
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.assertStopReason(thread.GetStopReason(), 
lldb.eStopReasonBreakpoint)
+        self.assertEqual(breakpoint.GetHitCount(), 2)
+
+    def test_scripted_plan_queueing_the_run_to_address(self):
+        """A scripted plan that queues RunToAddress from a breakpoint site 
stops
+        at the target."""
+        target, process, thread = self.run_to_breakpoint()
+        self.runCmd("command script import run_to_address_plan.py")
+        next_pc = self.pc_after(target, thread, 1)
+        args = lldb.SBStructuredData()
+        args.SetFromJSON('{"addr":%d}' % next_pc)
+        err = thread.StepUsingScriptedThreadPlan(
+            "run_to_address_plan.RunToAddress", args, True
+        )
+        self.assertSuccess(err)
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        self.assertEqual(thread.GetFrameAtIndex(0).GetPC(), next_pc)
diff --git 
a/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/main.c 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/main.c
new file mode 100644
index 0000000000000..0390a53881454
--- /dev/null
+++ b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/main.c
@@ -0,0 +1,10 @@
+static int f(int x) {
+  int y = x + 1; // Set a breakpoint here.
+  return y;
+}
+
+int main() {
+  int a = f(1);
+  a += f(2);
+  return a;
+}
diff --git 
a/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/run_to_address_plan.py
 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/run_to_address_plan.py
new file mode 100644
index 0000000000000..8f5e862d57349
--- /dev/null
+++ 
b/lldb/test/API/functionalities/thread_plan/step_over_breakpoint_site/run_to_address_plan.py
@@ -0,0 +1,20 @@
+import lldb
+
+
+class RunToAddress:
+    """Queues a run-to-address sub-plan for the address it is handed."""
+
+    def __init__(self, thread_plan, args_data):
+        self.thread_plan = thread_plan
+        target = thread_plan.GetThread().GetProcess().GetTarget()
+        addr = args_data.GetValueForKey("addr").GetUnsignedIntegerValue()
+        self.sub_plan = thread_plan.QueueThreadPlanForRunToAddress(
+            lldb.SBAddress(addr, target), lldb.SBError()
+        )
+
+    def explains_stop(self, event):
+        return False
+
+    def should_stop(self, event):
+        self.thread_plan.SetPlanComplete(True)
+        return True

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to