| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmDebuggerBreakpointManager.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <utility> | |
| #include <cm3p/cppdap/optional.h> | |
| #include <cm3p/cppdap/session.h> | |
| #include <cm3p/cppdap/types.h> | |
| #include "cmDebuggerSourceBreakpoint.h" | |
| #include "cmListFileCache.h" | |
| #include "cmSystemTools.h" | |
| namespace cmDebugger { | |
| // Resolve a source path to its canonical form so that breakpoint map | |
| // keys match regardless of the case used by the DAP client or by | |
| // CollapseFullPath. On case-insensitive filesystems (macOS, Windows) | |
| // ToNormalizedPathOnDisk loads the on-disk capitalization while | |
| // preserving symbolic links in logical paths. | |
| static std::string NormalizePath(std::string const& sourcePath) | |
| { | |
| return cmSystemTools::ToNormalizedPathOnDisk(sourcePath); | |
| } | |
| cmDebuggerBreakpointManager::cmDebuggerBreakpointManager( | |
| dap::Session* dapSession) | |
| : DapSession(dapSession) | |
| { | |
| // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints | |
| DapSession->registerHandler([&](dap::SetBreakpointsRequest const& request) { | |
| return HandleSetBreakpointsRequest(request); | |
| }); | |
| } | |
| int64_t cmDebuggerBreakpointManager::FindFunctionStartLine( | |
| std::string const& sourcePath, int64_t line) | |
| { | |
| auto location = | |
| find_if(ListFileFunctionLines[sourcePath].begin(), | |
| ListFileFunctionLines[sourcePath].end(), | |
| [=](cmDebuggerFunctionLocation loc) { | |
| return loc.StartLine <= line && loc.EndLine >= line; | |
| }); | |
| if (location != ListFileFunctionLines[sourcePath].end()) { | |
| return location->StartLine; | |
| } | |
| return 0; | |
| } | |
| int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine( | |
| std::string const& sourcePath, int64_t line) | |
| { | |
| auto location = find_if( | |
| ListFileFunctionLines[sourcePath].begin(), | |
| ListFileFunctionLines[sourcePath].end(), | |
| [=](cmDebuggerFunctionLocation loc) { return loc.StartLine >= line; }); | |
| if (location != ListFileFunctionLines[sourcePath].end()) { | |
| return location->StartLine; | |
| } | |
| if (!ListFileFunctionLines[sourcePath].empty() && | |
| ListFileFunctionLines[sourcePath].back().EndLine <= line) { | |
| // return last function start line for any breakpoints after. | |
| return ListFileFunctionLines[sourcePath].back().StartLine; | |
| } | |
| return 0; | |
| } | |
| dap::SetBreakpointsResponse | |
| cmDebuggerBreakpointManager::HandleSetBreakpointsRequest( | |
| dap::SetBreakpointsRequest const& request) | |
| { | |
| std::unique_lock<std::mutex> lock(Mutex); | |
| dap::SetBreakpointsResponse response; | |
| auto sourcePath = NormalizePath(request.source.path.value()); | |
| dap::array<dap::SourceBreakpoint> const defaultValue{}; | |
| auto const& breakpoints = request.breakpoints.value(defaultValue); | |
| if (Breakpoints.find(sourcePath) != Breakpoints.end()) { | |
| Breakpoints[sourcePath].clear(); | |
| } | |
| response.breakpoints.resize(breakpoints.size()); | |
| if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) { | |
| // The file has loaded, we can validate breakpoints. | |
| for (size_t i = 0; i < breakpoints.size(); i++) { | |
| int64_t correctedLine = | |
| CalibrateBreakpointLine(sourcePath, breakpoints[i].line); | |
| if (correctedLine > 0) { | |
| Breakpoints[sourcePath].emplace_back(NextBreakpointId++, | |
| correctedLine); | |
| response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); | |
| response.breakpoints[i].line = | |
| Breakpoints[sourcePath].back().GetLine(); | |
| response.breakpoints[i].verified = true; | |
| } else { | |
| response.breakpoints[i].verified = false; | |
| response.breakpoints[i].line = breakpoints[i].line; | |
| } | |
| dap::Source dapSrc; | |
| dapSrc.path = sourcePath; | |
| response.breakpoints[i].source = dapSrc; | |
| } | |
| } else { | |
| // The file has not loaded, validate breakpoints later. | |
| ListFilePendingValidations.emplace(sourcePath); | |
| for (size_t i = 0; i < breakpoints.size(); i++) { | |
| Breakpoints[sourcePath].emplace_back(NextBreakpointId++, | |
| breakpoints[i].line); | |
| response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); | |
| response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine(); | |
| response.breakpoints[i].verified = false; | |
| dap::Source dapSrc; | |
| dapSrc.path = sourcePath; | |
| response.breakpoints[i].source = dapSrc; | |
| } | |
| } | |
| return response; | |
| } | |
| void cmDebuggerBreakpointManager::SourceFileLoaded( | |
| std::string const& sourcePath, | |
| std::vector<cmListFileFunction> const& functions) | |
| { | |
| auto normalizedPath = NormalizePath(sourcePath); | |
| std::unique_lock<std::mutex> lock(Mutex); | |
| if (ListFileFunctionLines.find(normalizedPath) != | |
| ListFileFunctionLines.end()) { | |
| // this is not expected. | |
| return; | |
| } | |
| for (cmListFileFunction const& func : functions) { | |
| ListFileFunctionLines[normalizedPath].emplace_back( | |
| cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() }); | |
| } | |
| if (ListFilePendingValidations.find(normalizedPath) == | |
| ListFilePendingValidations.end()) { | |
| return; | |
| } | |
| ListFilePendingValidations.erase(normalizedPath); | |
| for (size_t i = 0; i < Breakpoints[normalizedPath].size(); i++) { | |
| dap::BreakpointEvent breakpointEvent; | |
| breakpointEvent.breakpoint.id = Breakpoints[normalizedPath][i].GetId(); | |
| breakpointEvent.breakpoint.line = Breakpoints[normalizedPath][i].GetLine(); | |
| auto source = dap::Source(); | |
| source.path = normalizedPath; | |
| breakpointEvent.breakpoint.source = source; | |
| int64_t correctedLine = CalibrateBreakpointLine( | |
| normalizedPath, Breakpoints[normalizedPath][i].GetLine()); | |
| if (correctedLine != Breakpoints[normalizedPath][i].GetLine()) { | |
| Breakpoints[normalizedPath][i].ChangeLine(correctedLine); | |
| } | |
| breakpointEvent.reason = "changed"; | |
| breakpointEvent.breakpoint.verified = (correctedLine > 0); | |
| if (breakpointEvent.breakpoint.verified) { | |
| breakpointEvent.breakpoint.line = correctedLine; | |
| } else { | |
| Breakpoints[normalizedPath][i].Invalid(); | |
| } | |
| DapSession->send(breakpointEvent); | |
| } | |
| } | |
| std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints( | |
| std::string const& sourcePath, int64_t line) | |
| { | |
| auto normalizedPath = NormalizePath(sourcePath); | |
| std::unique_lock<std::mutex> lock(Mutex); | |
| auto const& all = Breakpoints[normalizedPath]; | |
| std::vector<int64_t> breakpoints; | |
| if (all.empty()) { | |
| return breakpoints; | |
| } | |
| auto it = all.begin(); | |
| while ((it = std::find_if( | |
| it, all.end(), [&](cmDebuggerSourceBreakpoint const& breakpoint) { | |
| return (breakpoint.GetIsValid() && breakpoint.GetLine() == line); | |
| })) != all.end()) { | |
| breakpoints.emplace_back(it->GetId()); | |
| ++it; | |
| } | |
| return breakpoints; | |
| } | |
| size_t cmDebuggerBreakpointManager::GetBreakpointCount() const | |
| { | |
| size_t count = 0; | |
| for (auto const& pair : Breakpoints) { | |
| count += pair.second.size(); | |
| } | |
| return count; | |
| } | |
| void cmDebuggerBreakpointManager::ClearAll() | |
| { | |
| std::unique_lock<std::mutex> lock(Mutex); | |
| Breakpoints.clear(); | |
| } | |
| } // namespace cmDebugger |