File: | tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp |
Warning: | line 285, column 1 Potential leak of memory pointed to by field 'Obj' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // | |||
2 | // | |||
3 | // The LLVM Compiler Infrastructure | |||
4 | // | |||
5 | // This file is distributed under the University of Illinois Open Source | |||
6 | // License. See LICENSE.TXT for details. | |||
7 | // | |||
8 | //===----------------------------------------------------------------------===// | |||
9 | /// | |||
10 | /// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext | |||
11 | /// and ClangTidyError classes. | |||
12 | /// | |||
13 | /// This tool uses the Clang Tooling infrastructure, see | |||
14 | /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html | |||
15 | /// for details on setting it up with LLVM source tree. | |||
16 | /// | |||
17 | //===----------------------------------------------------------------------===// | |||
18 | ||||
19 | #include "ClangTidyDiagnosticConsumer.h" | |||
20 | #include "ClangTidyOptions.h" | |||
21 | #include "clang/AST/ASTDiagnostic.h" | |||
22 | #include "clang/Basic/DiagnosticOptions.h" | |||
23 | #include "clang/Frontend/DiagnosticRenderer.h" | |||
24 | #include "llvm/ADT/STLExtras.h" | |||
25 | #include "llvm/ADT/SmallString.h" | |||
26 | #include <tuple> | |||
27 | #include <vector> | |||
28 | using namespace clang; | |||
29 | using namespace tidy; | |||
30 | ||||
31 | namespace { | |||
32 | class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { | |||
33 | public: | |||
34 | ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, | |||
35 | DiagnosticOptions *DiagOpts, | |||
36 | ClangTidyError &Error) | |||
37 | : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} | |||
38 | ||||
39 | protected: | |||
40 | void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc, | |||
41 | DiagnosticsEngine::Level Level, StringRef Message, | |||
42 | ArrayRef<CharSourceRange> Ranges, | |||
43 | DiagOrStoredDiag Info) override { | |||
44 | // Remove check name from the message. | |||
45 | // FIXME: Remove this once there's a better way to pass check names than | |||
46 | // appending the check name to the message in ClangTidyContext::diag and | |||
47 | // using getCustomDiagID. | |||
48 | std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]"; | |||
49 | if (Message.endswith(CheckNameInMessage)) | |||
50 | Message = Message.substr(0, Message.size() - CheckNameInMessage.size()); | |||
51 | ||||
52 | auto TidyMessage = | |||
53 | Loc.isValid() | |||
54 | ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc) | |||
55 | : tooling::DiagnosticMessage(Message); | |||
56 | if (Level == DiagnosticsEngine::Note) { | |||
57 | Error.Notes.push_back(TidyMessage); | |||
58 | return; | |||
59 | } | |||
60 | assert(Error.Message.Message.empty() && "Overwriting a diagnostic message")(static_cast <bool> (Error.Message.Message.empty() && "Overwriting a diagnostic message") ? void (0) : __assert_fail ("Error.Message.Message.empty() && \"Overwriting a diagnostic message\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 60, __extension__ __PRETTY_FUNCTION__)); | |||
61 | Error.Message = TidyMessage; | |||
62 | } | |||
63 | ||||
64 | void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, | |||
65 | DiagnosticsEngine::Level Level, | |||
66 | ArrayRef<CharSourceRange> Ranges) override {} | |||
67 | ||||
68 | void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, | |||
69 | SmallVectorImpl<CharSourceRange> &Ranges, | |||
70 | ArrayRef<FixItHint> Hints) override { | |||
71 | assert(Loc.isValid())(static_cast <bool> (Loc.isValid()) ? void (0) : __assert_fail ("Loc.isValid()", "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 71, __extension__ __PRETTY_FUNCTION__)); | |||
72 | for (const auto &FixIt : Hints) { | |||
73 | CharSourceRange Range = FixIt.RemoveRange; | |||
74 | assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&(static_cast <bool> (Range.getBegin().isValid() && Range.getEnd().isValid() && "Invalid range in the fix-it hint." ) ? void (0) : __assert_fail ("Range.getBegin().isValid() && Range.getEnd().isValid() && \"Invalid range in the fix-it hint.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 75, __extension__ __PRETTY_FUNCTION__)) | |||
75 | "Invalid range in the fix-it hint.")(static_cast <bool> (Range.getBegin().isValid() && Range.getEnd().isValid() && "Invalid range in the fix-it hint." ) ? void (0) : __assert_fail ("Range.getBegin().isValid() && Range.getEnd().isValid() && \"Invalid range in the fix-it hint.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 75, __extension__ __PRETTY_FUNCTION__)); | |||
76 | assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&(static_cast <bool> (Range.getBegin().isFileID() && Range.getEnd().isFileID() && "Only file locations supported in fix-it hints." ) ? void (0) : __assert_fail ("Range.getBegin().isFileID() && Range.getEnd().isFileID() && \"Only file locations supported in fix-it hints.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 77, __extension__ __PRETTY_FUNCTION__)) | |||
77 | "Only file locations supported in fix-it hints.")(static_cast <bool> (Range.getBegin().isFileID() && Range.getEnd().isFileID() && "Only file locations supported in fix-it hints." ) ? void (0) : __assert_fail ("Range.getBegin().isFileID() && Range.getEnd().isFileID() && \"Only file locations supported in fix-it hints.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 77, __extension__ __PRETTY_FUNCTION__)); | |||
78 | ||||
79 | tooling::Replacement Replacement(Loc.getManager(), Range, | |||
80 | FixIt.CodeToInsert); | |||
81 | llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement); | |||
82 | // FIXME: better error handling (at least, don't let other replacements be | |||
83 | // applied). | |||
84 | if (Err) { | |||
85 | llvm::errs() << "Fix conflicts with existing fix! " | |||
86 | << llvm::toString(std::move(Err)) << "\n"; | |||
87 | assert(false && "Fix conflicts with existing fix!")(static_cast <bool> (false && "Fix conflicts with existing fix!" ) ? void (0) : __assert_fail ("false && \"Fix conflicts with existing fix!\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 87, __extension__ __PRETTY_FUNCTION__)); | |||
88 | } | |||
89 | } | |||
90 | } | |||
91 | ||||
92 | void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {} | |||
93 | ||||
94 | void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, | |||
95 | StringRef ModuleName) override {} | |||
96 | ||||
97 | void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc, | |||
98 | StringRef ModuleName) override {} | |||
99 | ||||
100 | void endDiagnostic(DiagOrStoredDiag D, | |||
101 | DiagnosticsEngine::Level Level) override { | |||
102 | assert(!Error.Message.Message.empty() && "Message has not been set")(static_cast <bool> (!Error.Message.Message.empty() && "Message has not been set") ? void (0) : __assert_fail ("!Error.Message.Message.empty() && \"Message has not been set\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 102, __extension__ __PRETTY_FUNCTION__)); | |||
103 | } | |||
104 | ||||
105 | private: | |||
106 | ClangTidyError &Error; | |||
107 | }; | |||
108 | } // end anonymous namespace | |||
109 | ||||
110 | ClangTidyError::ClangTidyError(StringRef CheckName, | |||
111 | ClangTidyError::Level DiagLevel, | |||
112 | StringRef BuildDirectory, bool IsWarningAsError) | |||
113 | : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory), | |||
114 | IsWarningAsError(IsWarningAsError) {} | |||
115 | ||||
116 | // Returns true if GlobList starts with the negative indicator ('-'), removes it | |||
117 | // from the GlobList. | |||
118 | static bool ConsumeNegativeIndicator(StringRef &GlobList) { | |||
119 | GlobList = GlobList.trim(" \r\n"); | |||
120 | if (GlobList.startswith("-")) { | |||
121 | GlobList = GlobList.substr(1); | |||
122 | return true; | |||
123 | } | |||
124 | return false; | |||
125 | } | |||
126 | // Converts first glob from the comma-separated list of globs to Regex and | |||
127 | // removes it and the trailing comma from the GlobList. | |||
128 | static llvm::Regex ConsumeGlob(StringRef &GlobList) { | |||
129 | StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(',')); | |||
130 | StringRef Glob = UntrimmedGlob.trim(' '); | |||
131 | GlobList = GlobList.substr(UntrimmedGlob.size() + 1); | |||
132 | SmallString<128> RegexText("^"); | |||
133 | StringRef MetaChars("()^$|*+?.[]\\{}"); | |||
134 | for (char C : Glob) { | |||
135 | if (C == '*') | |||
136 | RegexText.push_back('.'); | |||
137 | else if (MetaChars.find(C) != StringRef::npos) | |||
138 | RegexText.push_back('\\'); | |||
139 | RegexText.push_back(C); | |||
140 | } | |||
141 | RegexText.push_back('$'); | |||
142 | return llvm::Regex(RegexText); | |||
143 | } | |||
144 | ||||
145 | GlobList::GlobList(StringRef Globs) | |||
146 | : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)), | |||
147 | NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {} | |||
148 | ||||
149 | bool GlobList::contains(StringRef S, bool Contains) { | |||
150 | if (Regex.match(S)) | |||
151 | Contains = Positive; | |||
152 | ||||
153 | if (NextGlob) | |||
154 | Contains = NextGlob->contains(S, Contains); | |||
155 | return Contains; | |||
156 | } | |||
157 | ||||
158 | class ClangTidyContext::CachedGlobList { | |||
159 | public: | |||
160 | CachedGlobList(StringRef Globs) : Globs(Globs) {} | |||
161 | ||||
162 | bool contains(StringRef S) { | |||
163 | switch (auto &Result = Cache[S]) { | |||
164 | case Yes: return true; | |||
165 | case No: return false; | |||
166 | case None: | |||
167 | Result = Globs.contains(S) ? Yes : No; | |||
168 | return Result == Yes; | |||
169 | } | |||
170 | llvm_unreachable("invalid enum")::llvm::llvm_unreachable_internal("invalid enum", "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 170); | |||
171 | } | |||
172 | ||||
173 | private: | |||
174 | GlobList Globs; | |||
175 | enum Tristate { None, Yes, No }; | |||
176 | llvm::StringMap<Tristate> Cache; | |||
177 | }; | |||
178 | ||||
179 | ClangTidyContext::ClangTidyContext( | |||
180 | std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider, | |||
181 | bool AllowEnablingAnalyzerAlphaCheckers) | |||
182 | : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), | |||
183 | Profile(false), | |||
184 | AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) { | |||
185 | // Before the first translation unit we can get errors related to command-line | |||
186 | // parsing, use empty string for the file name in this case. | |||
187 | setCurrentFile(""); | |||
188 | } | |||
189 | ||||
190 | ClangTidyContext::~ClangTidyContext() = default; | |||
191 | ||||
192 | DiagnosticBuilder ClangTidyContext::diag( | |||
193 | StringRef CheckName, SourceLocation Loc, StringRef Description, | |||
194 | DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { | |||
195 | assert(Loc.isValid())(static_cast <bool> (Loc.isValid()) ? void (0) : __assert_fail ("Loc.isValid()", "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 195, __extension__ __PRETTY_FUNCTION__)); | |||
196 | unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( | |||
197 | Level, (Description + " [" + CheckName + "]").str()); | |||
198 | CheckNamesByDiagnosticID.try_emplace(ID, CheckName); | |||
199 | return DiagEngine->Report(Loc, ID); | |||
200 | } | |||
201 | ||||
202 | void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) { | |||
203 | DiagEngine = Engine; | |||
204 | } | |||
205 | ||||
206 | void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) { | |||
207 | DiagEngine->setSourceManager(SourceMgr); | |||
208 | } | |||
209 | ||||
210 | void ClangTidyContext::setCurrentFile(StringRef File) { | |||
211 | CurrentFile = File; | |||
212 | CurrentOptions = getOptionsForFile(CurrentFile); | |||
213 | CheckFilter = llvm::make_unique<CachedGlobList>(*getOptions().Checks); | |||
214 | WarningAsErrorFilter = | |||
215 | llvm::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors); | |||
216 | } | |||
217 | ||||
218 | void ClangTidyContext::setASTContext(ASTContext *Context) { | |||
219 | DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); | |||
220 | LangOpts = Context->getLangOpts(); | |||
221 | } | |||
222 | ||||
223 | const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { | |||
224 | return OptionsProvider->getGlobalOptions(); | |||
225 | } | |||
226 | ||||
227 | const ClangTidyOptions &ClangTidyContext::getOptions() const { | |||
228 | return CurrentOptions; | |||
229 | } | |||
230 | ||||
231 | ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { | |||
232 | // Merge options on top of getDefaults() as a safeguard against options with | |||
233 | // unset values. | |||
234 | return ClangTidyOptions::getDefaults().mergeWith( | |||
235 | OptionsProvider->getOptions(File)); | |||
236 | } | |||
237 | ||||
238 | void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; } | |||
239 | ||||
240 | void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) { | |||
241 | ProfilePrefix = Prefix; | |||
242 | } | |||
243 | ||||
244 | llvm::Optional<ClangTidyProfiling::StorageParams> | |||
245 | ClangTidyContext::getProfileStorageParams() const { | |||
246 | if (ProfilePrefix.empty()) | |||
247 | return llvm::None; | |||
248 | ||||
249 | return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile); | |||
250 | } | |||
251 | ||||
252 | bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const { | |||
253 | assert(CheckFilter != nullptr)(static_cast <bool> (CheckFilter != nullptr) ? void (0) : __assert_fail ("CheckFilter != nullptr", "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 253, __extension__ __PRETTY_FUNCTION__)); | |||
254 | return CheckFilter->contains(CheckName); | |||
255 | } | |||
256 | ||||
257 | bool ClangTidyContext::treatAsError(StringRef CheckName) const { | |||
258 | assert(WarningAsErrorFilter != nullptr)(static_cast <bool> (WarningAsErrorFilter != nullptr) ? void (0) : __assert_fail ("WarningAsErrorFilter != nullptr", "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 258, __extension__ __PRETTY_FUNCTION__)); | |||
259 | return WarningAsErrorFilter->contains(CheckName); | |||
260 | } | |||
261 | ||||
262 | /// \brief Store a \c ClangTidyError. | |||
263 | void ClangTidyContext::storeError(const ClangTidyError &Error) { | |||
264 | Errors.push_back(Error); | |||
265 | } | |||
266 | ||||
267 | StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const { | |||
268 | llvm::DenseMap<unsigned, std::string>::const_iterator I = | |||
269 | CheckNamesByDiagnosticID.find(DiagnosticID); | |||
270 | if (I != CheckNamesByDiagnosticID.end()) | |||
271 | return I->second; | |||
272 | return ""; | |||
273 | } | |||
274 | ||||
275 | ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer( | |||
276 | ClangTidyContext &Ctx, bool RemoveIncompatibleErrors) | |||
277 | : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors), | |||
278 | LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false), | |||
279 | LastErrorWasIgnored(false) { | |||
280 | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); | |||
| ||||
281 | Diags = llvm::make_unique<DiagnosticsEngine>( | |||
282 | IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this, | |||
283 | /*ShouldOwnClient=*/false); | |||
284 | Context.setDiagnosticsEngine(Diags.get()); | |||
285 | } | |||
| ||||
286 | ||||
287 | void ClangTidyDiagnosticConsumer::finalizeLastError() { | |||
288 | if (!Errors.empty()) { | |||
289 | ClangTidyError &Error = Errors.back(); | |||
290 | if (!Context.isCheckEnabled(Error.DiagnosticName) && | |||
291 | Error.DiagLevel != ClangTidyError::Error) { | |||
292 | ++Context.Stats.ErrorsIgnoredCheckFilter; | |||
293 | Errors.pop_back(); | |||
294 | } else if (!LastErrorRelatesToUserCode) { | |||
295 | ++Context.Stats.ErrorsIgnoredNonUserCode; | |||
296 | Errors.pop_back(); | |||
297 | } else if (!LastErrorPassesLineFilter) { | |||
298 | ++Context.Stats.ErrorsIgnoredLineFilter; | |||
299 | Errors.pop_back(); | |||
300 | } else { | |||
301 | ++Context.Stats.ErrorsDisplayed; | |||
302 | } | |||
303 | } | |||
304 | LastErrorRelatesToUserCode = false; | |||
305 | LastErrorPassesLineFilter = false; | |||
306 | } | |||
307 | ||||
308 | static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, | |||
309 | unsigned DiagID, const ClangTidyContext &Context) { | |||
310 | const size_t NolintIndex = Line.find(NolintDirectiveText); | |||
311 | if (NolintIndex == StringRef::npos) | |||
312 | return false; | |||
313 | ||||
314 | size_t BracketIndex = NolintIndex + NolintDirectiveText.size(); | |||
315 | // Check if the specific checks are specified in brackets. | |||
316 | if (BracketIndex < Line.size() && Line[BracketIndex] == '(') { | |||
317 | ++BracketIndex; | |||
318 | const size_t BracketEndIndex = Line.find(')', BracketIndex); | |||
319 | if (BracketEndIndex != StringRef::npos) { | |||
320 | StringRef ChecksStr = | |||
321 | Line.substr(BracketIndex, BracketEndIndex - BracketIndex); | |||
322 | // Allow disabling all the checks with "*". | |||
323 | if (ChecksStr != "*") { | |||
324 | StringRef CheckName = Context.getCheckName(DiagID); | |||
325 | // Allow specifying a few check names, delimited with comma. | |||
326 | SmallVector<StringRef, 1> Checks; | |||
327 | ChecksStr.split(Checks, ',', -1, false); | |||
328 | llvm::transform(Checks, Checks.begin(), | |||
329 | [](StringRef S) { return S.trim(); }); | |||
330 | return llvm::find(Checks, CheckName) != Checks.end(); | |||
331 | } | |||
332 | } | |||
333 | } | |||
334 | return true; | |||
335 | } | |||
336 | ||||
337 | static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc, | |||
338 | unsigned DiagID, | |||
339 | const ClangTidyContext &Context) { | |||
340 | bool Invalid; | |||
341 | const char *CharacterData = SM.getCharacterData(Loc, &Invalid); | |||
342 | if (Invalid) | |||
343 | return false; | |||
344 | ||||
345 | // Check if there's a NOLINT on this line. | |||
346 | const char *P = CharacterData; | |||
347 | while (*P != '\0' && *P != '\r' && *P != '\n') | |||
348 | ++P; | |||
349 | StringRef RestOfLine(CharacterData, P - CharacterData + 1); | |||
350 | if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context)) | |||
351 | return true; | |||
352 | ||||
353 | // Check if there's a NOLINTNEXTLINE on the previous line. | |||
354 | const char *BufBegin = | |||
355 | SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid); | |||
356 | if (Invalid || P == BufBegin) | |||
357 | return false; | |||
358 | ||||
359 | // Scan backwards over the current line. | |||
360 | P = CharacterData; | |||
361 | while (P != BufBegin && *P != '\n') | |||
362 | --P; | |||
363 | ||||
364 | // If we reached the begin of the file there is no line before it. | |||
365 | if (P == BufBegin) | |||
366 | return false; | |||
367 | ||||
368 | // Skip over the newline. | |||
369 | --P; | |||
370 | const char *LineEnd = P; | |||
371 | ||||
372 | // Now we're on the previous line. Skip to the beginning of it. | |||
373 | while (P != BufBegin && *P != '\n') | |||
374 | --P; | |||
375 | ||||
376 | RestOfLine = StringRef(P, LineEnd - P + 1); | |||
377 | if (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context)) | |||
378 | return true; | |||
379 | ||||
380 | return false; | |||
381 | } | |||
382 | ||||
383 | static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, | |||
384 | unsigned DiagID, | |||
385 | const ClangTidyContext &Context) { | |||
386 | while (true) { | |||
387 | if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context)) | |||
388 | return true; | |||
389 | if (!Loc.isMacroID()) | |||
390 | return false; | |||
391 | Loc = SM.getImmediateExpansionRange(Loc).getBegin(); | |||
392 | } | |||
393 | return false; | |||
394 | } | |||
395 | ||||
396 | void ClangTidyDiagnosticConsumer::HandleDiagnostic( | |||
397 | DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { | |||
398 | if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) | |||
399 | return; | |||
400 | ||||
401 | if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error && | |||
402 | DiagLevel != DiagnosticsEngine::Fatal && | |||
403 | LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(), | |||
404 | Info.getLocation(), Info.getID(), | |||
405 | Context)) { | |||
406 | ++Context.Stats.ErrorsIgnoredNOLINT; | |||
407 | // Ignored a warning, should ignore related notes as well | |||
408 | LastErrorWasIgnored = true; | |||
409 | return; | |||
410 | } | |||
411 | ||||
412 | LastErrorWasIgnored = false; | |||
413 | // Count warnings/errors. | |||
414 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); | |||
415 | ||||
416 | if (DiagLevel == DiagnosticsEngine::Note) { | |||
417 | assert(!Errors.empty() &&(static_cast <bool> (!Errors.empty() && "A diagnostic note can only be appended to a message." ) ? void (0) : __assert_fail ("!Errors.empty() && \"A diagnostic note can only be appended to a message.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 418, __extension__ __PRETTY_FUNCTION__)) | |||
418 | "A diagnostic note can only be appended to a message.")(static_cast <bool> (!Errors.empty() && "A diagnostic note can only be appended to a message." ) ? void (0) : __assert_fail ("!Errors.empty() && \"A diagnostic note can only be appended to a message.\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 418, __extension__ __PRETTY_FUNCTION__)); | |||
419 | } else { | |||
420 | finalizeLastError(); | |||
421 | StringRef WarningOption = | |||
422 | Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag( | |||
423 | Info.getID()); | |||
424 | std::string CheckName = !WarningOption.empty() | |||
425 | ? ("clang-diagnostic-" + WarningOption).str() | |||
426 | : Context.getCheckName(Info.getID()).str(); | |||
427 | ||||
428 | if (CheckName.empty()) { | |||
429 | // This is a compiler diagnostic without a warning option. Assign check | |||
430 | // name based on its level. | |||
431 | switch (DiagLevel) { | |||
432 | case DiagnosticsEngine::Error: | |||
433 | case DiagnosticsEngine::Fatal: | |||
434 | CheckName = "clang-diagnostic-error"; | |||
435 | break; | |||
436 | case DiagnosticsEngine::Warning: | |||
437 | CheckName = "clang-diagnostic-warning"; | |||
438 | break; | |||
439 | default: | |||
440 | CheckName = "clang-diagnostic-unknown"; | |||
441 | break; | |||
442 | } | |||
443 | } | |||
444 | ||||
445 | ClangTidyError::Level Level = ClangTidyError::Warning; | |||
446 | if (DiagLevel == DiagnosticsEngine::Error || | |||
447 | DiagLevel == DiagnosticsEngine::Fatal) { | |||
448 | // Force reporting of Clang errors regardless of filters and non-user | |||
449 | // code. | |||
450 | Level = ClangTidyError::Error; | |||
451 | LastErrorRelatesToUserCode = true; | |||
452 | LastErrorPassesLineFilter = true; | |||
453 | } | |||
454 | bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && | |||
455 | Context.treatAsError(CheckName); | |||
456 | Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(), | |||
457 | IsWarningAsError); | |||
458 | } | |||
459 | ||||
460 | ClangTidyDiagnosticRenderer Converter( | |||
461 | Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), | |||
462 | Errors.back()); | |||
463 | SmallString<100> Message; | |||
464 | Info.FormatDiagnostic(Message); | |||
465 | FullSourceLoc Loc = | |||
466 | (Info.getLocation().isInvalid()) | |||
467 | ? FullSourceLoc() | |||
468 | : FullSourceLoc(Info.getLocation(), Info.getSourceManager()); | |||
469 | Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(), | |||
470 | Info.getFixItHints()); | |||
471 | ||||
472 | checkFilters(Info.getLocation()); | |||
473 | } | |||
474 | ||||
475 | bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, | |||
476 | unsigned LineNumber) const { | |||
477 | if (Context.getGlobalOptions().LineFilter.empty()) | |||
478 | return true; | |||
479 | for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { | |||
480 | if (FileName.endswith(Filter.Name)) { | |||
481 | if (Filter.LineRanges.empty()) | |||
482 | return true; | |||
483 | for (const FileFilter::LineRange &Range : Filter.LineRanges) { | |||
484 | if (Range.first <= LineNumber && LineNumber <= Range.second) | |||
485 | return true; | |||
486 | } | |||
487 | return false; | |||
488 | } | |||
489 | } | |||
490 | return false; | |||
491 | } | |||
492 | ||||
493 | void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) { | |||
494 | // Invalid location may mean a diagnostic in a command line, don't skip these. | |||
495 | if (!Location.isValid()) { | |||
496 | LastErrorRelatesToUserCode = true; | |||
497 | LastErrorPassesLineFilter = true; | |||
498 | return; | |||
499 | } | |||
500 | ||||
501 | const SourceManager &Sources = Diags->getSourceManager(); | |||
502 | if (!*Context.getOptions().SystemHeaders && | |||
503 | Sources.isInSystemHeader(Location)) | |||
504 | return; | |||
505 | ||||
506 | // FIXME: We start with a conservative approach here, but the actual type of | |||
507 | // location needed depends on the check (in particular, where this check wants | |||
508 | // to apply fixes). | |||
509 | FileID FID = Sources.getDecomposedExpansionLoc(Location).first; | |||
510 | const FileEntry *File = Sources.getFileEntryForID(FID); | |||
511 | ||||
512 | // -DMACRO definitions on the command line have locations in a virtual buffer | |||
513 | // that doesn't have a FileEntry. Don't skip these as well. | |||
514 | if (!File) { | |||
515 | LastErrorRelatesToUserCode = true; | |||
516 | LastErrorPassesLineFilter = true; | |||
517 | return; | |||
518 | } | |||
519 | ||||
520 | StringRef FileName(File->getName()); | |||
521 | LastErrorRelatesToUserCode = LastErrorRelatesToUserCode || | |||
522 | Sources.isInMainFile(Location) || | |||
523 | getHeaderFilter()->match(FileName); | |||
524 | ||||
525 | unsigned LineNumber = Sources.getExpansionLineNumber(Location); | |||
526 | LastErrorPassesLineFilter = | |||
527 | LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); | |||
528 | } | |||
529 | ||||
530 | llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() { | |||
531 | if (!HeaderFilter) | |||
532 | HeaderFilter = | |||
533 | llvm::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex); | |||
534 | return HeaderFilter.get(); | |||
535 | } | |||
536 | ||||
537 | void ClangTidyDiagnosticConsumer::removeIncompatibleErrors( | |||
538 | SmallVectorImpl<ClangTidyError> &Errors) const { | |||
539 | // Each error is modelled as the set of intervals in which it applies | |||
540 | // replacements. To detect overlapping replacements, we use a sweep line | |||
541 | // algorithm over these sets of intervals. | |||
542 | // An event here consists of the opening or closing of an interval. During the | |||
543 | // process, we maintain a counter with the amount of open intervals. If we | |||
544 | // find an endpoint of an interval and this counter is different from 0, it | |||
545 | // means that this interval overlaps with another one, so we set it as | |||
546 | // inapplicable. | |||
547 | struct Event { | |||
548 | // An event can be either the begin or the end of an interval. | |||
549 | enum EventType { | |||
550 | ET_Begin = 1, | |||
551 | ET_End = -1, | |||
552 | }; | |||
553 | ||||
554 | Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, | |||
555 | unsigned ErrorSize) | |||
556 | : Type(Type), ErrorId(ErrorId) { | |||
557 | // The events are going to be sorted by their position. In case of draw: | |||
558 | // | |||
559 | // * If an interval ends at the same position at which other interval | |||
560 | // begins, this is not an overlapping, so we want to remove the ending | |||
561 | // interval before adding the starting one: end events have higher | |||
562 | // priority than begin events. | |||
563 | // | |||
564 | // * If we have several begin points at the same position, we will mark as | |||
565 | // inapplicable the ones that we process later, so the first one has to | |||
566 | // be the one with the latest end point, because this one will contain | |||
567 | // all the other intervals. For the same reason, if we have several end | |||
568 | // points in the same position, the last one has to be the one with the | |||
569 | // earliest begin point. In both cases, we sort non-increasingly by the | |||
570 | // position of the complementary. | |||
571 | // | |||
572 | // * In case of two equal intervals, the one whose error is bigger can | |||
573 | // potentially contain the other one, so we want to process its begin | |||
574 | // points before and its end points later. | |||
575 | // | |||
576 | // * Finally, if we have two equal intervals whose errors have the same | |||
577 | // size, none of them will be strictly contained inside the other. | |||
578 | // Sorting by ErrorId will guarantee that the begin point of the first | |||
579 | // one will be processed before, disallowing the second one, and the | |||
580 | // end point of the first one will also be processed before, | |||
581 | // disallowing the first one. | |||
582 | if (Type == ET_Begin) | |||
583 | Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId); | |||
584 | else | |||
585 | Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId); | |||
586 | } | |||
587 | ||||
588 | bool operator<(const Event &Other) const { | |||
589 | return Priority < Other.Priority; | |||
590 | } | |||
591 | ||||
592 | // Determines if this event is the begin or the end of an interval. | |||
593 | EventType Type; | |||
594 | // The index of the error to which the interval that generated this event | |||
595 | // belongs. | |||
596 | unsigned ErrorId; | |||
597 | // The events will be sorted based on this field. | |||
598 | std::tuple<unsigned, EventType, int, int, unsigned> Priority; | |||
599 | }; | |||
600 | ||||
601 | // Compute error sizes. | |||
602 | std::vector<int> Sizes; | |||
603 | for (const auto &Error : Errors) { | |||
604 | int Size = 0; | |||
605 | for (const auto &FileAndReplaces : Error.Fix) { | |||
606 | for (const auto &Replace : FileAndReplaces.second) | |||
607 | Size += Replace.getLength(); | |||
608 | } | |||
609 | Sizes.push_back(Size); | |||
610 | } | |||
611 | ||||
612 | // Build events from error intervals. | |||
613 | std::map<std::string, std::vector<Event>> FileEvents; | |||
614 | for (unsigned I = 0; I < Errors.size(); ++I) { | |||
615 | for (const auto &FileAndReplace : Errors[I].Fix) { | |||
616 | for (const auto &Replace : FileAndReplace.second) { | |||
617 | unsigned Begin = Replace.getOffset(); | |||
618 | unsigned End = Begin + Replace.getLength(); | |||
619 | const std::string &FilePath = Replace.getFilePath(); | |||
620 | // FIXME: Handle empty intervals, such as those from insertions. | |||
621 | if (Begin == End) | |||
622 | continue; | |||
623 | auto &Events = FileEvents[FilePath]; | |||
624 | Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]); | |||
625 | Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]); | |||
626 | } | |||
627 | } | |||
628 | } | |||
629 | ||||
630 | std::vector<bool> Apply(Errors.size(), true); | |||
631 | for (auto &FileAndEvents : FileEvents) { | |||
632 | std::vector<Event> &Events = FileAndEvents.second; | |||
633 | // Sweep. | |||
634 | std::sort(Events.begin(), Events.end()); | |||
635 | int OpenIntervals = 0; | |||
636 | for (const auto &Event : Events) { | |||
637 | if (Event.Type == Event::ET_End) | |||
638 | --OpenIntervals; | |||
639 | // This has to be checked after removing the interval from the count if it | |||
640 | // is an end event, or before adding it if it is a begin event. | |||
641 | if (OpenIntervals != 0) | |||
642 | Apply[Event.ErrorId] = false; | |||
643 | if (Event.Type == Event::ET_Begin) | |||
644 | ++OpenIntervals; | |||
645 | } | |||
646 | assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match")(static_cast <bool> (OpenIntervals == 0 && "Amount of begin/end points doesn't match" ) ? void (0) : __assert_fail ("OpenIntervals == 0 && \"Amount of begin/end points doesn't match\"" , "/build/llvm-toolchain-snapshot-7~svn338205/tools/clang/tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp" , 646, __extension__ __PRETTY_FUNCTION__)); | |||
647 | } | |||
648 | ||||
649 | for (unsigned I = 0; I < Errors.size(); ++I) { | |||
650 | if (!Apply[I]) { | |||
651 | Errors[I].Fix.clear(); | |||
652 | Errors[I].Notes.emplace_back( | |||
653 | "this fix will not be applied because it overlaps with another fix"); | |||
654 | } | |||
655 | } | |||
656 | } | |||
657 | ||||
658 | namespace { | |||
659 | struct LessClangTidyError { | |||
660 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { | |||
661 | const tooling::DiagnosticMessage &M1 = LHS.Message; | |||
662 | const tooling::DiagnosticMessage &M2 = RHS.Message; | |||
663 | ||||
664 | return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < | |||
665 | std::tie(M2.FilePath, M2.FileOffset, M2.Message); | |||
666 | } | |||
667 | }; | |||
668 | struct EqualClangTidyError { | |||
669 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { | |||
670 | LessClangTidyError Less; | |||
671 | return !Less(LHS, RHS) && !Less(RHS, LHS); | |||
672 | } | |||
673 | }; | |||
674 | } // end anonymous namespace | |||
675 | ||||
676 | // Flushes the internal diagnostics buffer to the ClangTidyContext. | |||
677 | void ClangTidyDiagnosticConsumer::finish() { | |||
678 | finalizeLastError(); | |||
679 | ||||
680 | std::sort(Errors.begin(), Errors.end(), LessClangTidyError()); | |||
681 | Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), | |||
682 | Errors.end()); | |||
683 | ||||
684 | if (RemoveIncompatibleErrors) | |||
685 | removeIncompatibleErrors(Errors); | |||
686 | ||||
687 | for (const ClangTidyError &Error : Errors) | |||
688 | Context.storeError(Error); | |||
689 | Errors.clear(); | |||
690 | } |