1 | //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // |
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 | /// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext |
10 | /// and ClangTidyError classes. |
11 | /// |
12 | /// This tool uses the Clang Tooling infrastructure, see |
13 | /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html |
14 | /// for details on setting it up with LLVM source tree. |
15 | /// |
16 | //===----------------------------------------------------------------------===// |
17 | |
18 | #include "ClangTidyDiagnosticConsumer.h" |
19 | #include "ClangTidyOptions.h" |
20 | #include "GlobList.h" |
21 | #include "NoLintDirectiveHandler.h" |
22 | #include "clang/AST/ASTContext.h" |
23 | #include "clang/AST/ASTDiagnostic.h" |
24 | #include "clang/AST/Attr.h" |
25 | #include "clang/Basic/CharInfo.h" |
26 | #include "clang/Basic/Diagnostic.h" |
27 | #include "clang/Basic/DiagnosticOptions.h" |
28 | #include "clang/Basic/FileManager.h" |
29 | #include "clang/Basic/SourceManager.h" |
30 | #include "clang/Frontend/DiagnosticRenderer.h" |
31 | #include "clang/Lex/Lexer.h" |
32 | #include "clang/Tooling/Core/Diagnostic.h" |
33 | #include "clang/Tooling/Core/Replacement.h" |
34 | #include "llvm/ADT/BitVector.h" |
35 | #include "llvm/ADT/STLExtras.h" |
36 | #include "llvm/ADT/SmallString.h" |
37 | #include "llvm/ADT/StringMap.h" |
38 | #include "llvm/Support/FormatVariadic.h" |
39 | #include "llvm/Support/Regex.h" |
40 | #include <optional> |
41 | #include <tuple> |
42 | #include <utility> |
43 | #include <vector> |
44 | using namespace clang; |
45 | using namespace tidy; |
46 | |
47 | namespace { |
48 | class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { |
49 | public: |
50 | ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, |
51 | DiagnosticOptions *DiagOpts, |
52 | ClangTidyError &Error) |
53 | : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} |
54 | |
55 | protected: |
56 | void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc, |
57 | DiagnosticsEngine::Level Level, StringRef Message, |
58 | ArrayRef<CharSourceRange> Ranges, |
59 | DiagOrStoredDiag Info) override { |
60 | // Remove check name from the message. |
61 | // FIXME: Remove this once there's a better way to pass check names than |
62 | // appending the check name to the message in ClangTidyContext::diag and |
63 | // using getCustomDiagID. |
64 | std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]" ; |
65 | if (Message.endswith(CheckNameInMessage)) |
66 | Message = Message.substr(0, Message.size() - CheckNameInMessage.size()); |
67 | |
68 | auto TidyMessage = |
69 | Loc.isValid() |
70 | ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc) |
71 | : tooling::DiagnosticMessage(Message); |
72 | |
73 | // Make sure that if a TokenRange is received from the check it is unfurled |
74 | // into a real CharRange for the diagnostic printer later. |
75 | // Whatever we store here gets decoupled from the current SourceManager, so |
76 | // we **have to** know the exact position and length of the highlight. |
77 | auto ToCharRange = [this, &Loc](const CharSourceRange &SourceRange) { |
78 | if (SourceRange.isCharRange()) |
79 | return SourceRange; |
80 | assert(SourceRange.isTokenRange()); |
81 | SourceLocation End = Lexer::getLocForEndOfToken( |
82 | SourceRange.getEnd(), 0, Loc.getManager(), LangOpts); |
83 | return CharSourceRange::getCharRange(SourceRange.getBegin(), End); |
84 | }; |
85 | |
86 | // We are only interested in valid ranges. |
87 | auto ValidRanges = |
88 | llvm::make_filter_range(Ranges, [](const CharSourceRange &R) { |
89 | return R.getAsRange().isValid(); |
90 | }); |
91 | |
92 | if (Level == DiagnosticsEngine::Note) { |
93 | Error.Notes.push_back(TidyMessage); |
94 | for (const CharSourceRange &SourceRange : ValidRanges) |
95 | Error.Notes.back().Ranges.emplace_back(Loc.getManager(), |
96 | ToCharRange(SourceRange)); |
97 | return; |
98 | } |
99 | assert(Error.Message.Message.empty() && "Overwriting a diagnostic message" ); |
100 | Error.Message = TidyMessage; |
101 | for (const CharSourceRange &SourceRange : ValidRanges) |
102 | Error.Message.Ranges.emplace_back(Loc.getManager(), |
103 | ToCharRange(SourceRange)); |
104 | } |
105 | |
106 | void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, |
107 | DiagnosticsEngine::Level Level, |
108 | ArrayRef<CharSourceRange> Ranges) override {} |
109 | |
110 | void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, |
111 | SmallVectorImpl<CharSourceRange> &Ranges, |
112 | ArrayRef<FixItHint> Hints) override { |
113 | assert(Loc.isValid()); |
114 | tooling::DiagnosticMessage *DiagWithFix = |
115 | Level == DiagnosticsEngine::Note ? &Error.Notes.back() : &Error.Message; |
116 | |
117 | for (const auto &FixIt : Hints) { |
118 | CharSourceRange Range = FixIt.RemoveRange; |
119 | assert(Range.getBegin().isValid() && Range.getEnd().isValid() && |
120 | "Invalid range in the fix-it hint." ); |
121 | assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() && |
122 | "Only file locations supported in fix-it hints." ); |
123 | |
124 | tooling::Replacement Replacement(Loc.getManager(), Range, |
125 | FixIt.CodeToInsert); |
126 | llvm::Error Err = |
127 | DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement); |
128 | // FIXME: better error handling (at least, don't let other replacements be |
129 | // applied). |
130 | if (Err) { |
131 | llvm::errs() << "Fix conflicts with existing fix! " |
132 | << llvm::toString(std::move(Err)) << "\n" ; |
133 | assert(false && "Fix conflicts with existing fix!" ); |
134 | } |
135 | } |
136 | } |
137 | |
138 | void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {} |
139 | |
140 | void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, |
141 | StringRef ModuleName) override {} |
142 | |
143 | void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc, |
144 | StringRef ModuleName) override {} |
145 | |
146 | void endDiagnostic(DiagOrStoredDiag D, |
147 | DiagnosticsEngine::Level Level) override { |
148 | assert(!Error.Message.Message.empty() && "Message has not been set" ); |
149 | } |
150 | |
151 | private: |
152 | ClangTidyError &Error; |
153 | }; |
154 | } // end anonymous namespace |
155 | |
156 | ClangTidyError::ClangTidyError(StringRef CheckName, |
157 | ClangTidyError::Level DiagLevel, |
158 | StringRef BuildDirectory, bool IsWarningAsError) |
159 | : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory), |
160 | IsWarningAsError(IsWarningAsError) {} |
161 | |
162 | ClangTidyContext::ClangTidyContext( |
163 | std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider, |
164 | bool AllowEnablingAnalyzerAlphaCheckers) |
165 | : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), |
166 | Profile(false), |
167 | AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers), |
168 | SelfContainedDiags(false) { |
169 | // Before the first translation unit we can get errors related to command-line |
170 | // parsing, use empty string for the file name in this case. |
171 | setCurrentFile("" ); |
172 | } |
173 | |
174 | ClangTidyContext::~ClangTidyContext() = default; |
175 | |
176 | DiagnosticBuilder ClangTidyContext::diag( |
177 | StringRef CheckName, SourceLocation Loc, StringRef Description, |
178 | DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { |
179 | assert(Loc.isValid()); |
180 | unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( |
181 | Level, (Description + " [" + CheckName + "]" ).str()); |
182 | CheckNamesByDiagnosticID.try_emplace(ID, CheckName); |
183 | return DiagEngine->Report(Loc, ID); |
184 | } |
185 | |
186 | DiagnosticBuilder ClangTidyContext::diag( |
187 | StringRef CheckName, StringRef Description, |
188 | DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { |
189 | unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( |
190 | Level, (Description + " [" + CheckName + "]" ).str()); |
191 | CheckNamesByDiagnosticID.try_emplace(ID, CheckName); |
192 | return DiagEngine->Report(ID); |
193 | } |
194 | |
195 | DiagnosticBuilder ClangTidyContext::diag(const tooling::Diagnostic &Error) { |
196 | SourceManager &SM = DiagEngine->getSourceManager(); |
197 | llvm::ErrorOr<const FileEntry *> File = |
198 | SM.getFileManager().getFile(Error.Message.FilePath); |
199 | FileID ID = SM.getOrCreateFileID(*File, SrcMgr::C_User); |
200 | SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID); |
201 | SourceLocation Loc = FileStartLoc.getLocWithOffset( |
202 | static_cast<SourceLocation::IntTy>(Error.Message.FileOffset)); |
203 | return diag(Error.DiagnosticName, Loc, Error.Message.Message, |
204 | static_cast<DiagnosticIDs::Level>(Error.DiagLevel)); |
205 | } |
206 | |
207 | DiagnosticBuilder ClangTidyContext::configurationDiag( |
208 | StringRef Message, |
209 | DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { |
210 | return diag("clang-tidy-config" , Message, Level); |
211 | } |
212 | |
213 | bool ClangTidyContext::shouldSuppressDiagnostic( |
214 | DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info, |
215 | SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO, |
216 | bool EnableNoLintBlocks) { |
217 | std::string CheckName = getCheckName(Info.getID()); |
218 | return NoLintHandler.shouldSuppress(DiagLevel, Info, CheckName, NoLintErrors, |
219 | AllowIO, EnableNoLintBlocks); |
220 | } |
221 | |
222 | void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) { |
223 | DiagEngine->setSourceManager(SourceMgr); |
224 | } |
225 | |
226 | static bool parseFileExtensions(llvm::ArrayRef<std::string> AllFileExtensions, |
227 | FileExtensionsSet &FileExtensions) { |
228 | FileExtensions.clear(); |
229 | for (StringRef Suffix : AllFileExtensions) { |
230 | StringRef Extension = Suffix.trim(); |
231 | if (!llvm::all_of(Extension, isAlphanumeric)) |
232 | return false; |
233 | FileExtensions.insert(Extension); |
234 | } |
235 | return true; |
236 | } |
237 | |
238 | void ClangTidyContext::setCurrentFile(StringRef File) { |
239 | CurrentFile = std::string(File); |
240 | CurrentOptions = getOptionsForFile(CurrentFile); |
241 | CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks); |
242 | WarningAsErrorFilter = |
243 | std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors); |
244 | if (!parseFileExtensions(*getOptions().HeaderFileExtensions, |
245 | HeaderFileExtensions)) |
246 | this->configurationDiag("Invalid header file extensions" ); |
247 | if (!parseFileExtensions(*getOptions().ImplementationFileExtensions, |
248 | ImplementationFileExtensions)) |
249 | this->configurationDiag("Invalid implementation file extensions" ); |
250 | } |
251 | |
252 | void ClangTidyContext::setASTContext(ASTContext *Context) { |
253 | DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); |
254 | LangOpts = Context->getLangOpts(); |
255 | } |
256 | |
257 | const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { |
258 | return OptionsProvider->getGlobalOptions(); |
259 | } |
260 | |
261 | const ClangTidyOptions &ClangTidyContext::getOptions() const { |
262 | return CurrentOptions; |
263 | } |
264 | |
265 | ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { |
266 | // Merge options on top of getDefaults() as a safeguard against options with |
267 | // unset values. |
268 | return ClangTidyOptions::getDefaults().merge( |
269 | OptionsProvider->getOptions(File), 0); |
270 | } |
271 | |
272 | void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; } |
273 | |
274 | void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) { |
275 | ProfilePrefix = std::string(Prefix); |
276 | } |
277 | |
278 | std::optional<ClangTidyProfiling::StorageParams> |
279 | ClangTidyContext::getProfileStorageParams() const { |
280 | if (ProfilePrefix.empty()) |
281 | return std::nullopt; |
282 | |
283 | return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile); |
284 | } |
285 | |
286 | bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const { |
287 | assert(CheckFilter != nullptr); |
288 | return CheckFilter->contains(CheckName); |
289 | } |
290 | |
291 | bool ClangTidyContext::treatAsError(StringRef CheckName) const { |
292 | assert(WarningAsErrorFilter != nullptr); |
293 | return WarningAsErrorFilter->contains(CheckName); |
294 | } |
295 | |
296 | std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const { |
297 | std::string ClangWarningOption = std::string( |
298 | DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID)); |
299 | if (!ClangWarningOption.empty()) |
300 | return "clang-diagnostic-" + ClangWarningOption; |
301 | llvm::DenseMap<unsigned, std::string>::const_iterator I = |
302 | CheckNamesByDiagnosticID.find(DiagnosticID); |
303 | if (I != CheckNamesByDiagnosticID.end()) |
304 | return I->second; |
305 | return "" ; |
306 | } |
307 | |
308 | ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer( |
309 | ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine, |
310 | bool RemoveIncompatibleErrors, bool GetFixesFromNotes, |
311 | bool EnableNolintBlocks) |
312 | : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine), |
313 | RemoveIncompatibleErrors(RemoveIncompatibleErrors), |
314 | GetFixesFromNotes(GetFixesFromNotes), |
315 | EnableNolintBlocks(EnableNolintBlocks), LastErrorRelatesToUserCode(false), |
316 | LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {} |
317 | |
318 | void ClangTidyDiagnosticConsumer::finalizeLastError() { |
319 | if (!Errors.empty()) { |
320 | ClangTidyError &Error = Errors.back(); |
321 | if (Error.DiagnosticName == "clang-tidy-config" ) { |
322 | // Never ignore these. |
323 | } else if (!Context.isCheckEnabled(Error.DiagnosticName) && |
324 | Error.DiagLevel != ClangTidyError::Error) { |
325 | ++Context.Stats.ErrorsIgnoredCheckFilter; |
326 | Errors.pop_back(); |
327 | } else if (!LastErrorRelatesToUserCode) { |
328 | ++Context.Stats.ErrorsIgnoredNonUserCode; |
329 | Errors.pop_back(); |
330 | } else if (!LastErrorPassesLineFilter) { |
331 | ++Context.Stats.ErrorsIgnoredLineFilter; |
332 | Errors.pop_back(); |
333 | } else { |
334 | ++Context.Stats.ErrorsDisplayed; |
335 | } |
336 | } |
337 | LastErrorRelatesToUserCode = false; |
338 | LastErrorPassesLineFilter = false; |
339 | } |
340 | |
341 | namespace clang::tidy { |
342 | |
343 | const llvm::StringMap<tooling::Replacements> * |
344 | getFixIt(const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) { |
345 | if (!Diagnostic.Message.Fix.empty()) |
346 | return &Diagnostic.Message.Fix; |
347 | if (!GetFixFromNotes) |
348 | return nullptr; |
349 | const llvm::StringMap<tooling::Replacements> *Result = nullptr; |
350 | for (const auto &Note : Diagnostic.Notes) { |
351 | if (!Note.Fix.empty()) { |
352 | if (Result) |
353 | // We have 2 different fixes in notes, bail out. |
354 | return nullptr; |
355 | Result = &Note.Fix; |
356 | } |
357 | } |
358 | return Result; |
359 | } |
360 | |
361 | } // namespace clang::tidy |
362 | |
363 | void ClangTidyDiagnosticConsumer::HandleDiagnostic( |
364 | DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { |
365 | if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) |
366 | return; |
367 | |
368 | SmallVector<tooling::Diagnostic, 1> SuppressionErrors; |
369 | if (Context.shouldSuppressDiagnostic(DiagLevel, Info, SuppressionErrors, |
370 | EnableNolintBlocks)) { |
371 | ++Context.Stats.ErrorsIgnoredNOLINT; |
372 | // Ignored a warning, should ignore related notes as well |
373 | LastErrorWasIgnored = true; |
374 | Context.DiagEngine->Clear(); |
375 | for (const auto &Error : SuppressionErrors) |
376 | Context.diag(Error); |
377 | return; |
378 | } |
379 | |
380 | LastErrorWasIgnored = false; |
381 | // Count warnings/errors. |
382 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); |
383 | |
384 | if (DiagLevel == DiagnosticsEngine::Note) { |
385 | assert(!Errors.empty() && |
386 | "A diagnostic note can only be appended to a message." ); |
387 | } else { |
388 | finalizeLastError(); |
389 | std::string CheckName = Context.getCheckName(Info.getID()); |
390 | if (CheckName.empty()) { |
391 | // This is a compiler diagnostic without a warning option. Assign check |
392 | // name based on its level. |
393 | switch (DiagLevel) { |
394 | case DiagnosticsEngine::Error: |
395 | case DiagnosticsEngine::Fatal: |
396 | CheckName = "clang-diagnostic-error" ; |
397 | break; |
398 | case DiagnosticsEngine::Warning: |
399 | CheckName = "clang-diagnostic-warning" ; |
400 | break; |
401 | case DiagnosticsEngine::Remark: |
402 | CheckName = "clang-diagnostic-remark" ; |
403 | break; |
404 | default: |
405 | CheckName = "clang-diagnostic-unknown" ; |
406 | break; |
407 | } |
408 | } |
409 | |
410 | ClangTidyError::Level Level = ClangTidyError::Warning; |
411 | if (DiagLevel == DiagnosticsEngine::Error || |
412 | DiagLevel == DiagnosticsEngine::Fatal) { |
413 | // Force reporting of Clang errors regardless of filters and non-user |
414 | // code. |
415 | Level = ClangTidyError::Error; |
416 | LastErrorRelatesToUserCode = true; |
417 | LastErrorPassesLineFilter = true; |
418 | } else if (DiagLevel == DiagnosticsEngine::Remark) { |
419 | Level = ClangTidyError::Remark; |
420 | } |
421 | |
422 | bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && |
423 | Context.treatAsError(CheckName); |
424 | Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(), |
425 | IsWarningAsError); |
426 | } |
427 | |
428 | if (ExternalDiagEngine) { |
429 | // If there is an external diagnostics engine, like in the |
430 | // ClangTidyPluginAction case, forward the diagnostics to it. |
431 | forwardDiagnostic(Info); |
432 | } else { |
433 | ClangTidyDiagnosticRenderer Converter( |
434 | Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), |
435 | Errors.back()); |
436 | SmallString<100> Message; |
437 | Info.FormatDiagnostic(Message); |
438 | FullSourceLoc Loc; |
439 | if (Info.hasSourceManager()) |
440 | Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager()); |
441 | else if (Context.DiagEngine->hasSourceManager()) |
442 | Loc = FullSourceLoc(Info.getLocation(), |
443 | Context.DiagEngine->getSourceManager()); |
444 | Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(), |
445 | Info.getFixItHints()); |
446 | } |
447 | |
448 | if (Info.hasSourceManager()) |
449 | checkFilters(Info.getLocation(), Info.getSourceManager()); |
450 | |
451 | Context.DiagEngine->Clear(); |
452 | for (const auto &Error : SuppressionErrors) |
453 | Context.diag(Error); |
454 | } |
455 | |
456 | bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, |
457 | unsigned LineNumber) const { |
458 | if (Context.getGlobalOptions().LineFilter.empty()) |
459 | return true; |
460 | for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { |
461 | if (FileName.endswith(Filter.Name)) { |
462 | if (Filter.LineRanges.empty()) |
463 | return true; |
464 | for (const FileFilter::LineRange &Range : Filter.LineRanges) { |
465 | if (Range.first <= LineNumber && LineNumber <= Range.second) |
466 | return true; |
467 | } |
468 | return false; |
469 | } |
470 | } |
471 | return false; |
472 | } |
473 | |
474 | void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) { |
475 | // Acquire a diagnostic ID also in the external diagnostics engine. |
476 | auto DiagLevelAndFormatString = |
477 | Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation()); |
478 | unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID( |
479 | DiagLevelAndFormatString.first, DiagLevelAndFormatString.second); |
480 | |
481 | // Forward the details. |
482 | auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID); |
483 | for (auto Hint : Info.getFixItHints()) |
484 | Builder << Hint; |
485 | for (auto Range : Info.getRanges()) |
486 | Builder << Range; |
487 | for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) { |
488 | DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index); |
489 | switch (Kind) { |
490 | case clang::DiagnosticsEngine::ak_std_string: |
491 | Builder << Info.getArgStdStr(Index); |
492 | break; |
493 | case clang::DiagnosticsEngine::ak_c_string: |
494 | Builder << Info.getArgCStr(Index); |
495 | break; |
496 | case clang::DiagnosticsEngine::ak_sint: |
497 | Builder << Info.getArgSInt(Index); |
498 | break; |
499 | case clang::DiagnosticsEngine::ak_uint: |
500 | Builder << Info.getArgUInt(Index); |
501 | break; |
502 | case clang::DiagnosticsEngine::ak_tokenkind: |
503 | Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index)); |
504 | break; |
505 | case clang::DiagnosticsEngine::ak_identifierinfo: |
506 | Builder << Info.getArgIdentifier(Index); |
507 | break; |
508 | case clang::DiagnosticsEngine::ak_qual: |
509 | Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index)); |
510 | break; |
511 | case clang::DiagnosticsEngine::ak_qualtype: |
512 | Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index)); |
513 | break; |
514 | case clang::DiagnosticsEngine::ak_declarationname: |
515 | Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index)); |
516 | break; |
517 | case clang::DiagnosticsEngine::ak_nameddecl: |
518 | Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index)); |
519 | break; |
520 | case clang::DiagnosticsEngine::ak_nestednamespec: |
521 | Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Index)); |
522 | break; |
523 | case clang::DiagnosticsEngine::ak_declcontext: |
524 | Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index)); |
525 | break; |
526 | case clang::DiagnosticsEngine::ak_qualtype_pair: |
527 | assert(false); // This one is not passed around. |
528 | break; |
529 | case clang::DiagnosticsEngine::ak_attr: |
530 | Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index)); |
531 | break; |
532 | case clang::DiagnosticsEngine::ak_addrspace: |
533 | Builder << static_cast<LangAS>(Info.getRawArg(Index)); |
534 | break; |
535 | } |
536 | } |
537 | } |
538 | |
539 | void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location, |
540 | const SourceManager &Sources) { |
541 | // Invalid location may mean a diagnostic in a command line, don't skip these. |
542 | if (!Location.isValid()) { |
543 | LastErrorRelatesToUserCode = true; |
544 | LastErrorPassesLineFilter = true; |
545 | return; |
546 | } |
547 | |
548 | if (!*Context.getOptions().SystemHeaders && |
549 | (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location))) |
550 | return; |
551 | |
552 | // FIXME: We start with a conservative approach here, but the actual type of |
553 | // location needed depends on the check (in particular, where this check wants |
554 | // to apply fixes). |
555 | FileID FID = Sources.getDecomposedExpansionLoc(Location).first; |
556 | const FileEntry *File = Sources.getFileEntryForID(FID); |
557 | |
558 | // -DMACRO definitions on the command line have locations in a virtual buffer |
559 | // that doesn't have a FileEntry. Don't skip these as well. |
560 | if (!File) { |
561 | LastErrorRelatesToUserCode = true; |
562 | LastErrorPassesLineFilter = true; |
563 | return; |
564 | } |
565 | |
566 | StringRef FileName(File->getName()); |
567 | LastErrorRelatesToUserCode = LastErrorRelatesToUserCode || |
568 | Sources.isInMainFile(Location) || |
569 | getHeaderFilter()->match(FileName); |
570 | |
571 | unsigned LineNumber = Sources.getExpansionLineNumber(Location); |
572 | LastErrorPassesLineFilter = |
573 | LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); |
574 | } |
575 | |
576 | llvm::Regex *ClangTidyDiagnosticConsumer::() { |
577 | if (!HeaderFilter) |
578 | HeaderFilter = |
579 | std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex); |
580 | return HeaderFilter.get(); |
581 | } |
582 | |
583 | void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() { |
584 | // Each error is modelled as the set of intervals in which it applies |
585 | // replacements. To detect overlapping replacements, we use a sweep line |
586 | // algorithm over these sets of intervals. |
587 | // An event here consists of the opening or closing of an interval. During the |
588 | // process, we maintain a counter with the amount of open intervals. If we |
589 | // find an endpoint of an interval and this counter is different from 0, it |
590 | // means that this interval overlaps with another one, so we set it as |
591 | // inapplicable. |
592 | struct Event { |
593 | // An event can be either the begin or the end of an interval. |
594 | enum EventType { |
595 | ET_Begin = 1, |
596 | ET_Insert = 0, |
597 | ET_End = -1, |
598 | }; |
599 | |
600 | Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, |
601 | unsigned ErrorSize) |
602 | : Type(Type), ErrorId(ErrorId) { |
603 | // The events are going to be sorted by their position. In case of draw: |
604 | // |
605 | // * If an interval ends at the same position at which other interval |
606 | // begins, this is not an overlapping, so we want to remove the ending |
607 | // interval before adding the starting one: end events have higher |
608 | // priority than begin events. |
609 | // |
610 | // * If we have several begin points at the same position, we will mark as |
611 | // inapplicable the ones that we process later, so the first one has to |
612 | // be the one with the latest end point, because this one will contain |
613 | // all the other intervals. For the same reason, if we have several end |
614 | // points in the same position, the last one has to be the one with the |
615 | // earliest begin point. In both cases, we sort non-increasingly by the |
616 | // position of the complementary. |
617 | // |
618 | // * In case of two equal intervals, the one whose error is bigger can |
619 | // potentially contain the other one, so we want to process its begin |
620 | // points before and its end points later. |
621 | // |
622 | // * Finally, if we have two equal intervals whose errors have the same |
623 | // size, none of them will be strictly contained inside the other. |
624 | // Sorting by ErrorId will guarantee that the begin point of the first |
625 | // one will be processed before, disallowing the second one, and the |
626 | // end point of the first one will also be processed before, |
627 | // disallowing the first one. |
628 | switch (Type) { |
629 | case ET_Begin: |
630 | Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId); |
631 | break; |
632 | case ET_Insert: |
633 | Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId); |
634 | break; |
635 | case ET_End: |
636 | Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId); |
637 | break; |
638 | } |
639 | } |
640 | |
641 | bool operator<(const Event &Other) const { |
642 | return Priority < Other.Priority; |
643 | } |
644 | |
645 | // Determines if this event is the begin or the end of an interval. |
646 | EventType Type; |
647 | // The index of the error to which the interval that generated this event |
648 | // belongs. |
649 | unsigned ErrorId; |
650 | // The events will be sorted based on this field. |
651 | std::tuple<unsigned, EventType, int, int, unsigned> Priority; |
652 | }; |
653 | |
654 | removeDuplicatedDiagnosticsOfAliasCheckers(); |
655 | |
656 | // Compute error sizes. |
657 | std::vector<int> Sizes; |
658 | std::vector< |
659 | std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>> |
660 | ErrorFixes; |
661 | for (auto &Error : Errors) { |
662 | if (const auto *Fix = getFixIt(Error, GetFixesFromNotes)) |
663 | ErrorFixes.emplace_back( |
664 | &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix)); |
665 | } |
666 | for (const auto &ErrorAndFix : ErrorFixes) { |
667 | int Size = 0; |
668 | for (const auto &FileAndReplaces : *ErrorAndFix.second) { |
669 | for (const auto &Replace : FileAndReplaces.second) |
670 | Size += Replace.getLength(); |
671 | } |
672 | Sizes.push_back(Size); |
673 | } |
674 | |
675 | // Build events from error intervals. |
676 | llvm::StringMap<std::vector<Event>> FileEvents; |
677 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
678 | for (const auto &FileAndReplace : *ErrorFixes[I].second) { |
679 | for (const auto &Replace : FileAndReplace.second) { |
680 | unsigned Begin = Replace.getOffset(); |
681 | unsigned End = Begin + Replace.getLength(); |
682 | auto &Events = FileEvents[Replace.getFilePath()]; |
683 | if (Begin == End) { |
684 | Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]); |
685 | } else { |
686 | Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]); |
687 | Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]); |
688 | } |
689 | } |
690 | } |
691 | } |
692 | |
693 | llvm::BitVector Apply(ErrorFixes.size(), true); |
694 | for (auto &FileAndEvents : FileEvents) { |
695 | std::vector<Event> &Events = FileAndEvents.second; |
696 | // Sweep. |
697 | llvm::sort(Events); |
698 | int OpenIntervals = 0; |
699 | for (const auto &Event : Events) { |
700 | switch (Event.Type) { |
701 | case Event::ET_Begin: |
702 | if (OpenIntervals++ != 0) |
703 | Apply[Event.ErrorId] = false; |
704 | break; |
705 | case Event::ET_Insert: |
706 | if (OpenIntervals != 0) |
707 | Apply[Event.ErrorId] = false; |
708 | break; |
709 | case Event::ET_End: |
710 | if (--OpenIntervals != 0) |
711 | Apply[Event.ErrorId] = false; |
712 | break; |
713 | } |
714 | } |
715 | assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match" ); |
716 | } |
717 | |
718 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
719 | if (!Apply[I]) { |
720 | ErrorFixes[I].second->clear(); |
721 | ErrorFixes[I].first->Notes.emplace_back( |
722 | "this fix will not be applied because it overlaps with another fix" ); |
723 | } |
724 | } |
725 | } |
726 | |
727 | namespace { |
728 | struct LessClangTidyError { |
729 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
730 | const tooling::DiagnosticMessage &M1 = LHS.Message; |
731 | const tooling::DiagnosticMessage &M2 = RHS.Message; |
732 | |
733 | return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName, |
734 | M1.Message) < |
735 | std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message); |
736 | } |
737 | }; |
738 | struct EqualClangTidyError { |
739 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
740 | LessClangTidyError Less; |
741 | return !Less(LHS, RHS) && !Less(RHS, LHS); |
742 | } |
743 | }; |
744 | } // end anonymous namespace |
745 | |
746 | std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() { |
747 | finalizeLastError(); |
748 | |
749 | llvm::stable_sort(Errors, LessClangTidyError()); |
750 | Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), |
751 | Errors.end()); |
752 | if (RemoveIncompatibleErrors) |
753 | removeIncompatibleErrors(); |
754 | return std::move(Errors); |
755 | } |
756 | |
757 | namespace { |
758 | struct LessClangTidyErrorWithoutDiagnosticName { |
759 | bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const { |
760 | const tooling::DiagnosticMessage &M1 = LHS->Message; |
761 | const tooling::DiagnosticMessage &M2 = RHS->Message; |
762 | |
763 | return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < |
764 | std::tie(M2.FilePath, M2.FileOffset, M2.Message); |
765 | } |
766 | }; |
767 | } // end anonymous namespace |
768 | |
769 | void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() { |
770 | using UniqueErrorSet = |
771 | std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>; |
772 | UniqueErrorSet UniqueErrors; |
773 | |
774 | auto IT = Errors.begin(); |
775 | while (IT != Errors.end()) { |
776 | ClangTidyError &Error = *IT; |
777 | std::pair<UniqueErrorSet::iterator, bool> Inserted = |
778 | UniqueErrors.insert(&Error); |
779 | |
780 | // Unique error, we keep it and move along. |
781 | if (Inserted.second) { |
782 | ++IT; |
783 | } else { |
784 | ClangTidyError &ExistingError = **Inserted.first; |
785 | const llvm::StringMap<tooling::Replacements> &CandidateFix = |
786 | Error.Message.Fix; |
787 | const llvm::StringMap<tooling::Replacements> &ExistingFix = |
788 | (*Inserted.first)->Message.Fix; |
789 | |
790 | if (CandidateFix != ExistingFix) { |
791 | |
792 | // In case of a conflict, don't suggest any fix-it. |
793 | ExistingError.Message.Fix.clear(); |
794 | ExistingError.Notes.emplace_back( |
795 | llvm::formatv("cannot apply fix-it because an alias checker has " |
796 | "suggested a different fix-it; please remove one of " |
797 | "the checkers ('{0}', '{1}') or " |
798 | "ensure they are both configured the same" , |
799 | ExistingError.DiagnosticName, Error.DiagnosticName) |
800 | .str()); |
801 | } |
802 | |
803 | if (Error.IsWarningAsError) |
804 | ExistingError.IsWarningAsError = true; |
805 | |
806 | // Since it is the same error, we should take it as alias and remove it. |
807 | ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName); |
808 | IT = Errors.erase(IT); |
809 | } |
810 | } |
811 | } |
812 | |