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

Reply via email to