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/AST/Expr.h" |
26 | #include "clang/Basic/CharInfo.h" |
27 | #include "clang/Basic/Diagnostic.h" |
28 | #include "clang/Basic/DiagnosticOptions.h" |
29 | #include "clang/Basic/FileManager.h" |
30 | #include "clang/Basic/SourceManager.h" |
31 | #include "clang/Frontend/DiagnosticRenderer.h" |
32 | #include "clang/Lex/Lexer.h" |
33 | #include "clang/Tooling/Core/Diagnostic.h" |
34 | #include "clang/Tooling/Core/Replacement.h" |
35 | #include "llvm/ADT/BitVector.h" |
36 | #include "llvm/ADT/STLExtras.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 EnableModuleHeadersParsing) |
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 dummy string for the file name in this case. |
170 | setCurrentFile("dummy"); |
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 | 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 | for (const auto &Error : SuppressionErrors) |
373 | Context.diag(Error); |
374 | return; |
375 | } |
376 | |
377 | LastErrorWasIgnored = false; |
378 | // Count warnings/errors. |
379 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); |
380 | |
381 | if (DiagLevel == DiagnosticsEngine::Note) { |
382 | assert(!Errors.empty() && |
383 | "A diagnostic note can only be appended to a message."); |
384 | } else { |
385 | finalizeLastError(); |
386 | std::string CheckName = Context.getCheckName(DiagnosticID: Info.getID()); |
387 | if (CheckName.empty()) { |
388 | // This is a compiler diagnostic without a warning option. Assign check |
389 | // name based on its level. |
390 | switch (DiagLevel) { |
391 | case DiagnosticsEngine::Error: |
392 | case DiagnosticsEngine::Fatal: |
393 | CheckName = "clang-diagnostic-error"; |
394 | break; |
395 | case DiagnosticsEngine::Warning: |
396 | CheckName = "clang-diagnostic-warning"; |
397 | break; |
398 | case DiagnosticsEngine::Remark: |
399 | CheckName = "clang-diagnostic-remark"; |
400 | break; |
401 | default: |
402 | CheckName = "clang-diagnostic-unknown"; |
403 | break; |
404 | } |
405 | } |
406 | |
407 | ClangTidyError::Level Level = ClangTidyError::Warning; |
408 | if (DiagLevel == DiagnosticsEngine::Error || |
409 | DiagLevel == DiagnosticsEngine::Fatal) { |
410 | // Force reporting of Clang errors regardless of filters and non-user |
411 | // code. |
412 | Level = ClangTidyError::Error; |
413 | LastErrorRelatesToUserCode = true; |
414 | LastErrorPassesLineFilter = true; |
415 | } else if (DiagLevel == DiagnosticsEngine::Remark) { |
416 | Level = ClangTidyError::Remark; |
417 | } |
418 | |
419 | bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning && |
420 | Context.treatAsError(CheckName); |
421 | Errors.emplace_back(args&: CheckName, args&: Level, args: Context.getCurrentBuildDirectory(), |
422 | args&: IsWarningAsError); |
423 | } |
424 | |
425 | if (ExternalDiagEngine) { |
426 | // If there is an external diagnostics engine, like in the |
427 | // ClangTidyPluginAction case, forward the diagnostics to it. |
428 | forwardDiagnostic(Info); |
429 | } else { |
430 | ClangTidyDiagnosticRenderer Converter( |
431 | Context.getLangOpts(), Context.DiagEngine->getDiagnosticOptions(), |
432 | Errors.back()); |
433 | SmallString<100> Message; |
434 | Info.FormatDiagnostic(OutStr&: Message); |
435 | FullSourceLoc Loc; |
436 | if (Info.hasSourceManager()) |
437 | Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager()); |
438 | else if (Context.DiagEngine->hasSourceManager()) |
439 | Loc = FullSourceLoc(Info.getLocation(), |
440 | Context.DiagEngine->getSourceManager()); |
441 | Converter.emitDiagnostic(Loc, Level: DiagLevel, Message, Ranges: Info.getRanges(), |
442 | FixItHints: Info.getFixItHints()); |
443 | } |
444 | |
445 | if (Info.hasSourceManager()) |
446 | checkFilters(Location: Info.getLocation(), Sources: Info.getSourceManager()); |
447 | |
448 | for (const auto &Error : SuppressionErrors) |
449 | Context.diag(Error); |
450 | } |
451 | |
452 | bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, |
453 | unsigned LineNumber) const { |
454 | if (Context.getGlobalOptions().LineFilter.empty()) |
455 | return true; |
456 | for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { |
457 | if (FileName.ends_with(Suffix: Filter.Name)) { |
458 | if (Filter.LineRanges.empty()) |
459 | return true; |
460 | for (const FileFilter::LineRange &Range : Filter.LineRanges) { |
461 | if (Range.first <= LineNumber && LineNumber <= Range.second) |
462 | return true; |
463 | } |
464 | return false; |
465 | } |
466 | } |
467 | return false; |
468 | } |
469 | |
470 | void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) { |
471 | // Acquire a diagnostic ID also in the external diagnostics engine. |
472 | auto DiagLevelAndFormatString = |
473 | Context.getDiagLevelAndFormatString(DiagnosticID: Info.getID(), Loc: Info.getLocation()); |
474 | unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID( |
475 | Level: DiagLevelAndFormatString.first, Message: DiagLevelAndFormatString.second); |
476 | |
477 | // Forward the details. |
478 | auto Builder = ExternalDiagEngine->Report(Loc: Info.getLocation(), DiagID: ExternalID); |
479 | for (const FixItHint &Hint : Info.getFixItHints()) |
480 | Builder << Hint; |
481 | for (auto Range : Info.getRanges()) |
482 | Builder << Range; |
483 | for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) { |
484 | DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Idx: Index); |
485 | switch (Kind) { |
486 | case clang::DiagnosticsEngine::ak_std_string: |
487 | Builder << Info.getArgStdStr(Idx: Index); |
488 | break; |
489 | case clang::DiagnosticsEngine::ak_c_string: |
490 | Builder << Info.getArgCStr(Idx: Index); |
491 | break; |
492 | case clang::DiagnosticsEngine::ak_sint: |
493 | Builder << Info.getArgSInt(Idx: Index); |
494 | break; |
495 | case clang::DiagnosticsEngine::ak_uint: |
496 | Builder << Info.getArgUInt(Idx: Index); |
497 | break; |
498 | case clang::DiagnosticsEngine::ak_tokenkind: |
499 | Builder << static_cast<tok::TokenKind>(Info.getRawArg(Idx: Index)); |
500 | break; |
501 | case clang::DiagnosticsEngine::ak_identifierinfo: |
502 | Builder << Info.getArgIdentifier(Idx: Index); |
503 | break; |
504 | case clang::DiagnosticsEngine::ak_qual: |
505 | Builder << Qualifiers::fromOpaqueValue(opaque: Info.getRawArg(Idx: Index)); |
506 | break; |
507 | case clang::DiagnosticsEngine::ak_qualtype: |
508 | Builder << QualType::getFromOpaquePtr(Ptr: (void *)Info.getRawArg(Idx: Index)); |
509 | break; |
510 | case clang::DiagnosticsEngine::ak_declarationname: |
511 | Builder << DeclarationName::getFromOpaqueInteger(P: Info.getRawArg(Idx: Index)); |
512 | break; |
513 | case clang::DiagnosticsEngine::ak_nameddecl: |
514 | Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Idx: Index)); |
515 | break; |
516 | case clang::DiagnosticsEngine::ak_nestednamespec: |
517 | Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Idx: Index)); |
518 | break; |
519 | case clang::DiagnosticsEngine::ak_declcontext: |
520 | Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Idx: Index)); |
521 | break; |
522 | case clang::DiagnosticsEngine::ak_qualtype_pair: |
523 | assert(false); // This one is not passed around. |
524 | break; |
525 | case clang::DiagnosticsEngine::ak_attr: |
526 | Builder << reinterpret_cast<Attr *>(Info.getRawArg(Idx: Index)); |
527 | break; |
528 | case clang::DiagnosticsEngine::ak_addrspace: |
529 | Builder << static_cast<LangAS>(Info.getRawArg(Idx: Index)); |
530 | break; |
531 | case clang::DiagnosticsEngine::ak_expr: |
532 | Builder << reinterpret_cast<const Expr *>(Info.getRawArg(Idx: Index)); |
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 | !getExcludeHeaderFilter()->match(String: FileName)); |
569 | |
570 | unsigned LineNumber = Sources.getExpansionLineNumber(Loc: Location); |
571 | LastErrorPassesLineFilter = |
572 | LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); |
573 | } |
574 | |
575 | llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() { |
576 | if (!HeaderFilter) |
577 | HeaderFilter = |
578 | std::make_unique<llvm::Regex>(args: *Context.getOptions().HeaderFilterRegex); |
579 | return HeaderFilter.get(); |
580 | } |
581 | |
582 | llvm::Regex *ClangTidyDiagnosticConsumer::getExcludeHeaderFilter() { |
583 | if (!ExcludeHeaderFilter) |
584 | ExcludeHeaderFilter = std::make_unique<llvm::Regex>( |
585 | args: *Context.getOptions().ExcludeHeaderFilterRegex); |
586 | return ExcludeHeaderFilter.get(); |
587 | } |
588 | |
589 | void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() { |
590 | // Each error is modelled as the set of intervals in which it applies |
591 | // replacements. To detect overlapping replacements, we use a sweep line |
592 | // algorithm over these sets of intervals. |
593 | // An event here consists of the opening or closing of an interval. During the |
594 | // process, we maintain a counter with the amount of open intervals. If we |
595 | // find an endpoint of an interval and this counter is different from 0, it |
596 | // means that this interval overlaps with another one, so we set it as |
597 | // inapplicable. |
598 | struct Event { |
599 | // An event can be either the begin or the end of an interval. |
600 | enum EventType { |
601 | ET_Begin = 1, |
602 | ET_Insert = 0, |
603 | ET_End = -1, |
604 | }; |
605 | |
606 | Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, |
607 | unsigned ErrorSize) |
608 | : Type(Type), ErrorId(ErrorId) { |
609 | // The events are going to be sorted by their position. In case of draw: |
610 | // |
611 | // * If an interval ends at the same position at which other interval |
612 | // begins, this is not an overlapping, so we want to remove the ending |
613 | // interval before adding the starting one: end events have higher |
614 | // priority than begin events. |
615 | // |
616 | // * If we have several begin points at the same position, we will mark as |
617 | // inapplicable the ones that we process later, so the first one has to |
618 | // be the one with the latest end point, because this one will contain |
619 | // all the other intervals. For the same reason, if we have several end |
620 | // points in the same position, the last one has to be the one with the |
621 | // earliest begin point. In both cases, we sort non-increasingly by the |
622 | // position of the complementary. |
623 | // |
624 | // * In case of two equal intervals, the one whose error is bigger can |
625 | // potentially contain the other one, so we want to process its begin |
626 | // points before and its end points later. |
627 | // |
628 | // * Finally, if we have two equal intervals whose errors have the same |
629 | // size, none of them will be strictly contained inside the other. |
630 | // Sorting by ErrorId will guarantee that the begin point of the first |
631 | // one will be processed before, disallowing the second one, and the |
632 | // end point of the first one will also be processed before, |
633 | // disallowing the first one. |
634 | switch (Type) { |
635 | case ET_Begin: |
636 | Priority = std::make_tuple(args&: Begin, args&: Type, args: -End, args: -ErrorSize, args&: ErrorId); |
637 | break; |
638 | case ET_Insert: |
639 | Priority = std::make_tuple(args&: Begin, args&: Type, args: -End, args&: ErrorSize, args&: ErrorId); |
640 | break; |
641 | case ET_End: |
642 | Priority = std::make_tuple(args&: End, args&: Type, args: -Begin, args&: ErrorSize, args&: ErrorId); |
643 | break; |
644 | } |
645 | } |
646 | |
647 | bool operator<(const Event &Other) const { |
648 | return Priority < Other.Priority; |
649 | } |
650 | |
651 | // Determines if this event is the begin or the end of an interval. |
652 | EventType Type; |
653 | // The index of the error to which the interval that generated this event |
654 | // belongs. |
655 | unsigned ErrorId; |
656 | // The events will be sorted based on this field. |
657 | std::tuple<unsigned, EventType, int, int, unsigned> Priority; |
658 | }; |
659 | |
660 | removeDuplicatedDiagnosticsOfAliasCheckers(); |
661 | |
662 | // Compute error sizes. |
663 | std::vector<int> Sizes; |
664 | std::vector< |
665 | std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>> |
666 | ErrorFixes; |
667 | for (auto &Error : Errors) { |
668 | if (const auto *Fix = getFixIt(Diagnostic: Error, AnyFix: GetFixesFromNotes)) |
669 | ErrorFixes.emplace_back( |
670 | args: &Error, args: const_cast<llvm::StringMap<tooling::Replacements> *>(Fix)); |
671 | } |
672 | for (const auto &ErrorAndFix : ErrorFixes) { |
673 | int Size = 0; |
674 | for (const auto &FileAndReplaces : *ErrorAndFix.second) { |
675 | for (const auto &Replace : FileAndReplaces.second) |
676 | Size += Replace.getLength(); |
677 | } |
678 | Sizes.push_back(x: Size); |
679 | } |
680 | |
681 | // Build events from error intervals. |
682 | llvm::StringMap<std::vector<Event>> FileEvents; |
683 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
684 | for (const auto &FileAndReplace : *ErrorFixes[I].second) { |
685 | for (const auto &Replace : FileAndReplace.second) { |
686 | unsigned Begin = Replace.getOffset(); |
687 | unsigned End = Begin + Replace.getLength(); |
688 | auto &Events = FileEvents[Replace.getFilePath()]; |
689 | if (Begin == End) { |
690 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_Insert, args&: I, args&: Sizes[I]); |
691 | } else { |
692 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_Begin, args&: I, args&: Sizes[I]); |
693 | Events.emplace_back(args&: Begin, args&: End, args: Event::ET_End, args&: I, args&: Sizes[I]); |
694 | } |
695 | } |
696 | } |
697 | } |
698 | |
699 | llvm::BitVector Apply(ErrorFixes.size(), true); |
700 | for (auto &FileAndEvents : FileEvents) { |
701 | std::vector<Event> &Events = FileAndEvents.second; |
702 | // Sweep. |
703 | llvm::sort(C&: Events); |
704 | int OpenIntervals = 0; |
705 | for (const auto &Event : Events) { |
706 | switch (Event.Type) { |
707 | case Event::ET_Begin: |
708 | if (OpenIntervals++ != 0) |
709 | Apply[Event.ErrorId] = false; |
710 | break; |
711 | case Event::ET_Insert: |
712 | if (OpenIntervals != 0) |
713 | Apply[Event.ErrorId] = false; |
714 | break; |
715 | case Event::ET_End: |
716 | if (--OpenIntervals != 0) |
717 | Apply[Event.ErrorId] = false; |
718 | break; |
719 | } |
720 | } |
721 | assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match"); |
722 | } |
723 | |
724 | for (unsigned I = 0; I < ErrorFixes.size(); ++I) { |
725 | if (!Apply[I]) { |
726 | ErrorFixes[I].second->clear(); |
727 | ErrorFixes[I].first->Notes.emplace_back( |
728 | Args: "this fix will not be applied because it overlaps with another fix"); |
729 | } |
730 | } |
731 | } |
732 | |
733 | namespace { |
734 | struct LessClangTidyError { |
735 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
736 | const tooling::DiagnosticMessage &M1 = LHS.Message; |
737 | const tooling::DiagnosticMessage &M2 = RHS.Message; |
738 | |
739 | return std::tie(args: M1.FilePath, args: M1.FileOffset, args: LHS.DiagnosticName, |
740 | args: M1.Message) < |
741 | std::tie(args: M2.FilePath, args: M2.FileOffset, args: RHS.DiagnosticName, args: M2.Message); |
742 | } |
743 | }; |
744 | struct EqualClangTidyError { |
745 | bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { |
746 | LessClangTidyError Less; |
747 | return !Less(LHS, RHS) && !Less(RHS, LHS); |
748 | } |
749 | }; |
750 | } // end anonymous namespace |
751 | |
752 | std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() { |
753 | finalizeLastError(); |
754 | |
755 | llvm::stable_sort(Range&: Errors, C: LessClangTidyError()); |
756 | Errors.erase(first: llvm::unique(R&: Errors, P: EqualClangTidyError()), last: Errors.end()); |
757 | if (RemoveIncompatibleErrors) |
758 | removeIncompatibleErrors(); |
759 | return std::move(Errors); |
760 | } |
761 | |
762 | namespace { |
763 | struct LessClangTidyErrorWithoutDiagnosticName { |
764 | bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const { |
765 | const tooling::DiagnosticMessage &M1 = LHS->Message; |
766 | const tooling::DiagnosticMessage &M2 = RHS->Message; |
767 | |
768 | return std::tie(args: M1.FilePath, args: M1.FileOffset, args: M1.Message) < |
769 | std::tie(args: M2.FilePath, args: M2.FileOffset, args: M2.Message); |
770 | } |
771 | }; |
772 | } // end anonymous namespace |
773 | |
774 | void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() { |
775 | using UniqueErrorSet = |
776 | std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>; |
777 | UniqueErrorSet UniqueErrors; |
778 | |
779 | auto IT = Errors.begin(); |
780 | while (IT != Errors.end()) { |
781 | ClangTidyError &Error = *IT; |
782 | std::pair<UniqueErrorSet::iterator, bool> Inserted = |
783 | UniqueErrors.insert(x: &Error); |
784 | |
785 | // Unique error, we keep it and move along. |
786 | if (Inserted.second) { |
787 | ++IT; |
788 | } else { |
789 | ClangTidyError &ExistingError = **Inserted.first; |
790 | const llvm::StringMap<tooling::Replacements> &CandidateFix = |
791 | Error.Message.Fix; |
792 | const llvm::StringMap<tooling::Replacements> &ExistingFix = |
793 | (*Inserted.first)->Message.Fix; |
794 | |
795 | if (CandidateFix != ExistingFix) { |
796 | |
797 | // In case of a conflict, don't suggest any fix-it. |
798 | ExistingError.Message.Fix.clear(); |
799 | ExistingError.Notes.emplace_back( |
800 | Args: llvm::formatv(Fmt: "cannot apply fix-it because an alias checker has " |
801 | "suggested a different fix-it; please remove one of " |
802 | "the checkers ('{0}', '{1}') or " |
803 | "ensure they are both configured the same", |
804 | Vals&: ExistingError.DiagnosticName, Vals&: Error.DiagnosticName) |
805 | .str()); |
806 | } |
807 | |
808 | if (Error.IsWarningAsError) |
809 | ExistingError.IsWarningAsError = true; |
810 | |
811 | // Since it is the same error, we should take it as alias and remove it. |
812 | ExistingError.EnabledDiagnosticAliases.emplace_back(args&: Error.DiagnosticName); |
813 | IT = Errors.erase(position: IT); |
814 | } |
815 | } |
816 | } |
817 |
Definitions
- ClangTidyDiagnosticRenderer
- ClangTidyDiagnosticRenderer
- emitDiagnosticMessage
- emitDiagnosticLoc
- emitCodeContext
- emitIncludeLocation
- emitImportLocation
- emitBuildingModuleLocation
- endDiagnostic
- ClangTidyError
- ClangTidyContext
- ~ClangTidyContext
- diag
- diag
- diag
- configurationDiag
- shouldSuppressDiagnostic
- setSourceManager
- parseFileExtensions
- setCurrentFile
- setASTContext
- getGlobalOptions
- getOptions
- getOptionsForFile
- setEnableProfiling
- setProfileStoragePrefix
- getProfileStorageParams
- isCheckEnabled
- treatAsError
- getCheckName
- ClangTidyDiagnosticConsumer
- finalizeLastError
- getFixIt
- HandleDiagnostic
- passesLineFilter
- forwardDiagnostic
- checkFilters
- getHeaderFilter
- getExcludeHeaderFilter
- removeIncompatibleErrors
- LessClangTidyError
- operator()
- EqualClangTidyError
- operator()
- take
- LessClangTidyErrorWithoutDiagnosticName
- operator()
Learn to use CMake with our Intro Training
Find out more