[clang-tools-extra] clangd(PathMapping): normalize mixed WSL/Windows URIs (PR #155191)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 24 14:54:22 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra
Author: Alexandre (blaadje)
<details>
<summary>Changes</summary>
## Description
This change adds proper normalization of URIs between WSL and Windows paths in **PathMapping**.
It fixes the issue where using `--path-mappings=/mnt/c/=C:/` with `clangd.exe` from **WSL** produced hybrid paths mixing WSL and Windows fragments.
## What’s been done:
- In `clang-tools-extra/clangd/PathMapping.cpp` :
- Normalize URIs containing encoded Windows fragments (`C:%5c...`).
- Convert `/mnt/<drive>/...` into `/X:/...` when a drive letter is detected.
- Adjust mapping logic to prevent hybrid paths.
- In `clang-tools-extra/clangd/unittests/PathMappingTests.cpp` :
- Added test for WSL → Windows conversion (ClientToServer).
- Added test for Windows → WSL conversion (ServerToClient).
## Resources :
- Fixes issue: https://github.com/clangd/clangd/issues/2462
---
Full diff: https://github.com/llvm/llvm-project/pull/155191.diff
2 Files Affected:
- (modified) clang-tools-extra/clangd/PathMapping.cpp (+70-5)
- (modified) clang-tools-extra/clangd/unittests/PathMappingTests.cpp (+22)
``````````diff
diff --git a/clang-tools-extra/clangd/PathMapping.cpp b/clang-tools-extra/clangd/PathMapping.cpp
index 4b93ff2c60c5c..68cdf0e3f037e 100644
--- a/clang-tools-extra/clangd/PathMapping.cpp
+++ b/clang-tools-extra/clangd/PathMapping.cpp
@@ -12,6 +12,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/Path.h"
#include <algorithm>
+#include <cctype>
#include <optional>
#include <tuple>
@@ -28,6 +29,30 @@ std::optional<std::string> doPathMapping(llvm::StringRef S,
llvm::consumeError(Uri.takeError());
return std::nullopt;
}
+
+ std::string BodyStr = (*Uri).body().str();
+ std::replace(BodyStr.begin(), BodyStr.end(), '\\', '/');
+
+ 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] == ':' &&
+ BodyStr[i + 2] == '/') {
+ BodyStr = std::string("/") + BodyStr.substr(i);
+ DidPreferEmbeddedDrive = true;
+ break;
+ }
+ }
+ }
+
+ if (Dir == PathMapping::Direction::ClientToServer && DidPreferEmbeddedDrive) {
+ if (BodyStr.size() >= 3 && BodyStr[0] == '/' &&
+ std::isalpha((unsigned char)BodyStr[1]) && BodyStr[2] == ':') {
+ 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
? Mapping.ClientPath
@@ -35,13 +60,53 @@ 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)) {
+ if (From.empty() || From.back() == '/' || Working.empty() ||
+ Working.front() == '/') {
+ llvm::StringRef Adjusted = Working;
+
+ 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)) {
+ Adjusted = Working.substr(i + 3);
+ break;
+ }
+ }
+ }
+ }
+ std::string MappedBody = (To + Adjusted).str();
+ return URI((*Uri).scheme(), (*Uri).authority(), MappedBody).toString();
+ }
+ }
+ }
+
+ 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 (W.starts_with(From)) {
+ llvm::StringRef Rest = W.substr(From.size());
+ if (Rest.empty() || Rest.front() == '/') {
+ std::string MappedBody = (To + Rest).str();
+ return URI((*Uri).scheme(), (*Uri).authority(), MappedBody)
+ .toString();
+ }
+ }
}
}
+
return std::nullopt;
}
diff --git a/clang-tools-extra/clangd/unittests/PathMappingTests.cpp b/clang-tools-extra/clangd/unittests/PathMappingTests.cpp
index 2e26148495a01..a5bc54e7c1d72 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:/Users/aukx/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
``````````
</details>
https://github.com/llvm/llvm-project/pull/155191
More information about the cfe-commits
mailing list