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(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 SourceRange.getEnd(), 0, Loc.getManager(), LangOpts); 82 return CharSourceRange::getCharRange(SourceRange.getBegin(), End); 83 }; 84 85 // We are only interested in valid ranges. 86 auto ValidRanges = 87 llvm::make_filter_range(Ranges, [](const CharSourceRange &R) { 88 return R.getAsRange().isValid(); 89 }); 90 91 if (Level == DiagnosticsEngine::Note) { 92 Error.Notes.push_back(TidyMessage); 93 for (const CharSourceRange &SourceRange : ValidRanges) 94 Error.Notes.back().Ranges.emplace_back(Loc.getManager(), 95 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(Loc.getManager(), 102 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(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(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 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, (Description + " [" + CheckName + "]").str()); 181 CheckNamesByDiagnosticID.try_emplace(ID, CheckName); 182 return DiagEngine->Report(Loc, 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, (Description + " [" + CheckName + "]").str()); 190 CheckNamesByDiagnosticID.try_emplace(ID, CheckName); 191 return DiagEngine->Report(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(FM.getFileRef(Error.Message.FilePath)); 198 FileID ID = SM.getOrCreateFileID(File, SrcMgr::C_User); 199 SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID); 200 SourceLocation Loc = FileStartLoc.getLocWithOffset( 201 static_cast<SourceLocation::IntTy>(Error.Message.FileOffset)); 202 return diag(Error.DiagnosticName, Loc, Error.Message.Message, 203 static_cast<DiagnosticIDs::Level>(Error.DiagLevel)); 204 } 205 206 DiagnosticBuilder ClangTidyContext::configurationDiag( 207 StringRef Message, 208 DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { 209 return diag("clang-tidy-config", 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(Info.getID()); 217 return NoLintHandler.shouldSuppress(DiagLevel, Info, 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(Extension, isAlphanumeric)) 231 return false; 232 FileExtensions.insert(Extension); 233 } 234 return true; 235 } 236 237 void ClangTidyContext::setCurrentFile(StringRef File) { 238 CurrentFile = std::string(File); 239 CurrentOptions = getOptionsForFile(CurrentFile); 240 CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks); 241 WarningAsErrorFilter = 242 std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors); 243 if (!parseFileExtensions(*getOptions().HeaderFileExtensions, 244 HeaderFileExtensions)) 245 this->configurationDiag("Invalid header file extensions"); 246 if (!parseFileExtensions(*getOptions().ImplementationFileExtensions, 247 ImplementationFileExtensions)) 248 this->configurationDiag("Invalid implementation file extensions"); 249 } 250 251 void ClangTidyContext::setASTContext(ASTContext *Context) { 252 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, 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 OptionsProvider->getOptions(File), 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(CheckName); 288 } 289 290 bool ClangTidyContext::treatAsError(StringRef CheckName) const { 291 assert(WarningAsErrorFilter != nullptr); 292 return WarningAsErrorFilter->contains(CheckName); 293 } 294 295 std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const { 296 std::string ClangWarningOption = std::string( 297 DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID)); 298 if (!ClangWarningOption.empty()) 299 return "clang-diagnostic-" + ClangWarningOption; 300 llvm::DenseMap<unsigned, std::string>::const_iterator I = 301 CheckNamesByDiagnosticID.find(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>(*Context.getOptions().HeaderFilterRegex); 320 321 if (Context.getOptions().ExcludeHeaderFilterRegex && 322 !Context.getOptions().ExcludeHeaderFilterRegex->empty()) 323 ExcludeHeaderFilter = std::make_unique<llvm::Regex>( 324 *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(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, SuppressionErrors, 379 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(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(CheckName, Level, Context.getCurrentBuildDirectory(), 433 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(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, DiagLevel, Message, Info.getRanges(), 453 Info.getFixItHints()); 454 } 455 456 if (Info.hasSourceManager()) 457 checkFilters(Info.getLocation(), 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(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(Info.getID(), Info.getLocation()); 485 unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID( 486 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second); 487 488 // Forward the details. 489 auto Builder = ExternalDiagEngine->Report(Info.getLocation(), 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(Index); 496 switch (Kind) { 497 case clang::DiagnosticsEngine::ak_std_string: 498 Builder << Info.getArgStdStr(Index); 499 break; 500 case clang::DiagnosticsEngine::ak_c_string: 501 Builder << Info.getArgCStr(Index); 502 break; 503 case clang::DiagnosticsEngine::ak_sint: 504 Builder << Info.getArgSInt(Index); 505 break; 506 case clang::DiagnosticsEngine::ak_uint: 507 Builder << Info.getArgUInt(Index); 508 break; 509 case clang::DiagnosticsEngine::ak_tokenkind: 510 Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index)); 511 break; 512 case clang::DiagnosticsEngine::ak_identifierinfo: 513 Builder << Info.getArgIdentifier(Index); 514 break; 515 case clang::DiagnosticsEngine::ak_qual: 516 Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index)); 517 break; 518 case clang::DiagnosticsEngine::ak_qualtype: 519 Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index)); 520 break; 521 case clang::DiagnosticsEngine::ak_declarationname: 522 Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index)); 523 break; 524 case clang::DiagnosticsEngine::ak_nameddecl: 525 Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index)); 526 break; 527 case clang::DiagnosticsEngine::ak_nestednamespec: 528 Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Index)); 529 break; 530 case clang::DiagnosticsEngine::ak_declcontext: 531 Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(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(Index)); 538 break; 539 case clang::DiagnosticsEngine::ak_addrspace: 540 Builder << static_cast<LangAS>(Info.getRawArg(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(Location) || Sources.isInSystemMacro(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(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(Location) || 576 (HeaderFilter && 577 (HeaderFilter->match(FileName) && 578 !(ExcludeHeaderFilter && ExcludeHeaderFilter->match(FileName)))); 579 580 unsigned LineNumber = Sources.getExpansionLineNumber(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(Begin, Type, -End, -ErrorSize, ErrorId); 633 break; 634 case ET_Insert: 635 Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId); 636 break; 637 case ET_End: 638 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, 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(Error, GetFixesFromNotes)) 665 ErrorFixes.emplace_back( 666 &Error, 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(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(Begin, End, Event::ET_Insert, I, Sizes[I]); 687 } else { 688 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]); 689 Events.emplace_back(Begin, End, Event::ET_End, I, 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(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 "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(M1.FilePath, M1.FileOffset, LHS.DiagnosticName, 736 M1.Message) < 737 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, 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(Errors, LessClangTidyError()); 752 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), 753 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(M1.FilePath, M1.FileOffset, M1.Message) < 766 std::tie(M2.FilePath, M2.FileOffset, 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(&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 llvm::formatv("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 ExistingError.DiagnosticName, 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(Error.DiagnosticName); 810 IT = Errors.erase(IT); 811 } 812 } 813 } 814