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 | Level, Message: (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 | Level, Message: (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 | if (Context.getOptions().HeaderFilterRegex && |
317 | !Context.getOptions().HeaderFilterRegex->empty()) |
318 | HeaderFilter = |
319 | std::make_unique<llvm::Regex>(args: *Context.getOptions().HeaderFilterRegex); |
320 | |
321 | if (Context.getOptions().ExcludeHeaderFilterRegex && |
322 | !Context.getOptions().ExcludeHeaderFilterRegex->empty()) |
323 | ExcludeHeaderFilter = std::make_unique<llvm::Regex>( |
324 | args: *Context.getOptions().ExcludeHeaderFilterRegex); |
325 | } |
326 | |
327 | void ClangTidyDiagnosticConsumer::finalizeLastError() { |
328 | if (!Errors.empty()) { |
329 | ClangTidyError &Error = Errors.back(); |
330 | if (Error.DiagnosticName == "clang-tidy-config" ) { |
331 | // Never ignore these. |
332 | } else if (!Context.isCheckEnabled(CheckName: Error.DiagnosticName) && |
333 | Error.DiagLevel != ClangTidyError::Error) { |
334 | ++Context.Stats.ErrorsIgnoredCheckFilter; |
335 | Errors.pop_back(); |
336 | } else if (!LastErrorRelatesToUserCode) { |
337 | ++Context.Stats.ErrorsIgnoredNonUserCode; |
338 | Errors.pop_back(); |
339 | } else if (!LastErrorPassesLineFilter) { |
340 | ++Context.Stats.ErrorsIgnoredLineFilter; |
341 | Errors.pop_back(); |
342 | } else { |
343 | ++Context.Stats.ErrorsDisplayed; |
344 | } |
345 | } |
346 | LastErrorRelatesToUserCode = false; |
347 | LastErrorPassesLineFilter = false; |
348 | } |
349 | |
350 | namespace clang::tidy { |
351 | |
352 | const llvm::StringMap<tooling::Replacements> * |
353 | getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) { |
354 | if (!Diagnostic.Message.Fix.empty()) |
355 | return &Diagnostic.Message.Fix; |
356 | if (!AnyFix) |
357 | return nullptr; |
358 | const llvm::StringMap<tooling::Replacements> *Result = nullptr; |
359 | for (const auto &Note : Diagnostic.Notes) { |
360 | if (!Note.Fix.empty()) { |
361 | if (Result) |
362 | // We have 2 different fixes in notes, bail out. |
363 | return nullptr; |
364 | Result = &Note.Fix; |
365 | } |
366 | } |
367 | return Result; |
368 | } |
369 | |
370 | } // namespace clang::tidy |
371 | |
372 | void ClangTidyDiagnosticConsumer::HandleDiagnostic( |
373 | DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { |
374 | if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) |
375 | return; |
376 | |
377 | SmallVector<tooling::Diagnostic, 1> SuppressionErrors; |
378 | if (Context.shouldSuppressDiagnostic(DiagLevel, Info, NoLintErrors&: SuppressionErrors, |
379 | AllowIO: EnableNolintBlocks)) { |
380 | ++Context.Stats.ErrorsIgnoredNOLINT; |
381 | // Ignored a warning, should ignore related notes as well |
382 | LastErrorWasIgnored = true; |
383 | for (const auto &Error : SuppressionErrors) |
384 | Context.diag(Error); |
385 | return; |
386 | } |
387 | |
388 | LastErrorWasIgnored = false; |
389 | // Count warnings/errors. |
390 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); |
391 | |
392 | if (DiagLevel == DiagnosticsEngine::Note) { |
393 | assert(!Errors.empty() && |
394 | "A diagnostic note can only be appended to a message." ); |
395 | } else { |
396 | finalizeLastError(); |
397 | std::string CheckName = Context.getCheckName(DiagnosticID: Info.getID()); |
398 | if (CheckName.empty()) { |
399 | // This is a compiler diagnostic without a warning option. Assign check |
400 | // name based on its level. |
401 | switch (DiagLevel) { |
402 | case DiagnosticsEngine::Error: |
403 | case DiagnosticsEngine::Fatal: |
404 | CheckName = "clang-diagnostic-error" ; |
405 | break; |
406 | case DiagnosticsEngine::Warning: |
407 | CheckName = "clang-diagnostic-warning" ; |
408 | break; |
409 | case DiagnosticsEngine::Remark: |
410 | CheckName = "clang-diagnostic-remark" ; |
411 | break; |
412 | default: |
413 | CheckName = "clang-diagnostic-unknown" ; |
414 | break; |
415 | } |
416 | } |
417 | |
418 | ClangTidyError::Level Level = ClangTidyError::Warning; |
419 | if (DiagLevel == DiagnosticsEngine::Error || |
420 | DiagLevel == DiagnosticsEngine::Fatal) { |
421 | // Force reporting of Clang errors regardless of filters and non-user |
422 | // code. |
423 | Level = ClangTidyError::Error; |
424 | LastErrorRelatesToUserCode = true; |
425 | LastErrorPassesLineFilter = true; |
426 | } else if (DiagLevel == DiagnosticsEngine::Remark) { |
427 | Level = ClangTidyError::Remark; |
428 | } |
429 | |
430 | bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && |
431 | Context.treatAsError(CheckName); |
432 | Errors.emplace_back(args&: CheckName, args&: Level, args: Context.getCurrentBuildDirectory(), |
433 | args&: IsWarningAsError); |
434 | } |
435 | |
436 | if (ExternalDiagEngine) { |
437 | // If there is an external diagnostics engine, like in the |
438 | // ClangTidyPluginAction case, forward the diagnostics to it. |
439 | forwardDiagnostic(Info); |
440 | } else { |
441 | ClangTidyDiagnosticRenderer Converter( |
442 | Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), |
443 | Errors.back()); |
444 | SmallString<100> Message; |
445 | Info.FormatDiagnostic(OutStr&: Message); |
446 | FullSourceLoc Loc; |
447 | if (Info.hasSourceManager()) |
448 | Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager()); |
449 | else if (Context.DiagEngine->hasSourceManager()) |
450 | Loc = FullSourceLoc(Info.getLocation(), |
451 | Context.DiagEngine->getSourceManager()); |
452 | Converter.emitDiagnostic(Loc, Level: DiagLevel, Message, Ranges: Info.getRanges(), |
453 | FixItHints: Info.getFixItHints()); |
454 | } |
455 | |
456 | if (Info.hasSourceManager()) |
457 | checkFilters(Location: Info.getLocation(), Sources: Info.getSourceManager()); |
458 | |
459 | for (const auto &Error : SuppressionErrors) |
460 | Context.diag(Error); |
461 | } |
462 | |
463 | bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, |
464 | unsigned LineNumber) const { |
465 | if (Context.getGlobalOptions().LineFilter.empty()) |
466 | return true; |
467 | for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { |
468 | if (FileName.ends_with(Suffix: Filter.Name)) { |
469 | if (Filter.LineRanges.empty()) |
470 | return true; |
471 | for (const FileFilter::LineRange &Range : Filter.LineRanges) { |
472 | if (Range.first <= LineNumber && LineNumber <= Range.second) |
473 | return true; |
474 | } |
475 | return false; |
476 | } |
477 | } |
478 | return false; |
479 | } |
480 | |
481 | void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) { |
482 | // Acquire a diagnostic ID also in the external diagnostics engine. |
483 | auto DiagLevelAndFormatString = |
484 | Context.getDiagLevelAndFormatString(DiagnosticID: Info.getID(), Loc: Info.getLocation()); |
485 | unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID( |
486 | Level: DiagLevelAndFormatString.first, Message: DiagLevelAndFormatString.second); |
487 | |
488 | // Forward the details. |
489 | auto Builder = ExternalDiagEngine->Report(Loc: Info.getLocation(), DiagID: ExternalID); |
490 | for (const FixItHint &Hint : Info.getFixItHints()) |
491 | Builder << Hint; |
492 | for (auto Range : Info.getRanges()) |
493 | Builder << Range; |
494 | for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) { |
495 | DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Idx: Index); |
496 | switch (Kind) { |
497 | case clang::DiagnosticsEngine::ak_std_string: |
498 | Builder << Info.getArgStdStr(Idx: Index); |
499 | break; |
500 | case clang::DiagnosticsEngine::ak_c_string: |
501 | Builder << Info.getArgCStr(Idx: Index); |
502 | break; |
503 | case clang::DiagnosticsEngine::ak_sint: |
504 | Builder << Info.getArgSInt(Idx: Index); |
505 | break; |
506 | case clang::DiagnosticsEngine::ak_uint: |
507 | Builder << Info.getArgUInt(Idx: Index); |
508 | break; |
509 | case clang::DiagnosticsEngine::ak_tokenkind: |
510 | Builder << static_cast<tok::TokenKind>(Info.getRawArg(Idx: Index)); |
511 | break; |
512 | case clang::DiagnosticsEngine::ak_identifierinfo: |
513 | Builder << Info.getArgIdentifier(Idx: Index); |
514 | break; |
515 | case clang::DiagnosticsEngine::ak_qual: |
516 | Builder << Qualifiers::fromOpaqueValue(opaque: Info.getRawArg(Idx: Index)); |
517 | break; |
518 | case clang::DiagnosticsEngine::ak_qualtype: |
519 | Builder << QualType::getFromOpaquePtr(Ptr: (void *)Info.getRawArg(Idx: Index)); |
520 | break; |
521 | case clang::DiagnosticsEngine::ak_declarationname: |
522 | Builder << DeclarationName::getFromOpaqueInteger(P: Info.getRawArg(Idx: Index)); |
523 | break; |
524 | case clang::DiagnosticsEngine::ak_nameddecl: |
525 | Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Idx: Index)); |
526 | break; |
527 | case clang::DiagnosticsEngine::ak_nestednamespec: |
528 | Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Idx: Index)); |
529 | break; |
530 | case clang::DiagnosticsEngine::ak_declcontext: |
531 | Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Idx: Index)); |
532 | break; |
533 | case clang::DiagnosticsEngine::ak_qualtype_pair: |
534 | assert(false); // This one is not passed around. |
535 | break; |
536 | case clang::DiagnosticsEngine::ak_attr: |
537 | Builder << reinterpret_cast<Attr *>(Info.getRawArg(Idx: Index)); |
538 | break; |
539 | case clang::DiagnosticsEngine::ak_addrspace: |
540 | Builder << static_cast<LangAS>(Info.getRawArg(Idx: Index)); |
541 | break; |
542 | } |
543 | } |
544 | } |
545 | |
546 | void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location, |
547 | const SourceManager &Sources) { |
548 | // Invalid location may mean a diagnostic in a command line, don't skip these. |
549 | if (!Location.isValid()) { |
550 | LastErrorRelatesToUserCode = true; |
551 | LastErrorPassesLineFilter = true; |
552 | return; |
553 | } |
554 | |
555 | if (!*Context.getOptions().SystemHeaders && |
556 | (Sources.isInSystemHeader(Loc: Location) || Sources.isInSystemMacro(loc: Location))) |
557 | return; |
558 | |
559 | // FIXME: We start with a conservative approach here, but the actual type of |
560 | // location needed depends on the check (in particular, where this check wants |
561 | // to apply fixes). |
562 | FileID FID = Sources.getDecomposedExpansionLoc(Loc: Location).first; |
563 | OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID); |
564 | |
565 | // -DMACRO definitions on the command line have locations in a virtual buffer |
566 | // that doesn't have a FileEntry. Don't skip these as well. |
567 | if (!File) { |
568 | LastErrorRelatesToUserCode = true; |
569 | LastErrorPassesLineFilter = true; |
570 | return; |
571 | } |
572 | |
573 | StringRef FileName(File->getName()); |
574 | LastErrorRelatesToUserCode = |
575 | LastErrorRelatesToUserCode || Sources.isInMainFile(Loc: Location) || |
576 | (HeaderFilter && |
577 | (HeaderFilter->match(String: FileName) && |
578 | !(ExcludeHeaderFilter && ExcludeHeaderFilter->match(String: FileName)))); |
579 | |
580 | unsigned LineNumber = Sources.getExpansionLineNumber(Loc: Location); |
581 | LastErrorPassesLineFilter = |
582 | LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); |
583 | } |
584 | |
585 | void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() { |
586 | // Each error is modelled as the set of intervals in which it applies |
587 | // replacements. To detect overlapping replacements, we use a sweep line |
588 | // algorithm over these sets of intervals. |
589 | // An event here consists of the opening or closing of an interval. During the |
590 | // process, we maintain a counter with the amount of open intervals. If we |
591 | // find an endpoint of an interval and this counter is different from 0, it |
592 | // means that this interval overlaps with another one, so we set it as |
593 | // inapplicable. |
594 | struct Event { |
595 | // An event can be either the begin or the end of an interval. |
596 | enum EventType { |
597 | ET_Begin = 1, |
598 | ET_Insert = 0, |
599 | ET_End = -1, |
600 | }; |
601 | |
602 | Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, |
603 | unsigned ErrorSize) |
604 | : Type(Type), ErrorId(ErrorId) { |
605 | // The events are going to be sorted by their position. In case of draw: |
606 | // |
607 | // * If an interval ends at the same position at which other interval |
608 | // begins, this is not an overlapping, so we want to remove the ending |
609 | // interval before adding the starting one: end events have higher |
610 | // priority than begin events. |
611 | // |
612 | // * If we have several begin points at the same position, we will mark as |
613 | // inapplicable the ones that we process later, so the first one has to |
614 | // be the one with the latest end point, because this one will contain |
615 | // all the other intervals. For the same reason, if we have several end |
616 | // points in the same position, the last one has to be the one with the |
617 | // earliest begin point. In both cases, we sort non-increasingly by the |
618 | // position of the complementary. |
619 | // |
620 | // * In case of two equal intervals, the one whose error is bigger can |
621 | // potentially contain the other one, so we want to process its begin |
622 | // points before and its end points later. |
623 | // |
624 | // * Finally, if we have two equal intervals whose errors have the same |
625 | // size, none of them will be strictly contained inside the other. |
626 | // Sorting by ErrorId will guarantee that the begin point of the first |
627 | // one will be processed before, disallowing the second one, and the |
628 | // end point of the first one will also be processed before, |
629 | // disallowing the first one. |
630 | switch (Type) { |
631 | case ET_Begin: |
632 | Priority = std::make_tuple(args&: Begin, args&: Type, args: -End, args: -ErrorSize, args&: ErrorId); |
633 | break; |
634 | case ET_Insert: |
635 | Priority = std::make_tuple(args&: Begin, args&: Type, args: -End, args&: ErrorSize, args&: ErrorId); |
636 | break; |
637 | case ET_End: |
638 | Priority = std::make_tuple(args&: End, args&: Type, args: -Begin, args&: ErrorSize, args&: ErrorId); |
639 | break; |
640 | } |
641 | } |
642 | |
643 | bool operator<(const Event &Other) const { |
644 | return Priority < Other.Priority; |
645 | } |
646 | |
647 | // Determines if this event is the begin or the end of an interval. |
648 | EventType Type; |
649 | // The index of the error to which the interval that generated this event |
650 | // belongs. |
651 | unsigned ErrorId; |
652 | // The events will be sorted based on this field. |
653 | std::tuple<unsigned, EventType, int, int, unsigned> Priority; |
654 | }; |
655 | |
656 | removeDuplicatedDiagnosticsOfAliasCheckers(); |
657 | |
658 | // Compute error sizes. |
659 | std::vector<int> Sizes; |
660 | std::vector< |
661 | std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>> |
662 | ErrorFixes; |
663 | for (auto &Error : Errors) { |
664 | if (const auto *Fix = getFixIt(Diagnostic: Error, AnyFix: GetFixesFromNotes)) |
665 | ErrorFixes.emplace_back( |
666 | args: &Error, args: const_cast<llvm::StringMap<tooling::Replacements> *>(Fix)); |
667 | } |
668 | for (const auto &ErrorAndFix : ErrorFixes) { |
669 | int Size = 0; |
670 | for (const auto &FileAndReplaces : *ErrorAndFix.second) { |
671 | for (const auto &Replace : FileAndReplaces.second) |
672 | Size += Replace.getLength(); |
673 | } |
674 | Sizes.push_back(x: Size); |
675 | } |
676 | |
677 | // Build events from error intervals. |
678 | llvm::StringMap<std::vector<Event>> FileEvents; |
679 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
680 | for (const auto &FileAndReplace : *ErrorFixes[I].second) { |
681 | for (const auto &Replace : FileAndReplace.second) { |
682 | unsigned Begin = Replace.getOffset(); |
683 | unsigned End = Begin + Replace.getLength(); |
684 | auto &Events = FileEvents[Replace.getFilePath()]; |
685 | if (Begin == End) { |
686 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_Insert, args&: I, args&: Sizes[I]); |
687 | } else { |
688 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_Begin, args&: I, args&: Sizes[I]); |
689 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_End, args&: I, args&: Sizes[I]); |
690 | } |
691 | } |
692 | } |
693 | } |
694 | |
695 | llvm::BitVector Apply(ErrorFixes.size(), true); |
696 | for (auto &FileAndEvents : FileEvents) { |
697 | std::vector<Event> &Events = FileAndEvents.second; |
698 | // Sweep. |
699 | llvm::sort(C&: Events); |
700 | int OpenIntervals = 0; |
701 | for (const auto &Event : Events) { |
702 | switch (Event.Type) { |
703 | case Event::ET_Begin: |
704 | if (OpenIntervals++ != 0) |
705 | Apply[Event.ErrorId] = false; |
706 | break; |
707 | case Event::ET_Insert: |
708 | if (OpenIntervals != 0) |
709 | Apply[Event.ErrorId] = false; |
710 | break; |
711 | case Event::ET_End: |
712 | if (--OpenIntervals != 0) |
713 | Apply[Event.ErrorId] = false; |
714 | break; |
715 | } |
716 | } |
717 | assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match" ); |
718 | } |
719 | |
720 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
721 | if (!Apply[I]) { |
722 | ErrorFixes[I].second->clear(); |
723 | ErrorFixes[I].first->Notes.emplace_back( |
724 | Args: "this fix will not be applied because it overlaps with another fix" ); |
725 | } |
726 | } |
727 | } |
728 | |
729 | namespace { |
730 | struct LessClangTidyError { |
731 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
732 | const tooling::DiagnosticMessage &M1 = LHS.Message; |
733 | const tooling::DiagnosticMessage &M2 = RHS.Message; |
734 | |
735 | return std::tie(args: M1.FilePath, args: M1.FileOffset, args: LHS.DiagnosticName, |
736 | args: M1.Message) < |
737 | std::tie(args: M2.FilePath, args: M2.FileOffset, args: RHS.DiagnosticName, args: M2.Message); |
738 | } |
739 | }; |
740 | struct EqualClangTidyError { |
741 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
742 | LessClangTidyError Less; |
743 | return !Less(LHS, RHS) && !Less(RHS, LHS); |
744 | } |
745 | }; |
746 | } // end anonymous namespace |
747 | |
748 | std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() { |
749 | finalizeLastError(); |
750 | |
751 | llvm::stable_sort(Range&: Errors, C: LessClangTidyError()); |
752 | Errors.erase(first: std::unique(first: Errors.begin(), last: Errors.end(), binary_pred: EqualClangTidyError()), |
753 | last: Errors.end()); |
754 | if (RemoveIncompatibleErrors) |
755 | removeIncompatibleErrors(); |
756 | return std::move(Errors); |
757 | } |
758 | |
759 | namespace { |
760 | struct LessClangTidyErrorWithoutDiagnosticName { |
761 | bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const { |
762 | const tooling::DiagnosticMessage &M1 = LHS->Message; |
763 | const tooling::DiagnosticMessage &M2 = RHS->Message; |
764 | |
765 | return std::tie(args: M1.FilePath, args: M1.FileOffset, args: M1.Message) < |
766 | std::tie(args: M2.FilePath, args: M2.FileOffset, args: M2.Message); |
767 | } |
768 | }; |
769 | } // end anonymous namespace |
770 | |
771 | void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() { |
772 | using UniqueErrorSet = |
773 | std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>; |
774 | UniqueErrorSet UniqueErrors; |
775 | |
776 | auto IT = Errors.begin(); |
777 | while (IT != Errors.end()) { |
778 | ClangTidyError &Error = *IT; |
779 | std::pair<UniqueErrorSet::iterator, bool> Inserted = |
780 | UniqueErrors.insert(x: &Error); |
781 | |
782 | // Unique error, we keep it and move along. |
783 | if (Inserted.second) { |
784 | ++IT; |
785 | } else { |
786 | ClangTidyError &ExistingError = **Inserted.first; |
787 | const llvm::StringMap<tooling::Replacements> &CandidateFix = |
788 | Error.Message.Fix; |
789 | const llvm::StringMap<tooling::Replacements> &ExistingFix = |
790 | (*Inserted.first)->Message.Fix; |
791 | |
792 | if (CandidateFix != ExistingFix) { |
793 | |
794 | // In case of a conflict, don't suggest any fix-it. |
795 | ExistingError.Message.Fix.clear(); |
796 | ExistingError.Notes.emplace_back( |
797 | Args: llvm::formatv(Fmt: "cannot apply fix-it because an alias checker has " |
798 | "suggested a different fix-it; please remove one of " |
799 | "the checkers ('{0}', '{1}') or " |
800 | "ensure they are both configured the same" , |
801 | Vals&: ExistingError.DiagnosticName, Vals&: Error.DiagnosticName) |
802 | .str()); |
803 | } |
804 | |
805 | if (Error.IsWarningAsError) |
806 | ExistingError.IsWarningAsError = true; |
807 | |
808 | // Since it is the same error, we should take it as alias and remove it. |
809 | ExistingError.EnabledDiagnosticAliases.emplace_back(args&: Error.DiagnosticName); |
810 | IT = Errors.erase(position: IT); |
811 | } |
812 | } |
813 | } |
814 | |