Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmDebuggerBreakpointManager.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmDebuggerBreakpointManager.h"
4
5
#include <algorithm>
6
#include <cstddef>
7
#include <cstdint>
8
#include <utility>
9
10
#include <cm3p/cppdap/optional.h>
11
#include <cm3p/cppdap/session.h>
12
#include <cm3p/cppdap/types.h>
13
14
#include "cmDebuggerSourceBreakpoint.h"
15
#include "cmListFileCache.h"
16
#include "cmSystemTools.h"
17
18
namespace cmDebugger {
19
20
cmDebuggerBreakpointManager::cmDebuggerBreakpointManager(
21
  dap::Session* dapSession)
22
0
  : DapSession(dapSession)
23
0
{
24
  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints
25
0
  DapSession->registerHandler([&](dap::SetBreakpointsRequest const& request) {
26
0
    return HandleSetBreakpointsRequest(request);
27
0
  });
28
0
}
29
30
int64_t cmDebuggerBreakpointManager::FindFunctionStartLine(
31
  std::string const& sourcePath, int64_t line)
32
0
{
33
0
  auto location =
34
0
    find_if(ListFileFunctionLines[sourcePath].begin(),
35
0
            ListFileFunctionLines[sourcePath].end(),
36
0
            [=](cmDebuggerFunctionLocation loc) {
37
0
              return loc.StartLine <= line && loc.EndLine >= line;
38
0
            });
39
40
0
  if (location != ListFileFunctionLines[sourcePath].end()) {
41
0
    return location->StartLine;
42
0
  }
43
44
0
  return 0;
45
0
}
46
47
int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine(
48
  std::string const& sourcePath, int64_t line)
49
0
{
50
0
  auto location = find_if(
51
0
    ListFileFunctionLines[sourcePath].begin(),
52
0
    ListFileFunctionLines[sourcePath].end(),
53
0
    [=](cmDebuggerFunctionLocation loc) { return loc.StartLine >= line; });
54
55
0
  if (location != ListFileFunctionLines[sourcePath].end()) {
56
0
    return location->StartLine;
57
0
  }
58
59
0
  if (!ListFileFunctionLines[sourcePath].empty() &&
60
0
      ListFileFunctionLines[sourcePath].back().EndLine <= line) {
61
    // return last function start line for any breakpoints after.
62
0
    return ListFileFunctionLines[sourcePath].back().StartLine;
63
0
  }
64
65
0
  return 0;
66
0
}
67
68
dap::SetBreakpointsResponse
69
cmDebuggerBreakpointManager::HandleSetBreakpointsRequest(
70
  dap::SetBreakpointsRequest const& request)
71
0
{
72
0
  std::unique_lock<std::mutex> lock(Mutex);
73
74
0
  dap::SetBreakpointsResponse response;
75
76
0
  auto sourcePath =
77
0
    cmSystemTools::GetActualCaseForPath(request.source.path.value());
78
0
  dap::array<dap::SourceBreakpoint> const defaultValue{};
79
0
  auto const& breakpoints = request.breakpoints.value(defaultValue);
80
81
0
  if (Breakpoints.find(sourcePath) != Breakpoints.end()) {
82
0
    Breakpoints[sourcePath].clear();
83
0
  }
84
0
  response.breakpoints.resize(breakpoints.size());
85
86
0
  if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
87
    // The file has loaded, we can validate breakpoints.
88
0
    for (size_t i = 0; i < breakpoints.size(); i++) {
89
0
      int64_t correctedLine =
90
0
        CalibrateBreakpointLine(sourcePath, breakpoints[i].line);
91
0
      if (correctedLine > 0) {
92
0
        Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
93
0
                                             correctedLine);
94
0
        response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
95
0
        response.breakpoints[i].line =
96
0
          Breakpoints[sourcePath].back().GetLine();
97
0
        response.breakpoints[i].verified = true;
98
0
      } else {
99
0
        response.breakpoints[i].verified = false;
100
0
        response.breakpoints[i].line = breakpoints[i].line;
101
0
      }
102
0
      dap::Source dapSrc;
103
0
      dapSrc.path = sourcePath;
104
0
      response.breakpoints[i].source = dapSrc;
105
0
    }
106
0
  } else {
107
    // The file has not loaded, validate breakpoints later.
108
0
    ListFilePendingValidations.emplace(sourcePath);
109
110
0
    for (size_t i = 0; i < breakpoints.size(); i++) {
111
0
      Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
112
0
                                           breakpoints[i].line);
113
0
      response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
114
0
      response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine();
115
0
      response.breakpoints[i].verified = false;
116
0
      dap::Source dapSrc;
117
0
      dapSrc.path = sourcePath;
118
0
      response.breakpoints[i].source = dapSrc;
119
0
    }
