[Lldb-commits] [lldb] [lldb] Read the bool that a scripted thread plan's should_step returns (PR #215521)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Sep 4 08:25:01 PDT 2026
https://github.com/alexey-gusarov updated https://github.com/llvm/llvm-project/pull/215521
>From 8fa79f53337fe1ff150de25df483923d475de1d9 Mon Sep 17 00:00:00 2001
From: Alexey Gusarov <alexey.gusarov at gmail.com>
Date: Tue, 11 Aug 2026 09:49:17 +0200
Subject: [PATCH] [lldb] Read the bool returned by a scripted thread plan's
should_step
`ScriptedThreadPlanPythonInterface::GetRunState()` used
`GetUnsignedIntegerValue()` to read the script's return value, whereas a Python
`bool` arrives as a `StructuredData::Boolean`.
As a result, a boolean return value was ignored and every scripted plan stepped.
An integer, on the other hand, was interpreted as a `StateType`.
The documented return type for `should_step` is `bool`
(`lldb/docs/use/tutorials/automating-stepping-logic.md`).
Read the return value as a bool. Any other value is treated as invalid, logged,
and causes the plan to step.
With this change, the behavior becomes:
should_step returns before after
True step step
False step run
0, 1, or lldb.eStateRunning run step
lldb.eStateStepping step step
Part (1) of #215189.
Assisted-by: Claude Code (Claude Opus 5)
---
.../ScriptedThreadPlanPythonInterface.cpp | 17 +++++-
.../recognizer/step-through/recognizer.py | 2 +
.../functionalities/step_scripted/Steps.py | 50 ++++++++++++++++
.../step_scripted/TestStepScripted.py | 58 +++++++++++++++++++
.../API/functionalities/step_scripted/main.c | 2 +-
5 files changed, 126 insertions(+), 3 deletions(-)
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