Coverage Report

Created: 2026-04-29 07:01

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
// Resolve a source path to its canonical form so that breakpoint map
21
// keys match regardless of the case used by the DAP client or by
22
// CollapseFullPath.  On case-insensitive filesystems (macOS, Windows)
23
// ToNormalizedPathOnDisk loads the on-disk capitalization while
24
// preserving symbolic links in logical paths.
25
static std::string NormalizePath(std::string const& sourcePath)
26
0
{
27
0
  return cmSystemTools::ToNormalizedPathOnDisk(sourcePath);
28
0
}
29
30
cmDebuggerBreakpointManager::cmDebuggerBreakpointManager(
31
  dap::Session* dapSession)
32
0
  : DapSession(dapSession)
33
0
{
34
  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints
35
0
  DapSession->registerHandler([&](dap::SetBreakpointsRequest const& request) {
36
0
    return HandleSetBreakpointsRequest(request);
37
0
  });
38
0
}
39
40
int64_t cmDebuggerBreakpointManager::FindFunctionStartLine(
41
  std::string const& sourcePath, int64_t line)
42
0
{
43
0
  auto location =
44
0
    find_if(ListFileFunctionLines[sourcePath].begin(),
45
0
            ListFileFunctionLines[sourcePath].end(),
46
0
            [=](cmDebuggerFunctionLocation loc) {
47
0
              return loc.StartLine <= line && loc.EndLine >= line;
48
0
            });
49
50
0
  if (location != ListFileFunctionLines[sourcePath].end()) {
51
0
    return location->StartLine;
52
0
  }
53
54
0
  return 0;
55
0
}
56
57
int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine(
58
  std::string const& sourcePath, int64_t line)
59
0
{
60
0
  auto location = find_if(
61
0
    ListFileFunctionLines[sourcePath].begin(),
62
0
    ListFileFunctionLines[sourcePath].end(),
63
0
    [=](cmDebuggerFunctionLocation loc) { return loc.StartLine >= line; });
64
65
0
  if (location != ListFileFunctionLines[sourcePath].end()) {
66
0
    return location->StartLine;
67
0
  }
68
69
0
  if (!ListFileFunctionLines[sourcePath].empty() &&
70
0
      ListFileFunctionLines[sourcePath].back().EndLine <= line) {
71
    // return last function start line for any breakpoints after.
72
0
    return ListFileFunctionLines[sourcePath].back().StartLine;
73
0
  }
74
75
0
  return 0;
76
0
}
77
78
dap::SetBreakpointsResponse
79
cmDebuggerBreakpointManager::HandleSetBreakpointsRequest(
80
  dap::SetBreakpointsRequest const& request)
81
0
{
82
0
  std::unique_lock<std::mutex> lock(Mutex);
83
84
0
  dap::SetBreakpointsResponse response;
85
86
0
  auto sourcePath = NormalizePath(request.source.path.value());
87
0
  dap::array<dap::SourceBreakpoint> const defaultValue{};
88
0
  auto const& breakpoints = request.breakpoints.value(defaultValue);
89
90
0
  if (Breakpoints.find(sourcePath) != Breakpoints.end()) {
91
0
    Breakpoints[sourcePath].clear();
92
0
  }
93
0
  response.breakpoints.resize(breakpoints.size());
94
95
0
  if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
96
    // The file has loaded, we can validate breakpoints.
97
0
    for (size_t i = 0; i < breakpoints.size(); i++) {
98
0
      int64_t correctedLine =
99
0
        CalibrateBreakpointLine(sourcePath, breakpoints[i].line);
100
0
      if (correctedLine > 0) {
101
0
        Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
102
0
                                             correctedLine);
103
0
        response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
104
0
        response.breakpoints[i].line =
105
0
          Breakpoints[sourcePath].back().GetLine();
106
0
        response.breakpoints[i].verified = true;
107
0
      } else {
108
0
        response.breakpoints[i].verified = false;
109
0
        response.breakpoints[i].line = breakpoints[i].line;
110
0
      }
111
0
      dap::Source dapSrc;
112
0
      dapSrc.path = sourcePath;
113
0
      response.breakpoints[i].source = dapSrc;
114
0
    }
