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 <[email protected]> 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() { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
