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