| 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 ) |
| 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::() { |
| 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::() { |
| 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 | |