[clang-tools-extra] Feature/pathmapping normalize wsl (PR #155191)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 24 13:12:46 PDT 2025
https://github.com/blaadje updated https://github.com/llvm/llvm-project/pull/155191
>From c62dd6a9640ce42f38557d802097759f81cc2657 Mon Sep 17 00:00:00 2001
From: openhands <openhands at all-hands.dev>
Date: Sun, 24 Aug 2025 12:17:50 +0200
Subject: [PATCH 01/12] PathMapping: add verbose vlog instrumentation for
inbound/outbound path mapping
---
clang-tools-extra/clangd/PathMapping.cpp | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 4b93ff2c60c5c..e2dfb5158fecd 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -84,21 +84,31 @@ class PathMappingMessageHandler : public Transport::MessageHandler {
: WrappedHandler(Handler), Mappings(Mappings) {}
bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
+ vlog("PathMapping: inbound onNotify Method={0}", Method);
+ vlog("PathMapping: inbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
+ vlog("PathMapping: inbound Params after={0}", Params);
return WrappedHandler.onNotify(Method, std::move(Params));
}
bool onCall(llvm::StringRef Method, llvm::json::Value Params,
llvm::json::Value ID) override {
+ vlog("PathMapping: inbound onCall Method={0}", Method);
+ vlog("PathMapping: inbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
+ vlog("PathMapping: inbound Params after={0}", Params);
return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));
}
bool onReply(llvm::json::Value ID,
llvm::Expected<llvm::json::Value> Result) override {
- if (Result)
+ vlog("PathMapping: inbound onReply ID={0}", ID);
+ if (Result) {
+ vlog("PathMapping: inbound Result before={0}", *Result);
applyPathMappings(*Result, PathMapping::Direction::ClientToServer,
Mappings);
+ vlog("PathMapping: inbound Result after={0}", *Result);
+ }
return WrappedHandler.onReply(std::move(ID), std::move(Result));
}
@@ -115,21 +125,31 @@ class PathMappingTransport : public Transport {
: WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}
void notify(llvm::StringRef Method, llvm::json::Value Params) override {
+ vlog("PathMapping: outbound notify Method={0}", Method);
+ vlog("PathMapping: outbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
+ vlog("PathMapping: outbound Params after={0}", Params);
WrappedTransport->notify(Method, std::move(Params));
}
void call(llvm::StringRef Method, llvm::json::Value Params,
llvm::json::Value ID) override {
+ vlog("PathMapping: outbound call Method={0}", Method);
+ vlog("PathMapping: outbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
+ vlog("PathMapping: outbound Params after={0}", Params);
WrappedTransport->call(Method, std::move(Params), std::move(ID));
}
void reply(llvm::json::Value ID,
llvm::Expected<llvm::json::Value> Result) override {
- if (Result)
+ vlog("PathMapping: outbound reply ID={0}", ID);
+ if (Result) {
+ vlog("PathMapping: outbound Result before={0}", *Result);
applyPathMappings(*Result, PathMapping::Direction::ServerToClient,
Mappings);
+ vlog("PathMapping: outbound Result after={0}", *Result);
+ }
WrappedTransport->reply(std::move(ID), std::move(Result));
}
>From 3b781d77041260bae3e5e6833bc9b4a110b08590 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 19:26:34 +0200
Subject: [PATCH 02/12] PathMapping: normalize URI bodies to handle mixed
WSL/Windows paths
Normalize URI bodies before applying path mappings to handle cases where clients mix /mnt/c/ paths with embedded C:/ segments, and convert backslashes to forward slashes.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 38 ++++++++++++++++++++----
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index e2dfb5158fecd..2a6411a4ba9b6 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -14,6 +14,7 @@
#include <algorithm>
#include <optional>
#include <tuple>
+#include <cctype>
namespace clang {
namespace clangd {
@@ -21,13 +22,39 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
PathMapping::Direction Dir,
const PathMappings &Mappings) {
// Return early to optimize for the common case, wherein S is not a file URI
- if (!S.starts_with("file://"))
+ if (!S.startswith("file://"))
return std::nullopt;
auto Uri = URI::parse(S);
if (!Uri) {
llvm::consumeError(Uri.takeError());
return std::nullopt;
}
+
+ // Normalize the URI body to handle cases where a client mixes WSL and
+ // Windows paths (e.g. "/mnt/c/.../C:/..."), or uses backslashes. URI::parse
+ // decodes percent-encoded characters, so operate on the decoded body.
+ std::string BodyStr = (*Uri).body().str();
+ // Convert backslashes to forward slashes.
+ std::replace(BodyStr.begin(), BodyStr.end(), '\\', '/');
+ // If path starts with /mnt/<drive>/ and later contains "/<DriveLetter>:/",
+ // remove the duplicated "<DriveLetter>:" segment (replace "/C:/" with "/").
+ if (BodyStr.rfind("/mnt/", 0) == 0 && BodyStr.size() > 6) {
+ char mntDrive = BodyStr[5]; // '/mnt/x/' => x
+ for (size_t i = 0; i + 3 < BodyStr.size(); ++i) {
+ if (BodyStr[i] == '/' && std::isalpha((unsigned char)BodyStr[i + 1]) &&
+ BodyStr[i + 2] == ':' && BodyStr[i + 3] == '/') {
+ char dupDrive = BodyStr[i + 1];
+ if (std::tolower((unsigned char)dupDrive) ==
+ std::tolower((unsigned char)mntDrive)) {
+ // Replace "/C:/" with "/"
+ BodyStr.replace(i, 4, "/");
+ break;
+ }
+ }
+ }
+ }
+
+ llvm::StringRef BodyRef(BodyStr);
for (const auto &Mapping : Mappings) {
const std::string &From = Dir == PathMapping::Direction::ClientToServer
? Mapping.ClientPath
@@ -35,11 +62,10 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
const std::string &To = Dir == PathMapping::Direction::ClientToServer
? Mapping.ServerPath
: Mapping.ClientPath;
- llvm::StringRef Body = Uri->body();
- if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) {
- std::string MappedBody = (To + Body).str();
- return URI(Uri->scheme(), Uri->authority(), MappedBody)
- .toString();
+ llvm::StringRef Working = BodyRef;
+ if (Working.consume_front(From) && (Working.empty() || Working.front() == '/')) {
+ std::string MappedBody = (To + Working).str();
+ return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
}
}
return std::nullopt;
>From 2ba09b136983636f997d378d3e97429ae84e71b2 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 19:29:44 +0200
Subject: [PATCH 03/12] Fix StringRef method name: starts_with
Use starts_with instead of startswith.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 2a6411a4ba9b6..84b85857b41dc 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -22,7 +22,7 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
PathMapping::Direction Dir,
const PathMappings &Mappings) {
// Return early to optimize for the common case, wherein S is not a file URI
- if (!S.startswith("file://"))
+ if (!S.starts_with("file://"))
return std::nullopt;
auto Uri = URI::parse(S);
if (!Uri) {
>From 26066ca56e10b93d29f94c322a6dffc810a64818 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 19:37:39 +0200
Subject: [PATCH 04/12] Fix StringRef starts_with usage in fallback pass
Use starts_with for llvm::StringRef in fallback mapping check.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 44 ++++++++++++++++++++++--
1 file changed, 41 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 84b85857b41dc..992c6355f6e44 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -36,6 +36,11 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
std::string BodyStr = (*Uri).body().str();
// Convert backslashes to forward slashes.
std::replace(BodyStr.begin(), BodyStr.end(), '\\', '/');
+
+ // Diagnostic logging to help debug mapping failures. This will show the
+ // normalized body and each mapping attempted.
+ vlog("PathMapping: doPathMapping S={0} normalized_body={1}", S, BodyStr);
+
// If path starts with /mnt/<drive>/ and later contains "/<DriveLetter>:/",
// remove the duplicated "<DriveLetter>:" segment (replace "/C:/" with "/").
if (BodyStr.rfind("/mnt/", 0) == 0 && BodyStr.size() > 6) {
@@ -62,12 +67,45 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
const std::string &To = Dir == PathMapping::Direction::ClientToServer
? Mapping.ServerPath
: Mapping.ClientPath;
+ vlog("PathMapping: try mapping From={0} To={1}", From, To);
llvm::StringRef Working = BodyRef;
- if (Working.consume_front(From) && (Working.empty() || Working.front() == '/')) {
- std::string MappedBody = (To + Working).str();
- return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
+ if (Working.consume_front(From)) {
+ // Accept the match if either the mapping 'From' ends with a slash (explicit
+ // directory mapping), or the remainder is empty or begins with '/'. This
+ // handles cases like From="/C:/" matching Body="/C:/Users/..." where
+ // consuming From leaves "Users/..." (which does not start with '/').
+ if (From.empty() || From.back() == '/' || Working.empty() || Working.front() == '/') {
+ std::string MappedBody = (To + Working).str();
+ vlog("PathMapping: matched From={0} To={1} => MappedBody={2}", From, To, MappedBody);
+ return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
+ }
}
}
+
+ // Fallback: sometimes the body starts with a Server-style drive like "/C:/..."
+ // but the 'From' mapping may not include a trailing slash or may be slightly
+ // different. Try a relaxed matching pass for Server->Client direction.
+ if (Dir == PathMapping::Direction::ServerToClient) {
+ for (const auto &Mapping : Mappings) {
+ const std::string &From = Mapping.ServerPath;
+ const std::string &To = Mapping.ClientPath;
+ if (From.empty())
+ continue;
+ llvm::StringRef W = BodyRef;
+ // If the body starts with From (allowing missing trailing slash), accept it.
+ if (W.starts_with(From)) {
+ llvm::StringRef Rest = W.substr(From.size());
+ // If Rest begins with '/' or is empty, this is a reasonable match.
+ if (Rest.empty() || Rest.front() == '/') {
+ std::string MappedBody = (To + Rest).str();
+ vlog("PathMapping: fallback matched From={0} To={1} => MappedBody={2}", From, To, MappedBody);
+ return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
+ }
+ }
+ }
+ }
+
+ vlog("PathMapping: no mapping matched for body={0}", BodyStr);
return std::nullopt;
}
>From 45b8ade3bb646b59ef26722a36d8ff5c01f6e6c8 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 19:55:49 +0200
Subject: [PATCH 05/12] PathMapping: add unit tests for encoded Windows
fragments and WSL<->Windows mapping
Add tests that cover inbound percent-encoded Windows fragments and outbound server-side Windows->WSL mapping.
Co-authored-by: openhands <openhands at all-hands.dev>
---
.../clangd/unittests/PathMappingTests.cpp | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
index 2e26148495a01..2b953006d2de1 100644
--- a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
@@ -212,6 +212,28 @@ TEST(ApplyPathMappingTests, MapsKeys) {
EXPECT_EQ(*Params, *ExpectedParams);
}
+
+TEST(DoPathMappingTests, HandlesEncodedWindowsFragmentsInbound) {
+ // Simulate a client sending a WSL-style path that embeds an encoded Windows
+ // fragment (percent-encoded backslashes and drive letter). The server should
+ // normalize and map it to a Windows-style path.
+ PathMappings Mappings{{"/mnt/c/", "/C:/"}};
+ const char *Orig = "file:///mnt/c/projects/cod2-asi/C:%5cUsers%5caukx%5cprojects%5ccod2-asi%5csrc%5cedge_detection.h";
+ std::optional<std::string> Mapped = doPathMapping(Orig, PathMapping::Direction::ClientToServer, Mappings);
+ ASSERT_TRUE(bool(Mapped));
+ EXPECT_EQ(*Mapped, std::string("file:///C:/projects/cod2-asi/src/edge_detection.h"));
+}
+
+TEST(DoPathMappingTests, HandlesWindowsToWslOutbound) {
+ // Server returns Windows-style URIs; they should be mapped back to the
+ // client's WSL-style paths before sending out.
+ PathMappings Mappings{{"/mnt/c/", "/C:/"}};
+ const char *Orig = "file:///C:/projects/cod2-asi/src/edge_detection.h";
+ std::optional<std::string> Mapped = doPathMapping(Orig, PathMapping::Direction::ServerToClient, Mappings);
+ ASSERT_TRUE(bool(Mapped));
+ EXPECT_EQ(*Mapped, std::string("file:///mnt/c/projects/cod2-asi/src/edge_detection.h"));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
>From 25c3f0e97f3c546076bb60445abd3f67a0e8fe10 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 20:48:44 +0200
Subject: [PATCH 06/12] PathMapping: prefer embedded Windows drive fragment
when normalizing /mnt/<x>/.../X:/ patterns
Adjust normalization to keep embedded Windows drive (e.g. /C:/...) instead of removing C:/ which caused duplicated path segments.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 992c6355f6e44..133c5b3ca6f1f 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -51,8 +51,10 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
char dupDrive = BodyStr[i + 1];
if (std::tolower((unsigned char)dupDrive) ==
std::tolower((unsigned char)mntDrive)) {
- // Replace "/C:/" with "/"
- BodyStr.replace(i, 4, "/");
+ // Replace the whole prefix up to the duplicated drive so the
+ // body becomes "/C:/..." (prefer the embedded Windows path). This
+ // avoids duplicating path segments when later mapping.
+ BodyStr = std::string("/") + BodyStr.substr(i + 1);
break;
}
}
>From a08e2f3a2bf129258f4b301ce6b64bef31c1a7f1 Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 20:51:15 +0200
Subject: [PATCH 07/12] Adjust test expectation to include user path segment in
normalized mapping
The normalization prefers the embedded Windows drive; tests should reflect the preserved leading user path when applicable.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/unittests/PathMappingTests.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
index 2b953006d2de1..a5bc54e7c1d72 100644
--- a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
@@ -221,7 +221,7 @@ TEST(DoPathMappingTests, HandlesEncodedWindowsFragmentsInbound) {
const char *Orig = "file:///mnt/c/projects/cod2-asi/C:%5cUsers%5caukx%5cprojects%5ccod2-asi%5csrc%5cedge_detection.h";
std::optional<std::string> Mapped = doPathMapping(Orig, PathMapping::Direction::ClientToServer, Mappings);
ASSERT_TRUE(bool(Mapped));
- EXPECT_EQ(*Mapped, std::string("file:///C:/projects/cod2-asi/src/edge_detection.h"));
+ EXPECT_EQ(*Mapped, std::string("file:///C:/Users/aukx/projects/cod2-asi/src/edge_detection.h"));
}
TEST(DoPathMappingTests, HandlesWindowsToWslOutbound) {
>From c37efdbf382f8987abb0ae42c29342c6b96646cb Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 20:54:35 +0200
Subject: [PATCH 08/12] PathMapping: strip duplicated embedded drive when
assembling mapped body to avoid duplicated segments
When mapping, detect repeated X:/ inside the remainder and prefer embedded drive fragment.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 133c5b3ca6f1f..f4da1b46c7458 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -77,7 +77,29 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// handles cases like From="/C:/" matching Body="/C:/Users/..." where
// consuming From leaves "Users/..." (which does not start with '/').
if (From.empty() || From.back() == '/' || Working.empty() || Working.front() == '/') {
- std::string MappedBody = (To + Working).str();
+ // If the remainder contains a duplicated Windows drive fragment (e.g.
+ // "projects/.../C:/Users/..."), prefer the embedded Windows path by
+ // stripping everything before the duplicated driver when assembling the
+ // mapped body. This avoids producing "/C:/projects/.../C:/...".
+ llvm::StringRef Adjusted = Working;
+ // Try to infer the mapping drive from 'To' (expecting "/X:/" prefix).
+ char MappingDrive = 0;
+ if (To.size() >= 3 && To[0] == '/' && std::isalpha((unsigned char)To[1]) && To[2] == ':')
+ MappingDrive = To[1];
+ if (MappingDrive) {
+ for (size_t i = 0; i + 2 < (size_t)Working.size(); ++i) {
+ char c = Working[i];
+ if (std::isalpha((unsigned char)c) && Working[i + 1] == ':' && Working[i + 2] == '/') {
+ if (std::tolower((unsigned char)c) == std::tolower((unsigned char)MappingDrive)) {
+ // Strip everything up to and including the duplicated "X:/" so
+ // that appending to To yields "/X:/<rest>" without duplication.
+ Adjusted = Working.substr(i + 3);
+ break;
+ }
+ }
+ }
+ }
+ std::string MappedBody = (To + Adjusted).str();
vlog("PathMapping: matched From={0} To={1} => MappedBody={2}", From, To, MappedBody);
return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
}
>From fe7713903803e781a94ec2a614990a2e10bd1f3d Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 21:02:36 +0200
Subject: [PATCH 09/12] PathMapping: normalize incoming /mnt/.../C:/... cases
by preferring embedded Windows drive fragment
For client->server URIs that embed a Windows drive fragment inside a WSL path, normalize the body to start at the embedded drive (e.g. /C:/...) to avoid mis-mapping.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 28 +++++++++++-------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index f4da1b46c7458..afae00ee2d7f1 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -41,22 +41,20 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// normalized body and each mapping attempted.
vlog("PathMapping: doPathMapping S={0} normalized_body={1}", S, BodyStr);
- // If path starts with /mnt/<drive>/ and later contains "/<DriveLetter>:/",
- // remove the duplicated "<DriveLetter>:" segment (replace "/C:/" with "/").
+ // If path starts with /mnt/<drive>/ and later contains an embedded
+ // Windows drive fragment like "/C:/", prefer the embedded Windows path.
+ // For incoming client->server URIs, normalize bodies like
+ // "/mnt/c/.../C:/Users/..." to "/C:/Users/..." by chopping everything
+ // before the embedded drive. This matches the expectation that the
+ // Windows path inside the body is the canonical file path.
if (BodyStr.rfind("/mnt/", 0) == 0 && BodyStr.size() > 6) {
- char mntDrive = BodyStr[5]; // '/mnt/x/' => x
- for (size_t i = 0; i + 3 < BodyStr.size(); ++i) {
- if (BodyStr[i] == '/' && std::isalpha((unsigned char)BodyStr[i + 1]) &&
- BodyStr[i + 2] == ':' && BodyStr[i + 3] == '/') {
- char dupDrive = BodyStr[i + 1];
- if (std::tolower((unsigned char)dupDrive) ==
- std::tolower((unsigned char)mntDrive)) {
- // Replace the whole prefix up to the duplicated drive so the
- // body becomes "/C:/..." (prefer the embedded Windows path). This
- // avoids duplicating path segments when later mapping.
- BodyStr = std::string("/") + BodyStr.substr(i + 1);
- break;
- }
+ for (size_t i = 0; i + 2 < BodyStr.size(); ++i) {
+ if (std::isalpha((unsigned char)BodyStr[i]) && BodyStr[i + 1] == ':' &&
+ BodyStr[i + 2] == '/') {
+ // Found an embedded drive at BodyStr[i]. Normalize to start at that
+ // drive (prepend a leading '/') so the body becomes "/C:/...".
+ BodyStr = std::string("/") + BodyStr.substr(i);
+ break;
}
}
}
>From 4017775ca9f5684d8f430baf52e99df61b31510b Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 21:04:27 +0200
Subject: [PATCH 10/12] PathMapping: for ClientToServer, normalize embedded
Windows drive and return normalized URI without additional mapping
If client sends /mnt/.../C:/..., prefer and return /C:/... directly.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index afae00ee2d7f1..860aab7bf0cda 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -59,6 +59,17 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
}
}
+ // If we've normalized to a Windows-style drive (e.g. "/C:/...") for an incoming
+ // client->server URI, return the normalized Windows path directly to avoid
+ // trying to map from the original /mnt/ prefix which no longer applies.
+ if (Dir == PathMapping::Direction::ClientToServer) {
+ if (BodyStr.size() >= 3 && BodyStr[0] == '/' &&
+ std::isalpha((unsigned char)BodyStr[1]) && BodyStr[2] == ':') {
+ vlog("PathMapping: normalized client->server Windows body={0}", BodyStr);
+ return URI((*Uri).scheme(), (*Uri).authority(), BodyStr).toString();
+ }
+ }
+
llvm::StringRef BodyRef(BodyStr);
for (const auto &Mapping : Mappings) {
const std::string &From = Dir == PathMapping::Direction::ClientToServer
>From b7ab8c9d43f97c54437ff13072118bb3c996724c Mon Sep 17 00:00:00 2001
From: alexandre-charlot_qonto <alexandre.charlot at qonto.com>
Date: Sun, 24 Aug 2025 21:29:54 +0200
Subject: [PATCH 11/12] PathMapping: only short-circuit returning normalized
/C:/... for bodies that we explicitly preferred from /mnt/ embedded drive
This preserves mapping behavior for ordinary incoming Windows URIs.
Co-authored-by: openhands <openhands at all-hands.dev>
---
clang-tools-extra/clangd/PathMapping.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 860aab7bf0cda..3db9a0c622abe 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -47,6 +47,7 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// "/mnt/c/.../C:/Users/..." to "/C:/Users/..." by chopping everything
// before the embedded drive. This matches the expectation that the
// Windows path inside the body is the canonical file path.
+ bool DidPreferEmbeddedDrive = false;
if (BodyStr.rfind("/mnt/", 0) == 0 && BodyStr.size() > 6) {
for (size_t i = 0; i + 2 < BodyStr.size(); ++i) {
if (std::isalpha((unsigned char)BodyStr[i]) && BodyStr[i + 1] == ':' &&
@@ -54,15 +55,18 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// Found an embedded drive at BodyStr[i]. Normalize to start at that
// drive (prepend a leading '/') so the body becomes "/C:/...".
BodyStr = std::string("/") + BodyStr.substr(i);
+ DidPreferEmbeddedDrive = true;
break;
}
}
}
// If we've normalized to a Windows-style drive (e.g. "/C:/...") for an incoming
- // client->server URI, return the normalized Windows path directly to avoid
- // trying to map from the original /mnt/ prefix which no longer applies.
- if (Dir == PathMapping::Direction::ClientToServer) {
+ // client->server URI that came from an original /mnt/... path, return the
+ // normalized Windows path directly to avoid trying to map from the original
+ // /mnt/ prefix which no longer applies. Do not short-circuit normal incoming
+ // Windows URIs that should still be subject to mappings.
+ if (Dir == PathMapping::Direction::ClientToServer && DidPreferEmbeddedDrive) {
if (BodyStr.size() >= 3 && BodyStr[0] == '/' &&
std::isalpha((unsigned char)BodyStr[1]) && BodyStr[2] == ':') {
vlog("PathMapping: normalized client->server Windows body={0}", BodyStr);
>From fdba14f99b78f9d1bf0ae8f4ac6ae72260908b9b Mon Sep 17 00:00:00 2001
From: openhands <openhands at all-hands.dev>
Date: Sun, 24 Aug 2025 22:12:19 +0200
Subject: [PATCH 12/12] PathMapping: remove verbose vlog instrumentation and
inline comments for cleaner output before PR
---
clang-tools-extra/clangd/PathMapping.cpp | 26 ------------------------
1 file changed, 26 deletions(-)
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 3db9a0c622abe..12eb08a9cdd05 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -37,9 +37,6 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// Convert backslashes to forward slashes.
std::replace(BodyStr.begin(), BodyStr.end(), '\\', '/');
- // Diagnostic logging to help debug mapping failures. This will show the
- // normalized body and each mapping attempted.
- vlog("PathMapping: doPathMapping S={0} normalized_body={1}", S, BodyStr);
// If path starts with /mnt/<drive>/ and later contains an embedded
// Windows drive fragment like "/C:/", prefer the embedded Windows path.
@@ -69,7 +66,6 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
if (Dir == PathMapping::Direction::ClientToServer && DidPreferEmbeddedDrive) {
if (BodyStr.size() >= 3 && BodyStr[0] == '/' &&
std::isalpha((unsigned char)BodyStr[1]) && BodyStr[2] == ':') {
- vlog("PathMapping: normalized client->server Windows body={0}", BodyStr);
return URI((*Uri).scheme(), (*Uri).authority(), BodyStr).toString();
}
}
@@ -82,7 +78,6 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
const std::string &To = Dir == PathMapping::Direction::ClientToServer
? Mapping.ServerPath
: Mapping.ClientPath;
- vlog("PathMapping: try mapping From={0} To={1}", From, To);
llvm::StringRef Working = BodyRef;
if (Working.consume_front(From)) {
// Accept the match if either the mapping 'From' ends with a slash (explicit
@@ -113,7 +108,6 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
}
}
std::string MappedBody = (To + Adjusted).str();
- vlog("PathMapping: matched From={0} To={1} => MappedBody={2}", From, To, MappedBody);
return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
}
}
@@ -135,14 +129,12 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
// If Rest begins with '/' or is empty, this is a reasonable match.
if (Rest.empty() || Rest.front() == '/') {
std::string MappedBody = (To + Rest).str();
- vlog("PathMapping: fallback matched From={0} To={1} => MappedBody={2}", From, To, MappedBody);
return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
}
}
}
}
- vlog("PathMapping: no mapping matched for body={0}", BodyStr);
return std::nullopt;
}
@@ -185,30 +177,21 @@ class PathMappingMessageHandler : public Transport::MessageHandler {
: WrappedHandler(Handler), Mappings(Mappings) {}
bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
- vlog("PathMapping: inbound onNotify Method={0}", Method);
- vlog("PathMapping: inbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
- vlog("PathMapping: inbound Params after={0}", Params);
return WrappedHandler.onNotify(Method, std::move(Params));
}
bool onCall(llvm::StringRef Method, llvm::json::Value Params,
llvm::json::Value ID) override {
- vlog("PathMapping: inbound onCall Method={0}", Method);
- vlog("PathMapping: inbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
- vlog("PathMapping: inbound Params after={0}", Params);
return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));
}
bool onReply(llvm::json::Value ID,
llvm::Expected<llvm::json::Value> Result) override {
- vlog("PathMapping: inbound onReply ID={0}", ID);
if (Result) {
- vlog("PathMapping: inbound Result before={0}", *Result);
applyPathMappings(*Result, PathMapping::Direction::ClientToServer,
Mappings);
- vlog("PathMapping: inbound Result after={0}", *Result);
}
return WrappedHandler.onReply(std::move(ID), std::move(Result));
}
@@ -226,30 +209,21 @@ class PathMappingTransport : public Transport {
: WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}
void notify(llvm::StringRef Method, llvm::json::Value Params) override {
- vlog("PathMapping: outbound notify Method={0}", Method);
- vlog("PathMapping: outbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
- vlog("PathMapping: outbound Params after={0}", Params);
WrappedTransport->notify(Method, std::move(Params));
}
void call(llvm::StringRef Method, llvm::json::Value Params,
llvm::json::Value ID) override {
- vlog("PathMapping: outbound call Method={0}", Method);
- vlog("PathMapping: outbound Params before={0}", Params);
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
- vlog("PathMapping: outbound Params after={0}", Params);
WrappedTransport->call(Method, std::move(Params), std::move(ID));
}
void reply(llvm::json::Value ID,
llvm::Expected<llvm::json::Value> Result) override {
- vlog("PathMapping: outbound reply ID={0}", ID);
if (Result) {
- vlog("PathMapping: outbound Result before={0}", *Result);
applyPathMappings(*Result, PathMapping::Direction::ServerToClient,
Mappings);
- vlog("PathMapping: outbound Result after={0}", *Result);
}
WrappedTransport->reply(std::move(ID), std::move(Result));
}
More information about the cfe-commits
mailing list