115
0
  } else {
116
    // The file has not loaded, validate breakpoints later.
117
0
    ListFilePendingValidations.emplace(sourcePath);
118
119
0
    for (size_t i = 0; i < breakpoints.size(); i++) {
120
0
      Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
121
0
                                           breakpoints[i].line);
122
0
      response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
123
0
      response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine();
124
0
      response.breakpoints[i].verified = false;
125
0
      dap::Source dapSrc;
126
0
      dapSrc.path = sourcePath;
127
0
      response.breakpoints[i].source = dapSrc;
128
0
    }
129
0
  }
130
131
0
  return response;
132
0
}
133
134
void cmDebuggerBreakpointManager::SourceFileLoaded(
135
  std::string const& sourcePath,
136
  std::vector<cmListFileFunction> const& functions)
137
0
{
138
0
  auto normalizedPath = NormalizePath(sourcePath);
139
140
0
  std::unique_lock<std::mutex> lock(Mutex);
141
0
  if (ListFileFunctionLines.find(normalizedPath) !=
142
0
      ListFileFunctionLines.end()) {
143
    // this is not expected.
144
0
    return;
145
0
  }
146
147
0
  for (cmListFileFunction const& func : functions) {
148
0
    ListFileFunctionLines[normalizedPath].emplace_back(
149
0
      cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() });
150
0
  }
151
152
0
  if (ListFilePendingValidations.find(normalizedPath) ==
153
0
      ListFilePendingValidations.end()) {
154
0
    return;
155
0
  }
156
157
0
  ListFilePendingValidations.erase(normalizedPath);
158
159
0
  for (size_t i = 0; i < Breakpoints[normalizedPath].size(); i++) {
160
0
    dap::BreakpointEvent breakpointEvent;
161
0
    breakpointEvent.breakpoint.id = Breakpoints[normalizedPath][i].GetId();
162
0
    breakpointEvent.breakpoint.line = Breakpoints[normalizedPath][i].GetLine();
163
0
    auto source = dap::Source();
164
0
    source.path = normalizedPath;
165
0
    breakpointEvent.breakpoint.source = source;
166
0
    int64_t correctedLine = CalibrateBreakpointLine(
167
0
      normalizedPath, Breakpoints[normalizedPath][i].GetLine());
168
0
    if (correctedLine != Breakpoints[normalizedPath][i].GetLine()) {
169
0
      Breakpoints[normalizedPath][i].ChangeLine(correctedLine);
170
0
    }
171
0
    breakpointEvent.reason = "changed";
172
0
    breakpointEvent.breakpoint.verified = (correctedLine > 0);
173
0
    if (breakpointEvent.breakpoint.verified) {
174
0
      breakpointEvent.breakpoint.line = correctedLine;
175
0
    } else {
176
0
      Breakpoints[normalizedPath][i].Invalid();
177
0
    }
178
179
0
    DapSession->send(breakpointEvent);
180
0
  }
181
0
}
182
183
std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints(
184
  std::string const& sourcePath, int64_t line)
185
0
{
186
0
  auto normalizedPath = NormalizePath(sourcePath);
187
188
0
  std::unique_lock<std::mutex> lock(Mutex);
189
0
  auto const& all = Breakpoints[normalizedPath];
190
0
  std::vector<int64_t> breakpoints;
191
0
  if (all.empty()) {
192
0
    return breakpoints;
193
0
  }
194
195
0
  auto it = all.begin();
196
197
0
  while ((it = std::find_if(
198
0
            it, all.end(), [&](cmDebuggerSourceBreakpoint const& breakpoint) {
199
0
              return (breakpoint.GetIsValid() && breakpoint.GetLine() == line);
200
0
            })) != all.end()) {
201
0
    breakpoints.emplace_back(it->GetId());
202
0
    ++it;
203
0
  }
204
205
0
  return breakpoints;
206
0
}
207
208
size_t cmDebuggerBreakpointManager::GetBreakpointCount() const
209
0
{
210
0
  size_t count = 0;
211
0
  for (auto const& pair : Breakpoints) {
212
0
    count += pair.second.size();
213
0
  }
214
0
  return count;
215
0
}
216
217
void cmDebuggerBreakpointManager::ClearAll()
218
0
{
219
0
  std::unique_lock<std::mutex> lock(Mutex);
220
0
  Breakpoints.clear();
221
0
}
222
223
} // namespace cmDebugger