diff --git a/clang/docs/SanitizerSpecialCaseList.rst b/clang/docs/SanitizerSpecialCaseList.rst index 307c001664fba..f5c45c1b81df0 100644 --- a/clang/docs/SanitizerSpecialCaseList.rst +++ b/clang/docs/SanitizerSpecialCaseList.rst @@ -196,6 +196,23 @@ tool-specific docs. [{cfi-vcall,cfi-icall}] fun:*BadCfiCall + +.. note:: + + By default, ``src`` and ``mainfile`` are matched against the filename as seen + by LLVM. On Windows, this might involve a mix of forward and backslashes as + file separators, and writing patterns to match both variants can be + inconvenient. + + If the special case list file begins with ``#!canonical-paths``, then paths + will be canonicalized before patterns are matched against them. This involves + stripping any leading dots and slashes, and (on Windows only) converting all + backslashes to forward slashes. + + If the file uses both ``#!special-case-list-v1`` and ``#!canonical-paths``, + then they should occupy the first two lines, and ``#!canonical-paths`` must + appear on the second line. + ``mainfile`` is similar to applying ``-fno-sanitize=`` to a set of files but does not need plumbing into the build system. This works well for internal linkage functions but has a caveat for C++ vague linkage functions. diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp index dc3778bbf339c..71762d10aefa6 100644 --- a/clang/lib/Basic/Diagnostic.cpp +++ b/clang/lib/Basic/Diagnostic.cpp @@ -612,10 +612,18 @@ bool WarningsSpecialCaseList::isDiagSuppressed(diag::kind DiagId, SrcEntriesIt->getValue(); // We also use presumed locations here to improve reproducibility for // preprocessed inputs. - if (PresumedLoc PLoc = SM.getPresumedLoc(DiagLoc); PLoc.isValid()) - return globsMatches( - CategoriesToMatchers, - llvm::sys::path::remove_leading_dotslash(PLoc.getFilename())); + if (PresumedLoc PLoc = SM.getPresumedLoc(DiagLoc); PLoc.isValid()) { + if (CanonicalizePaths) { + return globsMatches( + CategoriesToMatchers, + llvm::sys::path::convert_to_slash( + llvm::sys::path::remove_leading_dotslash(PLoc.getFilename()))); + } else { + return globsMatches( + CategoriesToMatchers, + llvm::sys::path::remove_leading_dotslash(PLoc.getFilename())); + } + } return false; } diff --git a/clang/unittests/Basic/DiagnosticTest.cpp b/clang/unittests/Basic/DiagnosticTest.cpp index 4b3af00c3b0ce..2e052b9e5eaf3 100644 --- a/clang/unittests/Basic/DiagnosticTest.cpp +++ b/clang/unittests/Basic/DiagnosticTest.cpp @@ -360,4 +360,38 @@ TEST_F(SuppressionMappingTest, ParsingRespectsOtherWarningOpts) { clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS); EXPECT_THAT(diags(), IsEmpty()); } + +#ifdef _WIN32 +TEST_F(SuppressionMappingTest, CanonicalizesSlashesOnWindows) { + llvm::StringLiteral SuppressionMappingFile = R"(#!canonical-paths + [unused] + src:*clang/* + src:*clang/lib/Sema/*=emit + src:*clang/lib\\Sema/foo* + fun:suppress/me)"; + Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt"; + FS->addFile("foo.txt", /*ModificationTime=*/{}, + llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile)); + clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS); + EXPECT_THAT(diags(), IsEmpty()); + + EXPECT_TRUE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang/lib/Basic/bar.h)"))); + EXPECT_TRUE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang/lib/Basic\bar.h)"))); + EXPECT_TRUE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang\lib/Basic/bar.h)"))); + EXPECT_FALSE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang/lib/Sema/baz.h)"))); + EXPECT_FALSE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang/lib/Sema\baz.h)"))); + + // The backslash gets canonicalized so we never match the third pattern + EXPECT_FALSE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang\lib\Sema/foo.h)"))); + EXPECT_FALSE(Diags.isSuppressedViaMapping( + diag::warn_unused_function, locForFile(R"(clang/lib/Sema/foo.h)"))); +} +#endif + } // namespace diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md index 85c16b9c33f10..5a7f41ef3f0dd 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -174,6 +174,11 @@ Changes to BOLT Changes to Sanitizers --------------------- +* (Sanitizer Special Case Lists)[https://clang.llvm.org/docs/SanitizerSpecialCaseList.html] + may now be prefixed with ``#!canonical-paths`` to specify that filename patterns + should be matched against canonicalized paths, without leading dots or slashes + and (on Windows only) without any backslashes. + Other Changes ------------- diff --git a/llvm/include/llvm/Support/SpecialCaseList.h b/llvm/include/llvm/Support/SpecialCaseList.h index 22a62eac9e01a..5c5df23a8623d 100644 --- a/llvm/include/llvm/Support/SpecialCaseList.h +++ b/llvm/include/llvm/Support/SpecialCaseList.h @@ -122,7 +122,7 @@ class SpecialCaseList { class Matcher { public: LLVM_ABI Error insert(StringRef Pattern, unsigned LineNumber, - bool UseRegex); + bool UseGlobs); // Returns the line number in the source file that this query matches to. // Returns zero if no match is found. LLVM_ABI unsigned match(StringRef Query) const; @@ -154,6 +154,7 @@ class SpecialCaseList { }; std::vector
Sections; + bool CanonicalizePaths = false; LLVM_ABI Expected
addSection(StringRef SectionStr, unsigned FileIdx, unsigned LineNo, diff --git a/llvm/lib/Support/SpecialCaseList.cpp b/llvm/lib/Support/SpecialCaseList.cpp index 8d4e043bc1c9f..4de7478aaf53b 100644 --- a/llvm/lib/Support/SpecialCaseList.cpp +++ b/llvm/lib/Support/SpecialCaseList.cpp @@ -153,12 +153,17 @@ bool SpecialCaseList::parse(unsigned FileIdx, const MemoryBuffer *MB, return false; } + // Scan the start of the file for special comments. These don't appear when + // iterating below because comment lines are automatically skipped. + StringRef Buffer = MB->getBuffer(); // In https://reviews.llvm.org/D154014 we added glob support and planned to // remove regex support in patterns. We temporarily support the original - // behavior using regexes if "#!special-case-list-v1" is the first line of the - // file. For more details, see + // behavior using regexes if "#!special-case-list-v1" is the first line of + // the file. For more details, see // https://discourse.llvm.org/t/use-glob-instead-of-regex-for-specialcaselists/71666 - bool UseGlobs = !MB->getBuffer().starts_with("#!special-case-list-v1\n"); + bool UseGlobs = !Buffer.consume_front("#!special-case-list-v1\n"); + // Specifies that patterns should be matched against canonicalized filepaths. + CanonicalizePaths = Buffer.consume_front("#!canonical-paths\n"); for (line_iterator LineIt(*MB, /*SkipBlanks=*/true, /*CommentMarker=*/'#'); !LineIt.is_at_eof(); LineIt++) { @@ -237,7 +242,12 @@ unsigned SpecialCaseList::inSectionBlame(const SectionEntries &Entries, if (II == I->second.end()) return 0; - return II->getValue().match(Query); + if (CanonicalizePaths && (Prefix == "src" || Prefix == "mainfile")) { + return II->getValue().match(llvm::sys::path::convert_to_slash( + llvm::sys::path::remove_leading_dotslash(Query))); + } else { + return II->getValue().match(Query); + } } } // namespace llvm diff --git a/llvm/unittests/Support/SpecialCaseListTest.cpp b/llvm/unittests/Support/SpecialCaseListTest.cpp index 5be2b9e3a7a5d..5fc077f3d94ac 100644 --- a/llvm/unittests/Support/SpecialCaseListTest.cpp +++ b/llvm/unittests/Support/SpecialCaseListTest.cpp @@ -372,4 +372,22 @@ TEST_F(SpecialCaseListTest, FileIdx) { sys::fs::remove(Path); } +#ifdef _WIN32 +TEST_F(SpecialCaseListTest, CanonicalizePathsOnWindows) { + std::unique_ptr SCL = + makeSpecialCaseList("#!canonical-paths\n" + "\n" + "src:*foo/bar*\n" + "src:*foo\\\\baz\n" + "fun:hi\\\\bye=category\n"); + EXPECT_TRUE(SCL->inSection("", "src", "foo/bar")); + EXPECT_TRUE(SCL->inSection("", "src", "foo\\bar")); + // The baz pattern doesn't match because paths are canonicalized first + EXPECT_FALSE(SCL->inSection("", "src", "foo/baz")); + EXPECT_FALSE(SCL->inSection("", "src", "foo\\baz")); + // The canonicalization only applies to files + EXPECT_TRUE(SCL->inSection("", "fun", "hi\\bye", "category")); +} +#endif + } // namespace