[Lldb-commits] [lldb] dd0ee5e - [lldb] Read the bool that a scripted thread plan's should_step returns (#215521)

via lldb-commits lldb-commits at lists.llvm.org
Thu Sep 17 15:48:30 PDT 2026


Author: alexey-gusarov
Date: 2026-09-17T22:48:22Z
New Revision: dd0ee5e10da57e2b1ebf0cbf066b32a8ffe92c28

URL: https://github.com/llvm/llvm-project/commit/dd0ee5e10da57e2b1ebf0cbf066b32a8ffe92c28
DIFF: https://github.com/llvm/llvm-project/commit/dd0ee5e10da57e2b1ebf0cbf066b32a8ffe92c28.diff

LOG: [lldb] Read the bool that a scripted thread plan's should_step returns (#215521)

Fixes part (1) of #215189.

`ScriptedThreadPlanPythonInterface::GetRunState()` used
`GetUnsignedIntegerValue()` to read the script's `should_step` return
value. A
Python `bool` arrives as a `StructuredData::Boolean`, so the return
value was
discarded and every scripted thread plan reported `eStateStepping`: a
plan
returning `False` single-stepped exactly like one returning `True`.

The documented contract for `should_step` is a bool
(`lldb/docs/use/tutorials/automating-stepping-logic.md` and
`lldb/examples/python/templates/scripted_thread_plan.py`). Read it as
one; any
other return value is logged and causes the plan to step.

### Behavior change

| `should_step` returns             | before  | after   |
| --------------------------------- | ------- | ------- |
| `True`                            | step    | step    |
| `False`                           | step    | **run** |
| `0`, `1`, or `lldb.eStateRunning` | **run** | step    |
| `lldb.eStateStepping`             | step    | step    |

The integer case is an intentional behavior change. Integers used to be
reinterpreted as a `StateType`, which also made them the only way to
make a
scripted plan run while boolean `False` was being discarded. They are
not part
of the documented `should_step` contract.

The behavior for a missing `should_step` is unchanged by this PR: it
continues
to step.

### In-tree user of the old behavior


`commands/frame/recognizer/step-through/TestFrameRecognizerStepThrough.py`
returns `False` from `should_step` and relied on being single-stepped
anyway.
With `False` honored, its `ThreadPlanRunToAddress` sub-plan handles the
stop
that completes it, so `explains_stop` is not called.

Update the plan to notice completion of the sub-plan in `should_stop`
and call
`SetPlanComplete` there, as described by the `ScriptedThreadPlan`
documentation.

### Testing

`lldb/test/API/functionalities/step_scripted/` covers:

* `should_step == True` stepping one instruction;
* `should_step == False` running to the next breakpoint;
* non-bool integer and `StateType` returns stepping.

The `False` and invalid-return cases fail before this change on both
Linux and
Windows.

### Landing order

This change allows scripted plans returning `False` from `should_step`
to reach
the `SetAutoContinue(true)` path in
`Thread::SetupToStepOverBreakpointIfNeeded()`. #215522 fixes the
independent
thread-plan-stack issue on that path. The two changes are independent,
but are
best landed together.

---

Tool-use disclosure, per the LLVM AI tool policy: this change was
prepared with
the assistance of Claude Code (`Assisted-by:` trailer on the commit).
Everything
in the change was read and reviewed before posting.

Co-authored-by: Med Ismail Bennani <ismail at bennani.ma>

Added: 
    

Modified: 
    lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
    lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
    lldb/test/API/functionalities/step_scripted/Steps.py
    lldb/test/API/functionalities/step_scripted/TestStepScripted.py
    lldb/test/API/functionalities/step_scripted/main.c

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
index b18823ea60960..2d0f6fd9d9fe9 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
@@ -10,7 +10,9 @@
 
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Target/ThreadPlan.h"
+#include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
+#include "lldb/Utility/StreamString.h"
 #include "lldb/lldb-enumerations.h"
 
 #include "../SWIGPythonBridge.h"
@@ -86,8 +88,19 @@ lldb::StateType ScriptedThreadPlanPythonInterface::GetRunState() {
                                                     error))
     return lldb::eStateStepping;
 
-  return static_cast<lldb::StateType>(obj->GetUnsignedIntegerValue(
-      static_cast<uint32_t>(lldb::eStateStepping)));
+  // A thread plan's run state can formally be eStateSuspended, but that state
+  // is decided by the thread plan negotiation, not by the plan itself.  So a
+  // scripted plan's contract is only running or stepping: a bool.
+  if (StructuredData::Boolean *should_step = obj->GetAsBoolean())
+    return should_step->GetValue() ? lldb::eStateStepping : lldb::eStateRunning;
+
+  if (Log *log = GetLog(LLDBLog::Script)) {
+    StreamString reply;
+    obj->Dump(reply, /*pretty_print=*/false);
+    LLDB_LOG(log, "should_step returned {0}, not a bool; stepping.",
+             reply.GetData());
+  }
+  return lldb::eStateStepping;
 }
 
 llvm::Error

diff  --git a/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py b/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
index e74cde6233be2..cb41a7c200ad2 100644
--- a/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
+++ b/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
@@ -81,6 +81,8 @@ def explains_stop(self, event: lldb.SBEvent):
             return False
 
     def should_stop(self):
+        if self.addr_plan.IsPlanComplete():
+            self.thread_plan.SetPlanComplete(True)
         return self.thread_plan.IsPlanComplete()
 
     def should_step(self):

