[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