[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