diff  --git a/lldb/test/API/functionalities/step_scripted/Steps.py b/lldb/test/API/functionalities/step_scripted/Steps.py
index b36bb5f5f9048..e6f6b75167c76 100644
--- a/lldb/test/API/functionalities/step_scripted/Steps.py
+++ b/lldb/test/API/functionalities/step_scripted/Steps.py
@@ -140,3 +140,53 @@ def should_step(self):
 
     def explains_stop(self, event):
         return True
+
+
+# Top of the plan stack, no child plan: the thread's run state is whatever
+# should_step answers.
+class RunToNextBreakpoint:
+    def __init__(self, thread_plan, args_data):
+        self.thread_plan = thread_plan
+
+    def explains_stop(self, event):
+        return False
+
+    def should_stop(self, event):
+        self.thread_plan.SetPlanComplete(True)
+        return True
+
+    def should_step(self):
+        return False
+
+
+class StepOneInstruction(RunToNextBreakpoint):
+    def should_step(self):
+        return True
+
+
+class ReturnsZeroFromShouldStep(RunToNextBreakpoint):
+    """Answers should_step with an int, not a bool."""
+
+    def should_step(self):
+        return 0
+
+
+class ReturnsOneFromShouldStep(RunToNextBreakpoint):
+    """Answers should_step with an int, not a bool."""
+
+    def should_step(self):
+        return 1
+
+
+class ReturnsStateRunningFromShouldStep(RunToNextBreakpoint):
+    """Answers should_step with a StateType, not a bool."""
+
+    def should_step(self):
+        return lldb.eStateRunning
+
+
+class ReturnsStateSteppingFromShouldStep(RunToNextBreakpoint):
+    """Answers should_step with a StateType, not a bool."""
+
+    def should_step(self):
+        return lldb.eStateStepping

diff  --git a/lldb/test/API/functionalities/step_scripted/TestStepScripted.py b/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
index a08a39a710596..5fccb00bb60e1 100644
--- a/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
+++ b/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
@@ -45,6 +45,64 @@ def test_constructor_error_preserves_traceback(self):
             "ValueError: scripted plan construction failed", result.GetError()
         )
 
+    def run_scripted_plan_between_breakpoints(self, plan_name):
+        """Stop at the first breakpoint, set a second one further on, run
+        plan_name from there, and return the thread and the second breakpoint."""
+        self.build()
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "Set a breakpoint here", self.main_source_file
+        )
+        second = target.BreakpointCreateBySourceRegex(
+            "Run to this breakpoint", self.main_source_file
+        )
+        self.assertTrue(second.GetNumLocations() > 0, VALID_BREAKPOINT)
+        err = thread.StepUsingScriptedThreadPlan(plan_name)
+        self.assertSuccess(err)
+        return thread, second
+
+    def assert_plan_steps_one_instruction(self, plan_name):
+        thread, second = self.run_scripted_plan_between_breakpoints(plan_name)
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
+        self.assertEqual(second.GetHitCount(), 0)
+
+    def test_should_step_false_runs_to_the_next_breakpoint(self):
+        """should_step returning False lets the process run to the next
+        breakpoint instead of single-stepping."""
+        thread, second = self.run_scripted_plan_between_breakpoints(
+            "Steps.RunToNextBreakpoint"
+        )
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+        self.assertEqual(second.GetHitCount(), 1)
+
+    def test_should_step_true_steps_one_instruction(self):
+        """should_step returning True single-steps, so the next breakpoint is
+        not reached."""
+        self.assert_plan_steps_one_instruction("Steps.StepOneInstruction")
+
+    def test_should_step_returning_zero_steps(self):
+        """should_step returning an int instead of a bool is not an answer, and
+        the plan steps."""
+        self.assert_plan_steps_one_instruction("Steps.ReturnsZeroFromShouldStep")
+
+    def test_should_step_returning_one_steps(self):
+        """should_step returning an int instead of a bool is not an answer, and
+        the plan steps."""
+        self.assert_plan_steps_one_instruction("Steps.ReturnsOneFromShouldStep")
+
+    def test_should_step_returning_state_running_steps(self):
+        """should_step returning a StateType instead of a bool is not an answer,
+        and the plan steps."""
+        self.assert_plan_steps_one_instruction(
+            "Steps.ReturnsStateRunningFromShouldStep"
+        )
+
+    def test_should_step_returning_state_stepping_steps(self):
+        """should_step returning a StateType instead of a bool is not an answer,
+        and the plan steps."""
+        self.assert_plan_steps_one_instruction(
+            "Steps.ReturnsStateSteppingFromShouldStep"
+        )
+
     def step_out_with_scripted_plan(self, name):
         (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
             self, "Set a breakpoint here", self.main_source_file

diff  --git a/lldb/test/API/functionalities/step_scripted/main.c b/lldb/test/API/functionalities/step_scripted/main.c
index 9023120c44312..75497be1e377a 100644
--- a/lldb/test/API/functionalities/step_scripted/main.c
+++ b/lldb/test/API/functionalities/step_scripted/main.c
@@ -4,7 +4,7 @@ void foo() {
   int foo = 10; 
   printf("%d\n", foo); // Set a breakpoint here. 
   foo = 20;
-  printf("%d\n", foo);
+  printf("%d\n", foo); // Run to this breakpoint.
 }
 
 int main() {


        


More information about the lldb-commits mailing list