[Lldb-commits] [lldb] [lldb-dap] Reset ignore count on empty hit condition (PR #230492)

Sergei Druzhkov via lldb-commits lldb-commits at lists.llvm.org
Mon Oct 19 04:47:21 PDT 2026


https://github.com/DrSergei updated https://github.com/llvm/llvm-project/pull/230492

>From 381179d0c0b4c0c8f5e4abb6a22398a5241bea7d Mon Sep 17 00:00:00 2001
From: Sergei Druzhkov <serzhdruzhok at gmail.com>
Date: Fri, 9 Oct 2026 15:41:05 +0300
Subject: [PATCH] [lldb-dap] Reset ignore count on empty hit condition

---
 .../breakpoint/TestDAP_setBreakpoints.py      | 42 +++++++++++++
 .../TestDAP_setDataBreakpoints.py             | 42 +++++++++++++
 lldb/tools/lldb-dap/Breakpoint.cpp            |  5 ++
 lldb/tools/lldb-dap/Breakpoint.h              |  2 +-
 lldb/tools/lldb-dap/BreakpointBase.cpp        |  4 +-
 lldb/tools/lldb-dap/BreakpointBase.h          |  2 +-
 .../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 12 ++--
 lldb/tools/lldb-dap/Protocol/ProtocolTypes.h  |  8 +--
 lldb/tools/lldb-dap/Watchpoint.cpp            |  5 ++
 lldb/unittests/DAP/ProtocolTypesTest.cpp      | 61 +++++++++++++++++++
 10 files changed, 169 insertions(+), 14 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py
index 30c4410b6c6ee..55d3ba75e0726 100644
--- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py
@@ -365,6 +365,48 @@ def test_functionality(self):
         session.set_source_breakpoints(self.main_path, [])
         session.continue_to_exit()
 
+    @skipIfWindows
+    def test_remove_hit_condition(self):
+        """Tests removing hitCondition restores a stopping breakpoint."""
+        loop_line = line_number("main.cpp", "// break loop")
+
+        program = self.getBuildArtifact("a.out")
+
+        session = self.build_and_create_session()
+        with session.configure(LaunchArgs(program)) as ctx:
+            [loop_bp] = session.resolve_source_breakpoints(self.main_path, [loop_line])
+        process_event = ctx.process_event
+
+        stop_event = session.verify_stopped_on_breakpoint(loop_bp, after=process_event)
+
+        # Set a hitCondition, then omit it before any hits consume the ignore count.
+        [hit_condition_bp] = session.resolve_source_breakpoints(
+            self.main_path, [SourceBreakpoint(loop_line, hitCondition="3")]
+        )
+        self.assertEqual(
+            loop_bp,
+            hit_condition_bp,
+            "existing breakpoint should have its hitCondition updated",
+        )
+        [cleared_bp] = session.resolve_source_breakpoints(
+            self.main_path, [SourceBreakpoint(loop_line)]
+        )
+        self.assertEqual(
+            loop_bp,
+            cleared_bp,
+            "existing breakpoint should have its hitCondition removed",
+        )
+
+        # Check that the updated breakpoint stops at the next iteration.
+        stop_event = session.continue_to_breakpoint(loop_bp)
+        thread_ctx = session.thread_context_from(stop_event)
+        i_var = thread_ctx.top_frame().locals["i"]
+        self.assertEqual(i_var.value_as_int, 1, "i != 1 after removing hitCondition")
+
+        # Clear breakpoints and exit.
+        session.set_source_breakpoints(self.main_path, [])
+        session.continue_to_exit()
+
     @skipIfWindows
     def test_column_breakpoints(self):
         """Test setting multiple breakpoints in the same line at different columns."""
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
index 2be422ae3e092..7ed8cbae48aca 100644
--- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -293,6 +293,48 @@ def test_hit_count_preserved(self):
         session.set_data_breakpoints([])
         session.continue_to_exit()
 
+    @skipIfWindows
+    def test_remove_hit_condition(self):
+        """Tests removing hitCondition restores a stopping data breakpoint."""
+        source = "main.cpp"
+        program = self.getBuildArtifact("a.out")
+        session = self.build_and_create_session()
+        second_loop_break_line = line_number(source, "// second loop breakpoint")
+        with session.configure(LaunchArgs(program)) as ctx:
+            session.resolve_source_breakpoints(source, [second_loop_break_line])
+        stop_event = session.verify_stopped_on_breakpoint(after=ctx.process_event)
+
+        top_frame_ctx = session.top_frame_from(stop_event)
+        frame_id = top_frame_ctx.frame.id
+        locals_ref = top_frame_ctx.locals.variablesReference
+        response_x = session.data_breakpoint_info("x", locals_ref, frame_id)
+        x_data_id = self.expect_not_none(response_x.body.dataId)
+        session.set_source_breakpoints(source, [])
+
+        # Set a hitCondition, then omit it before any hits consume the ignore count.
+        set_response = session.set_data_breakpoints(
+            [DataBreakpoint(dataId=x_data_id, accessType="write", hitCondition="3")]
+        )
+        [bp_hit] = set_response.body.breakpoints
+        self.assertTrue(bp_hit.verified)
+        x_bp_id = self.expect_not_none(bp_hit.id)
+
+        set_response = session.set_data_breakpoints(
+            [DataBreakpoint(dataId=x_data_id, accessType="write")]
+        )
+        [bp_clear] = set_response.body.breakpoints
+        self.assertTrue(bp_clear.verified)
+        self.assertEqual(bp_clear.id, x_bp_id)
+
+        # Check that the updated data breakpoint stops at the next write.
+        stop_event = session.continue_to_breakpoint(x_bp_id)
+        top_frame = session.top_frame_from(stop_event)
+        self.assertEqual(top_frame.locals["x"].value, "2")
+
+        session.set_data_breakpoints([])
+        session.continue_to_exit()
+
+    @skipIfWindows
     def test_type_change_recreates(self):
         """Test setDataBreakpoints recreates watchpoint in case of changing type."""
         source = "main.cpp"
diff --git a/lldb/tools/lldb-dap/Breakpoint.cpp b/lldb/tools/lldb-dap/Breakpoint.cpp
index bb77d020cd208..daea2146a3212 100644
--- a/lldb/tools/lldb-dap/Breakpoint.cpp
+++ b/lldb/tools/lldb-dap/Breakpoint.cpp
@@ -44,6 +44,11 @@ GetPersistenceDataForSymbol(lldb::SBSymbol &symbol) {
 void Breakpoint::SetCondition() { m_bp.SetCondition(m_condition.c_str()); }
 
 void Breakpoint::SetHitCondition() {
+  if (m_hit_condition.empty()) {
+    m_bp.SetIgnoreCount(0);
+    return;
+  }
+
   uint64_t hitCount = 0;
   if (llvm::to_integer(m_hit_condition, hitCount))
     m_bp.SetIgnoreCount(hitCount - 1);
diff --git a/lldb/tools/lldb-dap/Breakpoint.h b/lldb/tools/lldb-dap/Breakpoint.h
index c4f1fa291f181..dd563b2a4b1a0 100644
--- a/lldb/tools/lldb-dap/Breakpoint.h
+++ b/lldb/tools/lldb-dap/Breakpoint.h
@@ -18,7 +18,7 @@ namespace lldb_dap {
 class Breakpoint : public BreakpointBase {
 public:
   Breakpoint(DAP &d, const std::optional<std::string> &condition,
-             const std::optional<std::string> &hit_condition)
+             const std::string &hit_condition)
       : BreakpointBase(d, condition, hit_condition) {}
   Breakpoint(DAP &d, lldb::SBBreakpoint bp) : BreakpointBase(d), m_bp(bp) {}
 
diff --git a/lldb/tools/lldb-dap/BreakpointBase.cpp b/lldb/tools/lldb-dap/BreakpointBase.cpp
index 1a4da92e44d31..dc1f08b442e43 100644
--- a/lldb/tools/lldb-dap/BreakpointBase.cpp
+++ b/lldb/tools/lldb-dap/BreakpointBase.cpp
@@ -12,9 +12,9 @@ using namespace lldb_dap;
 
 BreakpointBase::BreakpointBase(DAP &d,
                                const std::optional<std::string> &condition,
-                               const std::optional<std::string> &hit_condition)
+                               const std::string &hit_condition)
     : m_dap(d), m_condition(condition.value_or("")),
-      m_hit_condition(hit_condition.value_or("")) {}
+      m_hit_condition(hit_condition) {}
 
 void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) {
   if (m_condition != request_bp.m_condition) {
diff --git a/lldb/tools/lldb-dap/BreakpointBase.h b/lldb/tools/lldb-dap/BreakpointBase.h
index e9cfc112675c3..bba0659d24711 100644
--- a/lldb/tools/lldb-dap/BreakpointBase.h
+++ b/lldb/tools/lldb-dap/BreakpointBase.h
@@ -20,7 +20,7 @@ class BreakpointBase {
 public:
   explicit BreakpointBase(DAP &d) : m_dap(d) {}
   BreakpointBase(DAP &d, const std::optional<std::string> &condition,
-                 const std::optional<std::string> &hit_condition);
+                 const std::string &hit_condition);
   virtual ~BreakpointBase() = default;
 
   virtual void SetCondition() = 0;
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index b30fe191373ce..fed25d1feacfe 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -869,8 +869,8 @@ llvm::json::Value toJSON(const SourceBreakpoint &SB) {
     result.insert({"column", *SB.column});
   if (SB.condition)
     result.insert({"condition", *SB.condition});
-  if (SB.hitCondition)
-    result.insert({"hitCondition", *SB.hitCondition});
+  if (!SB.hitCondition.empty())
+    result.insert({"hitCondition", SB.hitCondition});
   if (!SB.logMessage.empty())
     result.insert({"logMessage", SB.logMessage});
   if (SB.mode)
@@ -892,8 +892,8 @@ llvm::json::Value toJSON(const FunctionBreakpoint &FB) {
 
   if (FB.condition)
     result.insert({"condition", *FB.condition});
-  if (FB.hitCondition)
-    result.insert({"hitCondition", *FB.hitCondition});
+  if (!FB.hitCondition.empty())
+    result.insert({"hitCondition", FB.hitCondition});
 
   return result;
 }
@@ -947,8 +947,8 @@ llvm::json::Value toJSON(const DataBreakpoint &DBI) {
     result.insert({"accessType", *DBI.accessType});
   if (DBI.condition)
     result.insert({"condition", *DBI.condition});
-  if (DBI.hitCondition)
-    result.insert({"hitCondition", *DBI.hitCondition});
+  if (!DBI.hitCondition.empty())
+    result.insert({"hitCondition", DBI.hitCondition});
 
   return result;
 }
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index a243d2438c4f4..4b2f3d5da2c3b 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -680,7 +680,7 @@ struct SourceBreakpoint {
   /// If both this property and `condition` are specified, `hitCondition` should
   /// be evaluated only if the `condition` is met, and the debug adapter should
   /// stop only if both conditions are met.
-  std::optional<String> hitCondition;
+  String hitCondition;
 
   /// If this attribute exists and is non-empty, the debug adapter must not
   /// 'break' (stop)
@@ -712,7 +712,7 @@ struct FunctionBreakpoint {
   /// The debug adapter is expected to interpret the expression as needed.
   /// The attribute is only honored by a debug adapter if the corresponding
   /// capability `supportsHitConditionalBreakpoints` is true.
-  std::optional<String> hitCondition;
+  String hitCondition;
 };
 bool fromJSON(const llvm::json::Value &, FunctionBreakpoint &,
               llvm::json::Path);
@@ -743,7 +743,7 @@ struct DataBreakpoint {
 
   /// An expression that controls how many hits of the breakpoint are ignored.
   /// The debug adapter is expected to interpret the expression as needed.
-  std::optional<String> hitCondition;
+  String hitCondition;
 };
 bool fromJSON(const llvm::json::Value &, DataBreakpoint &, llvm::json::Path);
 llvm::json::Value toJSON(const DataBreakpoint &);
@@ -769,7 +769,7 @@ struct InstructionBreakpoint {
   /// The debug adapter is expected to interpret the expression as needed.
   /// The attribute is only honored by a debug adapter if the corresponding
   /// capability `supportsHitConditionalBreakpoints` is true.
-  std::optional<String> hitCondition;
+  String hitCondition;
 
   /// The mode of this breakpoint. If defined, this must be one of the
   /// `breakpointModes` the debug adapter advertised in its `Capabilities`.
diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp
index 23e1e73cd22a8..d41c23ad79bc2 100644
--- a/lldb/tools/lldb-dap/Watchpoint.cpp
+++ b/lldb/tools/lldb-dap/Watchpoint.cpp
@@ -33,6 +33,11 @@ Watchpoint::Watchpoint(DAP &d, const protocol::DataBreakpoint &breakpoint)
 void Watchpoint::SetCondition() { m_wp.SetCondition(m_condition.c_str()); }
 
 void Watchpoint::SetHitCondition() {
+  if (m_hit_condition.empty()) {
+    m_wp.SetIgnoreCount(0);
+    return;
+  }
+
   uint64_t hitCount = 0;
   if (llvm::to_integer(m_hit_condition, hitCount))
     m_wp.SetIgnoreCount(hitCount - 1);
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index 3acb0f0e5bfe6..497f54b680df9 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -177,6 +177,21 @@ TEST(ProtocolTypesTest, SourceBreakpointOptionalLogMessage) {
       parse<SourceBreakpoint>(R"({"line": 0, "logMessage": null})"), Failed());
 }
 
+TEST(ProtocolTypesTest, SourceBreakpointOptionalHitCondition) {
+  for (StringRef json :
+       {R"({"line": 0})", R"({"line": 0, "hitCondition": ""})"}) {
+    Expected<SourceBreakpoint> source_breakpoint =
+        parse<SourceBreakpoint>(json);
+    ASSERT_THAT_EXPECTED(source_breakpoint, Succeeded());
+    EXPECT_EQ(source_breakpoint->line, 0u);
+    EXPECT_TRUE(source_breakpoint->hitCondition.empty());
+  }
+
+  EXPECT_THAT_EXPECTED(
+      parse<SourceBreakpoint>(R"({"line": 0, "hitCondition": null})"),
+      Failed());
+}
+
 TEST(ProtocolTypesTest, FunctionBreakpoint) {
   FunctionBreakpoint function_breakpoint;
   function_breakpoint.name = "myFunction";
@@ -194,6 +209,21 @@ TEST(ProtocolTypesTest, FunctionBreakpoint) {
             deserialized_function_breakpoint->hitCondition);
 }
 
+TEST(ProtocolTypesTest, FunctionBreakpointOptionalHitCondition) {
+  for (StringRef json :
+       {R"({"name": "main"})", R"({"name": "main", "hitCondition": ""})"}) {
+    Expected<FunctionBreakpoint> function_breakpoint =
+        parse<FunctionBreakpoint>(json);
+    ASSERT_THAT_EXPECTED(function_breakpoint, Succeeded());
+    EXPECT_EQ(function_breakpoint->name, "main");
+    EXPECT_TRUE(function_breakpoint->hitCondition.empty());
+  }
+
+  EXPECT_THAT_EXPECTED(
+      parse<FunctionBreakpoint>(R"({"name": "main", "hitCondition": null})"),
+      Failed());
+}
+
 TEST(ProtocolTypesTest, DataBreakpoint) {
   DataBreakpoint data_breakpoint_info;
   data_breakpoint_info.dataId = "variable1";
@@ -215,6 +245,37 @@ TEST(ProtocolTypesTest, DataBreakpoint) {
             deserialized_data_breakpoint_info->hitCondition);
 }
 
+TEST(ProtocolTypesTest, DataBreakpointOptionalHitCondition) {
+  for (StringRef json :
+       {R"({"dataId": "0/4"})", R"({"dataId": "0/4", "hitCondition": ""})"}) {
+    Expected<DataBreakpoint> data_breakpoint = parse<DataBreakpoint>(json);
+    ASSERT_THAT_EXPECTED(data_breakpoint, Succeeded());
+    EXPECT_EQ(data_breakpoint->dataId, "0/4");
+    EXPECT_TRUE(data_breakpoint->hitCondition.empty());
+  }
+
+  EXPECT_THAT_EXPECTED(
+      parse<DataBreakpoint>(R"({"dataId": "0/4", "hitCondition": null})"),
+      Failed());
+}
+
+TEST(ProtocolTypesTest, InstructionBreakpointOptionalHitCondition) {
+  for (StringRef json :
+       {R"({"instructionReference": "0x1000"})",
+        R"({"instructionReference": "0x1000", "hitCondition": ""})"}) {
+    Expected<InstructionBreakpoint> instruction_breakpoint =
+        parse<InstructionBreakpoint>(json);
+    ASSERT_THAT_EXPECTED(instruction_breakpoint, Succeeded());
+    EXPECT_EQ(instruction_breakpoint->instructionReference, "0x1000");
+    EXPECT_TRUE(instruction_breakpoint->hitCondition.empty());
+  }
+
+  EXPECT_THAT_EXPECTED(
+      parse<InstructionBreakpoint>(
+          R"({"instructionReference": "0x1000", "hitCondition": null})"),
+      Failed());
+}
+
 TEST(ProtocolTypesTest, Capabilities) {
   Capabilities capabilities;
 



More information about the lldb-commits mailing list