/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 |