120
0
  }
121
122
0
  return response;
123
0
}
124
125
void cmDebuggerBreakpointManager::SourceFileLoaded(
126
  std::string const& sourcePath,
127
  std::vector<cmListFileFunction> const& functions)
128
0
{
129
0
  std::unique_lock<std::mutex> lock(Mutex);
130
0
  if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
131
    // this is not expected.
132
0
    return;
133
0
  }
134
135
0
  for (cmListFileFunction const& func : functions) {
136
0
    ListFileFunctionLines[sourcePath].emplace_back(
137
0
      cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() });
138
0
  }
139
140
0
  if (ListFilePendingValidations.find(sourcePath) ==
141
0
      ListFilePendingValidations.end()) {
142
0
    return;
143
0
  }
144
145
0
  ListFilePendingValidations.erase(sourcePath);
146
147
0
  for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) {
148
0
    dap::BreakpointEvent breakpointEvent;
149
0
    breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId();
150
0
    breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine();
151
0
    auto source = dap::Source();
152
0
    source.path = sourcePath;
153
0
    breakpointEvent.breakpoint.source = source;
154
0
    int64_t correctedLine = CalibrateBreakpointLine(
155
0
      sourcePath, Breakpoints[sourcePath][i].GetLine());
156
0
    if (correctedLine != Breakpoints[sourcePath][i].GetLine()) {
157
0
      Breakpoints[sourcePath][i].ChangeLine(correctedLine);
158
0
    }
159
0
    breakpointEvent.reason = "changed";
160
0
    breakpointEvent.breakpoint.verified = (correctedLine > 0);
161
0
    if (breakpointEvent.breakpoint.verified) {
162
0
      breakpointEvent.breakpoint.line = correctedLine;
163
0
    } else {
164
0
      Breakpoints[sourcePath][i].Invalid();
165
0
    }
166
167
0
    DapSession->send(breakpointEvent);
168
0
  }
169
0
}
170
171
std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints(
172
  std::string const& sourcePath, int64_t line)
173
0
{
174
0
  std::unique_lock<std::mutex> lock(Mutex);
175
0
  auto const& all = Breakpoints[sourcePath];
176
0
  std::vector<int64_t> breakpoints;
177
0
  if (all.empty()) {
178
0
    return breakpoints;
179
0
  }
180
181
0
  auto it = all.begin();
182
183
0
  while ((it = std::find_if(
184
0
            it, all.end(), [&](cmDebuggerSourceBreakpoint const& breakpoint) {
185
0
              return (breakpoint.GetIsValid() && breakpoint.GetLine() == line);
186
0
            })) != all.end()) {
187
0
    breakpoints.emplace_back(it->GetId());
188
0
    ++it;
189
0
  }
190
191
0
  return breakpoints;
192
0
}
193
194
size_t cmDebuggerBreakpointManager::GetBreakpointCount() const
195
0
{
196
0
  size_t count = 0;
197
0
  for (auto const& pair : Breakpoints) {
198
0
    count += pair.second.size();
199
0
  }
200
0
  return count;
201
0
}
202
203
void cmDebuggerBreakpointManager::ClearAll()
204
0
{
205
0
  std::unique_lock<std::mutex> lock(Mutex);
206
0
  Breakpoints.clear();
207
0
}
208
209
} // namespace cmDebugger