Skip to content

Commit d840463

Browse files
kadircettstellar
authored andcommitted
[clangd] Treat paths case-insensitively depending on the platform
Path{Match,Exclude} and MountPoint were checking paths case-sensitively on all platforms, as with other features, this was causing problems on windows. Since users can have capital drive letters on config files, but editors might lower-case them. This patch addresses that issue by: - Creating regexes with case-insensitive matching on those platforms. - Introducing a new pathIsAncestor helper, which performs checks in a case-correct manner where needed. Differential Revision: https://reviews.llvm.org/D96690 (cherry picked from commit ecea721)
1 parent 8eeb3d9 commit d840463

File tree

7 files changed

+124
-20
lines changed

7 files changed

+124
-20
lines changed

clang-tools-extra/clangd/ConfigCompile.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "Features.inc"
3232
#include "TidyProvider.h"
3333
#include "support/Logger.h"
34+
#include "support/Path.h"
3435
#include "support/Trace.h"
3536
#include "llvm/ADT/None.h"
3637
#include "llvm/ADT/Optional.h"
@@ -101,9 +102,11 @@ struct FragmentCompiler {
101102
// Normalized Fragment::SourceInfo::Directory.
102103
std::string FragmentDirectory;
103104

104-
llvm::Optional<llvm::Regex> compileRegex(const Located<std::string> &Text) {
105+
llvm::Optional<llvm::Regex>
106+
compileRegex(const Located<std::string> &Text,
107+
llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags) {
105108
std::string Anchored = "^(" + *Text + ")$";
106-
llvm::Regex Result(Anchored);
109+
llvm::Regex Result(Anchored, Flags);
107110
std::string RegexError;
108111
if (!Result.isValid(RegexError)) {
109112
diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range);
@@ -195,9 +198,15 @@ struct FragmentCompiler {
195198
if (F.HasUnrecognizedCondition)
196199
Out.Conditions.push_back([&](const Params &) { return false; });
197200

201+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
202+
llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
203+
#else
204+
llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
205+
#endif
206+
198207
auto PathMatch = std::make_unique<std::vector<llvm::Regex>>();
199208
for (auto &Entry : F.PathMatch) {
200-
if (auto RE = compileRegex(Entry))
209+
if (auto RE = compileRegex(Entry, Flags))
201210
PathMatch->push_back(std::move(*RE));
202211
}
203212
if (!PathMatch->empty()) {
@@ -218,7 +227,7 @@ struct FragmentCompiler {
218227

219228
auto PathExclude = std::make_unique<std::vector<llvm::Regex>>();
220229
for (auto &Entry : F.PathExclude) {
221-
if (auto RE = compileRegex(Entry))
230+
if (auto RE = compileRegex(Entry, Flags))
222231
PathExclude->push_back(std::move(*RE));
223232
}
224233
if (!PathExclude->empty()) {
@@ -349,7 +358,8 @@ struct FragmentCompiler {
349358
return;
350359
Spec.MountPoint = std::move(*AbsPath);
351360
Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) {
352-
if (!P.Path.startswith(Spec.MountPoint))
361+
if (P.Path.empty() || !pathStartsWith(Spec.MountPoint, P.Path,
362+
llvm::sys::path::Style::posix))
353363
return;
354364
C.Index.External = Spec;
355365
// Disable background indexing for the files under the mountpoint.

clang-tools-extra/clangd/support/Path.cpp

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,33 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "support/Path.h"
10+
#include "llvm/Support/Path.h"
1011
namespace clang {
1112
namespace clangd {
1213

13-
std::string maybeCaseFoldPath(PathRef Path) {
14-
#if defined(_WIN32) || defined(__APPLE__)
15-
return Path.lower();
16-
#else
17-
return std::string(Path);
18-
#endif
19-
}
14+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
15+
std::string maybeCaseFoldPath(PathRef Path) { return Path.lower(); }
16+
bool pathEqual(PathRef A, PathRef B) { return A.equals_lower(B); }
17+
#else // NOT CLANGD_PATH_CASE_INSENSITIVE
18+
std::string maybeCaseFoldPath(PathRef Path) { return Path.str(); }
19+
bool pathEqual(PathRef A, PathRef B) { return A == B; }
20+
#endif // CLANGD_PATH_CASE_INSENSITIVE
2021

21-
bool pathEqual(PathRef A, PathRef B) {
22-
#if defined(_WIN32) || defined(__APPLE__)
23-
return A.equals_lower(B);
24-
#else
25-
return A == B;
26-
#endif
22+
bool pathStartsWith(PathRef Ancestor, PathRef Path,
23+
llvm::sys::path::Style Style) {
24+
assert(llvm::sys::path::is_absolute(Ancestor, Style) &&
25+
llvm::sys::path::is_absolute(Path, Style));
26+
// If ancestor ends with a separator drop that, so that we can match /foo/ as
27+
// a parent of /foo.
28+
if (llvm::sys::path::is_separator(Ancestor.back(), Style))
29+
Ancestor = Ancestor.drop_back();
30+
// Ensure Path starts with Ancestor.
31+
if (!pathEqual(Ancestor, Path.take_front(Ancestor.size())))
32+
return false;
33+
Path = Path.drop_front(Ancestor.size());
34+
// Then make sure either two paths are equal or Path has a separator
35+
// afterwards.
36+
return Path.empty() || llvm::sys::path::is_separator(Path.front(), Style);
2737
}
28-
2938
} // namespace clangd
3039
} // namespace clang

clang-tools-extra/clangd/support/Path.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_PATH_H
1111

1212
#include "llvm/ADT/StringRef.h"
13+
#include "llvm/Support/Path.h"
1314
#include <string>
1415

16+
/// Whether current platform treats paths case insensitively.
17+
#if defined(_WIN32) || defined(__APPLE__)
18+
#define CLANGD_PATH_CASE_INSENSITIVE
19+
#endif
20+
1521
namespace clang {
1622
namespace clangd {
1723

@@ -28,6 +34,12 @@ using PathRef = llvm::StringRef;
2834
std::string maybeCaseFoldPath(PathRef Path);
2935
bool pathEqual(PathRef, PathRef);
3036

37+
/// Checks if \p Ancestor is a proper ancestor of \p Path. This is just a
38+
/// smarter lexical prefix match, e.g: foo/bar/baz doesn't start with foo/./bar.
39+
/// Both \p Ancestor and \p Path must be absolute.
40+
bool pathStartsWith(
41+
PathRef Ancestor, PathRef Path,
42+
llvm::sys::path::Style Style = llvm::sys::path::Style::native);
3143
} // namespace clangd
3244
} // namespace clang
3345

clang-tools-extra/clangd/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ add_unittest(ClangdUnitTests ClangdTests
104104
support/FunctionTests.cpp
105105
support/MarkupTests.cpp
106106
support/MemoryTreeTests.cpp
107+
support/PathTests.cpp
107108
support/ThreadingTests.cpp
108109
support/TestTracer.cpp
109110
support/TraceTests.cpp

clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ TEST_F(ConfigCompileTests, Condition) {
9999
Frag.If.PathMatch.emplace_back("ba*r");
100100
EXPECT_FALSE(compileAndApply());
101101
EXPECT_THAT(Diags.Diagnostics, IsEmpty());
102+
103+
// Only matches case-insensitively.
104+
Frag = {};
105+
Frag.If.PathMatch.emplace_back("B.*R");
106+
EXPECT_THAT(Diags.Diagnostics, IsEmpty());
107+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
108+
EXPECT_TRUE(compileAndApply());
109+
#else
110+
EXPECT_FALSE(compileAndApply());
111+
#endif
112+
113+
Frag = {};
114+
Frag.If.PathExclude.emplace_back("B.*R");
115+
EXPECT_THAT(Diags.Diagnostics, IsEmpty());
116+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
117+
EXPECT_FALSE(compileAndApply());
118+
#else
119+
EXPECT_TRUE(compileAndApply());
120+
#endif
102121
}
103122

104123
TEST_F(ConfigCompileTests, CompileCommands) {
@@ -406,6 +425,23 @@ TEST_F(ConfigCompileTests, ExternalBlockMountPoint) {
406425
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
407426
ASSERT_TRUE(Conf.Index.External);
408427
EXPECT_THAT(Conf.Index.External->MountPoint, FooPath);
428+
429+
// Only matches case-insensitively.
430+
BazPath = testPath("fOo/baz.h", llvm::sys::path::Style::posix);
431+
BazPath = llvm::sys::path::convert_to_slash(BazPath);
432+
Parm.Path = BazPath;
433+
434+
FooPath = testPath("FOO/", llvm::sys::path::Style::posix);
435+
FooPath = llvm::sys::path::convert_to_slash(FooPath);
436+
Frag = GetFrag("", FooPath.c_str());
437+
compileAndApply();
438+
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
439+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
440+
ASSERT_TRUE(Conf.Index.External);
441+
EXPECT_THAT(Conf.Index.External->MountPoint, FooPath);
442+
#else
443+
ASSERT_FALSE(Conf.Index.External);
444+
#endif
409445
}
410446
} // namespace
411447
} // namespace config

clang-tools-extra/clangd/unittests/RenameTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ TEST(RenameTest, IndexMergeMainFile) {
11021102
EXPECT_THAT(Results.GlobalChanges.keys(),
11031103
UnorderedElementsAre(Main, testPath("other.cc")));
11041104

1105-
#if defined(_WIN32) || defined(__APPLE__)
1105+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
11061106
// On case-insensitive systems, no duplicates if AST vs index case differs.
11071107
// https://github.com/clangd/clangd/issues/665
11081108
TU.Filename = "MAIN.CC";
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===-- PathTests.cpp -------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "support/Path.h"
10+
#include "gmock/gmock.h"
11+
#include "gtest/gtest.h"
12+
13+
namespace clang {
14+
namespace clangd {
15+
namespace {
16+
TEST(PathTests, IsAncestor) {
17+
EXPECT_TRUE(pathStartsWith("/foo", "/foo"));
18+
EXPECT_TRUE(pathStartsWith("/foo/", "/foo"));
19+
20+
EXPECT_FALSE(pathStartsWith("/foo", "/fooz"));
21+
EXPECT_FALSE(pathStartsWith("/foo/", "/fooz"));
22+
23+
EXPECT_TRUE(pathStartsWith("/foo", "/foo/bar"));
24+
EXPECT_TRUE(pathStartsWith("/foo/", "/foo/bar"));
25+
26+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
27+
EXPECT_TRUE(pathStartsWith("/fOo", "/foo/bar"));
28+
EXPECT_TRUE(pathStartsWith("/foo", "/fOo/bar"));
29+
#else
30+
EXPECT_FALSE(pathStartsWith("/fOo", "/foo/bar"));
31+
EXPECT_FALSE(pathStartsWith("/foo", "/fOo/bar"));
32+
#endif
33+
}
34+
} // namespace
35+
} // namespace clangd
36+
} // namespace clang

0 commit comments

Comments
 (0)