Skip to content

Commit 0b7e2f6

Browse files
committed
[SpecialCaseList] Add backward compatible dot-slash handling
This commit introduces backward compatible dot-slash handling for SpecialCaseList. The new behavior is controlled by the `Version` field in the special case list file. - Version 1 and 2: Path is matches as-is, regardless of presense of "./". - Version 4 and higher: Paths with leading dot-slash are canonicalized to paths without dot-slash before matching. This means that a rule like `src=./foo` will never match, and `src=foo` will match both `foo` and `./foo`. - Version 3: Transitionary version. Paths is matched both, without canonicalization, and with canonicalized. If non-canonicalized path matches, but canonicalized does not, a warning is emitted. This change allows for a gradual transition to the new behavior, while maintaining backward compatibility with existing special case list files. Pull Request: llvm#162511
1 parent 6d3fee6 commit 0b7e2f6

File tree

2 files changed

+82
-17
lines changed

2 files changed

+82
-17
lines changed

llvm/lib/Support/SpecialCaseList.cpp

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
#include "llvm/Support/MemoryBuffer.h"
2727
#include "llvm/Support/Regex.h"
2828
#include "llvm/Support/VirtualFileSystem.h"
29-
#include "llvm/Support/raw_ostream.h"
29+
#include "llvm/Support/WithColor.h"
30+
#include <cassert>
3031
#include <memory>
32+
#include <mutex>
3133
#include <stdio.h>
3234
#include <string>
3335
#include <system_error>
@@ -44,6 +46,7 @@ class RegexMatcher {
4446
public:
4547
Error insert(StringRef Pattern, unsigned LineNumber);
4648
unsigned match(StringRef Query) const;
49+
StringRef findRule(unsigned LineNo) const;
4750

4851
private:
4952
struct Reg {
@@ -61,6 +64,7 @@ class GlobMatcher {
6164
public:
6265
Error insert(StringRef Pattern, unsigned LineNumber);
6366
unsigned match(StringRef Query) const;
67+
StringRef findRule(unsigned LineNo) const;
6468

6569
private:
6670
struct Glob {
@@ -90,15 +94,25 @@ class GlobMatcher {
9094
/// Represents a set of patterns and their line numbers
9195
class Matcher {
9296
public:
93-
Matcher(bool UseGlobs, bool RemoveDotSlash);
97+
enum class QueryOptions {
98+
kUnchanged,
99+
kNoSlashDot,
100+
kNoSlashDotWithFallback,
101+
};
102+
103+
Matcher(bool UseGlobs, QueryOptions QOpts = QueryOptions::kUnchanged);
94104

95105
Error insert(StringRef Pattern, unsigned LineNumber);
96106
unsigned match(StringRef Query) const;
97107

98108
bool matchAny(StringRef Query) const { return match(Query); }
99109

110+
private:
111+
unsigned matchInternal(StringRef Query) const;
112+
StringRef findRule(unsigned LineNo) const;
113+
100114
std::variant<RegexMatcher, GlobMatcher> M;
101-
bool RemoveDotSlash;
115+
QueryOptions Options;
102116
};
103117

104118
Error RegexMatcher::insert(StringRef Pattern, unsigned LineNumber) {
@@ -132,6 +146,14 @@ unsigned RegexMatcher::match(StringRef Query) const {
132146
return 0;
133147
}
134148

149+
StringRef RegexMatcher::findRule(unsigned LineNo) const {
150+
for (const auto &R : RegExes)
151+
if (R.LineNo == LineNo)
152+
return R.Name;
153+
assert(!"`findRule` should be called only with correct `LineNo`");
154+
return {};
155+
}
156+
135157
Error GlobMatcher::insert(StringRef Pattern, unsigned LineNumber) {
136158
if (Pattern.empty())
137159
return createStringError(errc::invalid_argument, "Supplied glob was blank");
@@ -218,8 +240,15 @@ unsigned GlobMatcher::match(StringRef Query) const {
218240
return Best < 0 ? 0 : Globs[Best].LineNo;
219241
}
220242

221-
Matcher::Matcher(bool UseGlobs, bool RemoveDotSlash)
222-
: RemoveDotSlash(RemoveDotSlash) {
243+
StringRef GlobMatcher::findRule(unsigned LineNo) const {
244+
for (const auto &G : Globs)
245+
if (G.LineNo == LineNo)
246+
return G.Name;
247+
assert(!"`findRule` should be called only with correct `LineNo`");
248+
return {};
249+
}
250+
251+
Matcher::Matcher(bool UseGlobs, QueryOptions QOpts) : Options(QOpts) {
223252
if (UseGlobs)
224253
M.emplace<GlobMatcher>();
225254
else
@@ -231,10 +260,42 @@ Error Matcher::insert(StringRef Pattern, unsigned LineNumber) {
231260
}
232261

233262
unsigned Matcher::match(StringRef Query) const {
234-
if (RemoveDotSlash)
235-
Query = llvm::sys::path::remove_leading_dotslash(Query);
263+
switch (Options) {
264+
case QueryOptions::kUnchanged:
265+
return matchInternal(Query);
266+
case QueryOptions::kNoSlashDot:
267+
return matchInternal(llvm::sys::path::remove_leading_dotslash(Query));
268+
case QueryOptions::kNoSlashDotWithFallback:
269+
break;
270+
}
271+
272+
StringRef FixedQuery = llvm::sys::path::remove_leading_dotslash(Query);
273+
unsigned FixedMatched = matchInternal(FixedQuery);
274+
if (FixedQuery == Query)
275+
return FixedMatched;
276+
277+
unsigned OriginalMatch = matchInternal(Query);
278+
if (OriginalMatch > FixedMatched) {
279+
static std::once_flag Warned;
280+
std::call_once(Warned, [&]() {
281+
WithColor::warning() << "Deprecated behaviour: '"
282+
<< findRule(OriginalMatch) << "' matches '" << Query
283+
<< "', but it should match '" << FixedQuery
284+
<< "'.\n";
285+
});
286+
}
287+
return std::max(OriginalMatch, FixedMatched);
288+
}
289+
290+
unsigned Matcher::matchInternal(StringRef Query) const {
236291
return std::visit([&](auto &V) -> unsigned { return V.match(Query); }, M);
237292
}
293+
294+
StringRef Matcher::findRule(unsigned LineNo) const {
295+
return std::visit([&](auto &V) -> StringRef { return V.findRule(LineNo); },
296+
M);
297+
}
298+
238299
} // namespace
239300

240301
class SpecialCaseList::Section::SectionImpl {
@@ -244,8 +305,7 @@ class SpecialCaseList::Section::SectionImpl {
244305
public:
245306
using SectionEntries = StringMap<StringMap<Matcher>>;
246307

247-
SectionImpl(StringRef Str, bool UseGlobs)
248-
: SectionMatcher(UseGlobs, /*RemoveDotSlash=*/false) {}
308+
SectionImpl(StringRef Str, bool UseGlobs) : SectionMatcher(UseGlobs) {}
249309

250310
Matcher SectionMatcher;
251311
SectionEntries Entries;
@@ -350,8 +410,6 @@ bool SpecialCaseList::parse(unsigned FileIdx, const MemoryBuffer *MB,
350410
// https://discourse.llvm.org/t/use-glob-instead-of-regex-for-specialcaselists/71666
351411
bool UseGlobs = Version > 1;
352412

353-
bool RemoveDotSlash = Version > 2;
354-
355413
auto ErrOrSection = addSection("*", FileIdx, 1, true);
356414
if (auto Err = ErrOrSection.takeError()) {
357415
Error = toString(std::move(Err));
@@ -397,10 +455,17 @@ bool SpecialCaseList::parse(unsigned FileIdx, const MemoryBuffer *MB,
397455
return false;
398456
}
399457

458+
Matcher::QueryOptions QOpts = Matcher::QueryOptions::kUnchanged;
459+
if (llvm::is_contained(PathPrefixes, Prefix)) {
460+
if (Version == 3)
461+
QOpts = Matcher::QueryOptions::kNoSlashDotWithFallback;
462+
else if (Version > 4)
463+
QOpts = Matcher::QueryOptions::kNoSlashDot;
464+
}
465+
400466
auto [Pattern, Category] = Postfix.split("=");
401-
auto [It, _] = CurrentImpl->Entries[Prefix].try_emplace(
402-
Category, UseGlobs,
403-
RemoveDotSlash && llvm::is_contained(PathPrefixes, Prefix));
467+
auto [It, _] =
468+
CurrentImpl->Entries[Prefix].try_emplace(Category, UseGlobs, QOpts);
404469
Pattern = Pattern.copy(StrAlloc);
405470
if (auto Err = It->second.insert(Pattern, LineNo)) {
406471
Error =

llvm/unittests/Support/SpecialCaseListTest.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ TEST_F(SpecialCaseListTest, DotSlash) {
329329
EXPECT_FALSE(SCL4->inSection("dot", "fun", "foo"));
330330

331331
EXPECT_TRUE(SCL2->inSection("dot", "src", "./bar"));
332-
EXPECT_FALSE(SCL3->inSection("dot", "src", "./bar"));
333-
EXPECT_FALSE(SCL4->inSection("dot", "src", "./bar"));
332+
EXPECT_TRUE(SCL3->inSection("dot", "src", "./bar"));
333+
EXPECT_TRUE(SCL4->inSection("dot", "src", "./bar"));
334334

335335
EXPECT_FALSE(SCL2->inSection("dot", "src", "bar"));
336336
EXPECT_FALSE(SCL3->inSection("dot", "src", "bar"));
@@ -346,7 +346,7 @@ TEST_F(SpecialCaseListTest, DotSlash) {
346346

347347
EXPECT_FALSE(SCL2->inSection("not", "src", "./bar"));
348348
EXPECT_TRUE(SCL3->inSection("not", "src", "./bar"));
349-
EXPECT_TRUE(SCL4->inSection("not", "src", "./bar"));
349+
EXPECT_FALSE(SCL4->inSection("not", "src", "./bar"));
350350

351351
EXPECT_TRUE(SCL2->inSection("not", "src", "bar"));
352352
EXPECT_TRUE(SCL3->inSection("not", "src", "bar"));

0 commit comments

Comments
 (0)