-
Notifications
You must be signed in to change notification settings - Fork 14.8k
clangd(PathMapping): normalize mixed WSL/Windows URIs #155191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
c25724d
to
14bd578
Compare
14bd578
to
f527c9c
Compare
@llvm/pr-subscribers-clangd Author: Alexandre (blaadje) ChangesDescriptionThis change adds proper normalization of URIs between WSL and Windows paths in PathMapping. What’s been done:
Resources :
Full diff: https://github.com/llvm/llvm-project/pull/155191.diff 2 Files Affected:
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
|
@llvm/pr-subscribers-clang-tools-extra Author: Alexandre (blaadje) ChangesDescriptionThis change adds proper normalization of URIs between WSL and Windows paths in PathMapping. What’s been done:
Resources :
Full diff: https://github.com/llvm/llvm-project/pull/155191.diff 2 Files Affected:
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
|
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:/
withclangd.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
:Resources :