mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-13 17:07:57 +00:00
The breakpoint manager normalizes incoming setBreakpoints paths with GetActualCaseForPath, but SourceFileLoaded and GetBreakpoints use paths from CollapseFullPath, which preserves whatever case the caller provided. On case-insensitive filesystems these can differ for the same file, so the unordered_map keys do not match and breakpoints are silently missed. On macOS (HFS+) GetActualCaseForPath is a no-op, so the setBreakpoints path passes through unmodified. If the DAP client sends "Test.cmake" but cmake was invoked with "cmake -P test.cmake", the breakpoint is stored under "Test.cmake" while GetBreakpoints looks up "test.cmake". On Windows GetActualCaseForPath *does* normalize, but only in HandleSetBreakpointsRequest. The breakpoint is stored under the canonical "Test.cmake", while SourceFileLoaded and GetBreakpoints still receive "test.cmake" from CollapseFullPath. The keys disagree from the opposite direction. Add a NormalizePath helper that calls ToNormalizedPathOnDisk and apply it in all three public entry points so that every map key goes through the same transformation. ToNormalizedPathOnDisk loads the on-disk capitalization on macOS and Windows while preserving symbolic links in logical paths.
224 lines
7.1 KiB
C++
224 lines
7.1 KiB
C++
/* 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
|