1 //===-- SpecialCaseList.cpp - special case list for sanitizers ------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This is a utility class for instrumentation passes (like AddressSanitizer
10 // or ThreadSanitizer) to avoid instrumenting some functions or global
11 // variables, or to instrument some functions or global variables in a specific
12 // way, based on a user-supplied list.
14 //===----------------------------------------------------------------------===//
16 #include "llvm/Support/SpecialCaseList.h"
17 #include "llvm/Support/LineIterator.h"
18 #include "llvm/Support/MemoryBuffer.h"
19 #include "llvm/Support/VirtualFileSystem.h"
22 #include <system_error>
27 Error
SpecialCaseList::Matcher::insert(StringRef Pattern
, unsigned LineNumber
,
30 return createStringError(errc::invalid_argument
,
32 (UseGlobs
? "glob" : "regex") + " was blank");
36 auto Regexp
= Pattern
.str();
37 for (size_t pos
= 0; (pos
= Regexp
.find('*', pos
)) != std::string::npos
;
38 pos
+= strlen(".*")) {
39 Regexp
.replace(pos
, strlen("*"), ".*");
42 Regexp
= (Twine("^(") + StringRef(Regexp
) + ")$").str();
44 // Check that the regexp is valid.
45 Regex
CheckRE(Regexp
);
47 if (!CheckRE
.isValid(REError
))
48 return createStringError(errc::invalid_argument
, REError
);
50 RegExes
.emplace_back(std::make_pair(
51 std::make_unique
<Regex
>(std::move(CheckRE
)), LineNumber
));
53 return Error::success();
56 auto [It
, DidEmplace
] = Globs
.try_emplace(Pattern
);
58 // We must be sure to use the string in the map rather than the provided
59 // reference which could be destroyed before match() is called
60 Pattern
= It
->getKey();
61 auto &Pair
= It
->getValue();
62 if (auto Err
= GlobPattern::create(Pattern
, /*MaxSubPatterns=*/1024)
63 .moveInto(Pair
.first
))
65 Pair
.second
= LineNumber
;
67 return Error::success();
70 unsigned SpecialCaseList::Matcher::match(StringRef Query
) const {
71 for (const auto &[Pattern
, Pair
] : Globs
)
72 if (Pair
.first
.match(Query
))
74 for (const auto &[Regex
, LineNumber
] : RegExes
)
75 if (Regex
->match(Query
))
80 // TODO: Refactor this to return Expected<...>
81 std::unique_ptr
<SpecialCaseList
>
82 SpecialCaseList::create(const std::vector
<std::string
> &Paths
,
83 llvm::vfs::FileSystem
&FS
, std::string
&Error
) {
84 std::unique_ptr
<SpecialCaseList
> SCL(new SpecialCaseList());
85 if (SCL
->createInternal(Paths
, FS
, Error
))
90 std::unique_ptr
<SpecialCaseList
> SpecialCaseList::create(const MemoryBuffer
*MB
,
92 std::unique_ptr
<SpecialCaseList
> SCL(new SpecialCaseList());
93 if (SCL
->createInternal(MB
, Error
))
98 std::unique_ptr
<SpecialCaseList
>
99 SpecialCaseList::createOrDie(const std::vector
<std::string
> &Paths
,
100 llvm::vfs::FileSystem
&FS
) {
102 if (auto SCL
= create(Paths
, FS
, Error
))
104 report_fatal_error(Twine(Error
));
107 bool SpecialCaseList::createInternal(const std::vector
<std::string
> &Paths
,
108 vfs::FileSystem
&VFS
, std::string
&Error
) {
109 for (const auto &Path
: Paths
) {
110 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> FileOrErr
=
111 VFS
.getBufferForFile(Path
);
112 if (std::error_code EC
= FileOrErr
.getError()) {
113 Error
= (Twine("can't open file '") + Path
+ "': " + EC
.message()).str();
116 std::string ParseError
;
117 if (!parse(FileOrErr
.get().get(), ParseError
)) {
118 Error
= (Twine("error parsing file '") + Path
+ "': " + ParseError
).str();
125 bool SpecialCaseList::createInternal(const MemoryBuffer
*MB
,
126 std::string
&Error
) {
127 if (!parse(MB
, Error
))
132 Expected
<SpecialCaseList::Section
*>
133 SpecialCaseList::addSection(StringRef SectionStr
, unsigned LineNo
,
135 auto [It
, DidEmplace
] = Sections
.try_emplace(SectionStr
);
136 auto &Section
= It
->getValue();
138 if (auto Err
= Section
.SectionMatcher
->insert(SectionStr
, LineNo
, UseGlobs
))
139 return createStringError(errc::invalid_argument
,
140 "malformed section at line " + Twine(LineNo
) +
142 "': " + toString(std::move(Err
)));
146 bool SpecialCaseList::parse(const MemoryBuffer
*MB
, std::string
&Error
) {
147 Section
*CurrentSection
;
148 if (auto Err
= addSection("*", 1).moveInto(CurrentSection
)) {
149 Error
= toString(std::move(Err
));
153 // In https://reviews.llvm.org/D154014 we transitioned to using globs instead
154 // of regexes to match patterns in special case lists. Since this was a
155 // breaking change, we will temporarily support the original behavior using
156 // regexes. If "#!special-case-list-v2" is the first line of the file, then
157 // we will use the new behavior using globs. For more details, see
158 // https://discourse.llvm.org/t/use-glob-instead-of-regex-for-specialcaselists/71666
159 bool UseGlobs
= MB
->getBuffer().startswith("#!special-case-list-v2\n");
161 for (line_iterator
LineIt(*MB
, /*SkipBlanks=*/true, /*CommentMarker=*/'#');
162 !LineIt
.is_at_eof(); LineIt
++) {
163 unsigned LineNo
= LineIt
.line_number();
164 StringRef Line
= LineIt
->trim();
168 // Save section names
169 if (Line
.startswith("[")) {
170 if (!Line
.endswith("]")) {
172 ("malformed section header on line " + Twine(LineNo
) + ": " + Line
)
177 if (auto Err
= addSection(Line
.drop_front().drop_back(), LineNo
, UseGlobs
)
178 .moveInto(CurrentSection
)) {
179 Error
= toString(std::move(Err
));
185 // Get our prefix and unparsed glob.
186 auto [Prefix
, Postfix
] = Line
.split(":");
187 if (Postfix
.empty()) {
188 // Missing ':' in the line.
189 Error
= ("malformed line " + Twine(LineNo
) + ": '" + Line
+ "'").str();
193 auto [Pattern
, Category
] = Postfix
.split("=");
194 auto &Entry
= CurrentSection
->Entries
[Prefix
][Category
];
195 if (auto Err
= Entry
.insert(Pattern
, LineNo
, UseGlobs
)) {
197 (Twine("malformed ") + (UseGlobs
? "glob" : "regex") + " in line " +
198 Twine(LineNo
) + ": '" + Pattern
+ "': " + toString(std::move(Err
)))
206 SpecialCaseList::~SpecialCaseList() = default;
208 bool SpecialCaseList::inSection(StringRef Section
, StringRef Prefix
,
209 StringRef Query
, StringRef Category
) const {
210 return inSectionBlame(Section
, Prefix
, Query
, Category
);
213 unsigned SpecialCaseList::inSectionBlame(StringRef Section
, StringRef Prefix
,
215 StringRef Category
) const {
216 for (const auto &It
: Sections
) {
217 const auto &S
= It
.getValue();
218 if (S
.SectionMatcher
->match(Section
)) {
219 unsigned Blame
= inSectionBlame(S
.Entries
, Prefix
, Query
, Category
);
227 unsigned SpecialCaseList::inSectionBlame(const SectionEntries
&Entries
,
228 StringRef Prefix
, StringRef Query
,
229 StringRef Category
) const {
230 SectionEntries::const_iterator I
= Entries
.find(Prefix
);
231 if (I
== Entries
.end()) return 0;
232 StringMap
<Matcher
>::const_iterator II
= I
->second
.find(Category
);
233 if (II
== I
->second
.end()) return 0;
235 return II
->getValue().match(Query
);