1
0
mirror of https://github.com/skeeto/w64devkit.git synced 2026-10-23 06:08:56 +03:00

cmake: normalize breakpoint paths

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 and apply it in all three public entry
points so that every map key goes through the same transformation.
On non-Windows it uses GetRealPath (wrapping realpath(3), which
returns the actual filesystem case on macOS and resolves symlinks on
Linux).  On Windows the existing GetActualCaseForPath is retained.
This commit is contained in:
Christopher Wellons
2026-04-11 14:17:48 -04:00
parent 8a109cdd71
commit dc1b730fc1

View File

@@ -0,0 +1,132 @@
debugger: normalize breakpoint paths on case-insensitive filesystems
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 and apply it in all three public entry
points so that every map key goes through the same transformation.
On non-Windows it uses GetRealPath (wrapping realpath(3), which
returns the actual filesystem case on macOS and resolves symlinks on
Linux). On Windows the existing GetActualCaseForPath is retained.
--- a/Source/cmDebuggerBreakpointManager.cxx
+++ b/Source/cmDebuggerBreakpointManager.cxx
@@ -17,6 +17,22 @@
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 macOS (case-insensitive HFS+) realpath()
+// returns the actual filesystem case; on Linux it resolves symlinks
+// (case is already significant); on Windows GetActualCaseForPath
+// walks path components with FindFirstFileW.
+static std::string NormalizePath(std::string const& sourcePath)
+{
+#ifdef _WIN32
+ return cmSystemTools::GetActualCaseForPath(sourcePath);
+#else
+ auto real = cmSystemTools::GetRealPath(sourcePath);
+ return real.empty() ? sourcePath : real;
+#endif
+}
+
cmDebuggerBreakpointManager::cmDebuggerBreakpointManager(
dap::Session* dapSession)
: DapSession(dapSession)
@@ -73,8 +89,7 @@
dap::SetBreakpointsResponse response;
- auto sourcePath =
- cmSystemTools::GetActualCaseForPath(request.source.path.value());
+ auto sourcePath = NormalizePath(request.source.path.value());
dap::array<dap::SourceBreakpoint> const defaultValue{};
auto const& breakpoints = request.breakpoints.value(defaultValue);
@@ -126,42 +141,46 @@
std::string const& sourcePath,
std::vector<cmListFileFunction> const& functions)
{
+ auto normalizedPath = NormalizePath(sourcePath);
+
std::unique_lock<std::mutex> lock(Mutex);
- if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
+ if (ListFileFunctionLines.find(normalizedPath) !=
+ ListFileFunctionLines.end()) {
// this is not expected.
return;
}
for (cmListFileFunction const& func : functions) {
- ListFileFunctionLines[sourcePath].emplace_back(
+ ListFileFunctionLines[normalizedPath].emplace_back(
cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() });
}
- if (ListFilePendingValidations.find(sourcePath) ==
+ if (ListFilePendingValidations.find(normalizedPath) ==
ListFilePendingValidations.end()) {
return;
}
- ListFilePendingValidations.erase(sourcePath);
+ ListFilePendingValidations.erase(normalizedPath);
- for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) {
+ for (size_t i = 0; i < Breakpoints[normalizedPath].size(); i++) {
dap::BreakpointEvent breakpointEvent;
- breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId();
- breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine();
+ breakpointEvent.breakpoint.id = Breakpoints[normalizedPath][i].GetId();
+ breakpointEvent.breakpoint.line =
+ Breakpoints[normalizedPath][i].GetLine();
auto source = dap::Source();
- source.path = sourcePath;
+ source.path = normalizedPath;
breakpointEvent.breakpoint.source = source;
int64_t correctedLine = CalibrateBreakpointLine(
- sourcePath, Breakpoints[sourcePath][i].GetLine());
- if (correctedLine != Breakpoints[sourcePath][i].GetLine()) {
- Breakpoints[sourcePath][i].ChangeLine(correctedLine);
+ 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[sourcePath][i].Invalid();
+ Breakpoints[normalizedPath][i].Invalid();
}
DapSession->send(breakpointEvent);
@@ -171,8 +190,10 @@
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[sourcePath];
+ auto const& all = Breakpoints[normalizedPath];
std::vector<int64_t> breakpoints;
if (all.empty()) {
return breakpoints;