[Lldb-commits] [lldb] Fix a crash when a stop hook deletes itself in its callback. (PR #160416)

via lldb-commits lldb-commits at lists.llvm.org
Thu Nov 6 11:43:46 PST 2025


https://github.com/jimingham updated https://github.com/llvm/llvm-project/pull/160416

>From 76644944606ed28affc01d7638b99372ccac86f7 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Tue, 23 Sep 2025 13:44:33 -0700
Subject: [PATCH 1/3] Fix a crash when a stop hook deletes itself in its
 callback.

---
 lldb/source/Target/Target.cpp                 |  8 ++++-
 .../target/stop-hooks/TestStopHookScripted.py | 33 +++++++++++++++++++
 .../commands/target/stop-hooks/stop_hook.py   | 24 ++++++++++++++
 3 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index fa98c24606492..e94fe854f8157 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3171,7 +3171,13 @@ bool Target::RunStopHooks(bool at_initial_stop) {
   bool should_stop = false;
   bool requested_continue = false;
 
-  for (auto stop_entry : m_stop_hooks) {
+  // A stop hook might get deleted while running stop hooks.  
+  // We have to decide what that means.  We will follow the rule that deleting
+  // a stop hook while processing these stop hooks will delete it for FUTURE
+  // stops but not this stop.  The easiest way to do that is to copy the
+  // stop hooks and iterate over the copy.
+  StopHookCollection stop_hooks_copy = m_stop_hooks;
+  for (auto stop_entry : stop_hooks_copy) {
     StopHookSP cur_hook_sp = stop_entry.second;
     if (!cur_hook_sp->IsActive())
       continue;
diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
index 954cac1592435..c0e3c2a8fcf31 100644
--- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
+++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
@@ -48,6 +48,39 @@ def test_bad_handler(self):
             "Got the right error",
         )
 
+    def test_self_deleting(self):
+        """Test that we can handle a stop hook that deletes itself"""
+        self.script_setup()
+        # Run to the first breakpoint before setting the stop hook
+        # so we don't have to figure out where it showed up in the new
+        # target.
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "Stop here first", self.main_source_file
+        )
+
+        # Now add our stop hook and register it:
+        result = lldb.SBCommandReturnObject()
+        command = "target stop-hook add -P stop_hook.self_deleting_stop"
+        self.interp.HandleCommand(command, result)
+        self.assertCommandReturn(result, f"Added my stop hook: {result.GetError()}")
+        
+        result_str = result.GetOutput()
+        p = re.compile("Stop hook #([0-9]+) added.")
+        m = p.match(result_str)
+        current_stop_hook_id = m.group(1)
+        command = "command script add -o -f stop_hook.handle_stop_hook_id handle_id"
+        self.interp.HandleCommand(command, result)
+        self.assertCommandReturn(result, "Added my command")
+
+        command = f"handle_id {current_stop_hook_id}"
+        self.interp.HandleCommand(command, result)
+        self.assertCommandReturn(result, "Registered my stop ID")
+
+        # Now step the process and make sure the stop hook was deleted.
+        thread.StepOver()
+        self.interp.HandleCommand("target stop-hook list", result)
+        self.assertEqual(result.GetOutput().rstrip(), "No stop hooks.", "Deleted hook")
+        
     def test_stop_hooks_scripted(self):
         """Test that a scripted stop hook works with no specifiers"""
         self.stop_hooks_scripted(5, "-I false")
diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
index cb7a4337c40d4..142c4ab4c5d89 100644
--- a/lldb/test/API/commands/target/stop-hooks/stop_hook.py
+++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
@@ -48,3 +48,27 @@ def handle_stop(self):
 class no_handle_stop:
     def __init__(self, target, extra_args, dict):
         print("I am okay")
+
+class self_deleting_stop:
+    def __init__(self, target, extra_args, dict):
+        self.target = target
+
+    def handle_stop(self, exe_ctx, stream):
+        interp = exe_ctx.target.debugger.GetCommandInterpreter()
+        result = lldb.SBCommandReturnObject()
+        interp.HandleCommand("handle_id", result)
+        id_str = result.GetOutput().rstrip()
+        
+        command = f"target stop-hook delete {id_str}"
+        interp.HandleCommand(command, result)
+
+stop_hook_id = 0
+def handle_stop_hook_id(debugger, command, exe_ctx, result, extra_args):
+    global stop_hook_id
+    if command == "":
+        result.AppendMessage(str(stop_hook_id))
+    else:
+        stop_hook_id = int(command)
+
+      
+                         

>From 95cec34938436d5eef25c73ce71da1bdddeef387 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Tue, 23 Sep 2025 17:20:21 -0700
Subject: [PATCH 2/3] formatting

---
 lldb/source/Target/Target.cpp                            | 2 +-
 .../commands/target/stop-hooks/TestStopHookScripted.py   | 4 ++--
 lldb/test/API/commands/target/stop-hooks/stop_hook.py    | 9 +++++----
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index e94fe854f8157..baf28c6de0ec7 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3171,7 +3171,7 @@ bool Target::RunStopHooks(bool at_initial_stop) {
   bool should_stop = false;
   bool requested_continue = false;
 
-  // A stop hook might get deleted while running stop hooks.  
+  // A stop hook might get deleted while running stop hooks.
   // We have to decide what that means.  We will follow the rule that deleting
   // a stop hook while processing these stop hooks will delete it for FUTURE
   // stops but not this stop.  The easiest way to do that is to copy the
diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
index c0e3c2a8fcf31..8e91781b87a39 100644
--- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
+++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
@@ -63,7 +63,7 @@ def test_self_deleting(self):
         command = "target stop-hook add -P stop_hook.self_deleting_stop"
         self.interp.HandleCommand(command, result)
         self.assertCommandReturn(result, f"Added my stop hook: {result.GetError()}")
-        
+
         result_str = result.GetOutput()
         p = re.compile("Stop hook #([0-9]+) added.")
         m = p.match(result_str)
@@ -80,7 +80,7 @@ def test_self_deleting(self):
         thread.StepOver()
         self.interp.HandleCommand("target stop-hook list", result)
         self.assertEqual(result.GetOutput().rstrip(), "No stop hooks.", "Deleted hook")
-        
+
     def test_stop_hooks_scripted(self):
         """Test that a scripted stop hook works with no specifiers"""
         self.stop_hooks_scripted(5, "-I false")
diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
index 142c4ab4c5d89..a41190baeadf2 100644
--- a/lldb/test/API/commands/target/stop-hooks/stop_hook.py
+++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
@@ -49,6 +49,7 @@ class no_handle_stop:
     def __init__(self, target, extra_args, dict):
         print("I am okay")
 
+
 class self_deleting_stop:
     def __init__(self, target, extra_args, dict):
         self.target = target
@@ -58,17 +59,17 @@ def handle_stop(self, exe_ctx, stream):
         result = lldb.SBCommandReturnObject()
         interp.HandleCommand("handle_id", result)
         id_str = result.GetOutput().rstrip()
-        
+
         command = f"target stop-hook delete {id_str}"
         interp.HandleCommand(command, result)
 
+
 stop_hook_id = 0
+
+
 def handle_stop_hook_id(debugger, command, exe_ctx, result, extra_args):
     global stop_hook_id
     if command == "":
         result.AppendMessage(str(stop_hook_id))
     else:
         stop_hook_id = int(command)
-
-      
-                         

>From 73ac3a038abd0c451b354b93018f56ddb1038c11 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Wed, 24 Sep 2025 16:06:04 -0700
Subject: [PATCH 3/3] Document the one-stop-hook-deletes-another behavior.

---
 lldb/source/Commands/CommandObjectTarget.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 940be42d1b6e3..e3f169ab4fa10 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5117,6 +5117,15 @@ class CommandObjectTargetStopHookDelete : public CommandObjectParsed {
       : CommandObjectParsed(interpreter, "target stop-hook delete",
                             "Delete a stop-hook.",
                             "target stop-hook delete [<idx>]") {
+    SetHelpLong(
+        R"(
+Deletes the stop hook by index.
+
+At any given stop, all enabled stop hooks that pass the stop filter will
+get a chance to run.  That means if one stop-hook deletes another stop hook 
+while executing, the deleted stop hook will still fire for the stop at which 
+it was deleted.
+        )");
     AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
   }
 



More information about the lldb-commits mailing list