| //===--- TextDiagnostics.cpp - Text Diagnostics for Paths -------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines the TextDiagnostics object. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Analysis/MacroExpansionContext.h" |
| #include "clang/Analysis/PathDiagnostic.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Basic/Version.h" |
| #include "clang/CrossTU/CrossTranslationUnit.h" |
| #include "clang/Frontend/ASTUnit.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Rewrite/Core/Rewriter.h" |
| #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" |
| #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
| #include "clang/Tooling/Core/Replacement.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Support/Casting.h" |
| |
| using namespace clang; |
| using namespace ento; |
| using namespace tooling; |
| |
| namespace { |
| /// Emitsd minimal diagnostics (report message + notes) for the 'none' output |
| /// type to the standard error, or to to compliment many others. Emits detailed |
| /// diagnostics in textual format for the 'text' output type. |
| class TextDiagnostics : public PathDiagnosticConsumer { |
| PathDiagnosticConsumerOptions DiagOpts; |
| DiagnosticsEngine &DiagEng; |
| const LangOptions &LO; |
| bool ShouldDisplayPathNotes; |
| |
| public: |
| TextDiagnostics(PathDiagnosticConsumerOptions DiagOpts, |
| DiagnosticsEngine &DiagEng, const LangOptions &LO, |
| bool ShouldDisplayPathNotes) |
| : DiagOpts(std::move(DiagOpts)), DiagEng(DiagEng), LO(LO), |
| ShouldDisplayPathNotes(ShouldDisplayPathNotes) {} |
| ~TextDiagnostics() override {} |
| |
| StringRef getName() const override { return "TextDiagnostics"; } |
| |
| bool supportsLogicalOpControlFlow() const override { return true; } |
| bool supportsCrossFileDiagnostics() const override { return true; } |
| |
| PathGenerationScheme getGenerationScheme() const override { |
| return ShouldDisplayPathNotes ? Minimal : None; |
| } |
| |
| void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
| FilesMade *filesMade) override { |
| unsigned WarnID = |
| DiagOpts.ShouldDisplayWarningsAsErrors |
| ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0") |
| : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); |
| unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0"); |
| SourceManager &SM = DiagEng.getSourceManager(); |
| |
| Replacements Repls; |
| auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String, |
| ArrayRef<SourceRange> Ranges, |
| ArrayRef<FixItHint> Fixits) { |
| if (!DiagOpts.ShouldApplyFixIts) { |
| DiagEng.Report(Loc, ID) << String << Ranges << Fixits; |
| return; |
| } |
| |
| DiagEng.Report(Loc, ID) << String << Ranges; |
| for (const FixItHint &Hint : Fixits) { |
| Replacement Repl(SM, Hint.RemoveRange, Hint.CodeToInsert); |
| |
| if (llvm::Error Err = Repls.add(Repl)) { |
| llvm::errs() << "Error applying replacement " << Repl.toString() |
| << ": " << Err << "\n"; |
| } |
| } |
| }; |
| |
| for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), |
| E = Diags.end(); |
| I != E; ++I) { |
| const PathDiagnostic *PD = *I; |
| std::string WarningMsg = (DiagOpts.ShouldDisplayDiagnosticName |
| ? " [" + PD->getCheckerName() + "]" |
| : "") |
| .str(); |
| |
| reportPiece(WarnID, PD->getLocation().asLocation(), |
| (PD->getShortDescription() + WarningMsg).str(), |
| PD->path.back()->getRanges(), PD->path.back()->getFixits()); |
| |
| // First, add extra notes, even if paths should not be included. |
| for (const auto &Piece : PD->path) { |
| if (!isa<PathDiagnosticNotePiece>(Piece.get())) |
| continue; |
| |
| reportPiece(NoteID, Piece->getLocation().asLocation(), |
| Piece->getString(), Piece->getRanges(), |
| Piece->getFixits()); |
| } |
| |
| if (!ShouldDisplayPathNotes) |
| continue; |
| |
| // Then, add the path notes if necessary. |
| PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true); |
| for (const auto &Piece : FlatPath) { |
| if (isa<PathDiagnosticNotePiece>(Piece.get())) |
| continue; |
| |
| reportPiece(NoteID, Piece->getLocation().asLocation(), |
| Piece->getString(), Piece->getRanges(), |
| Piece->getFixits()); |
| } |
| } |
| |
| if (Repls.empty()) |
| return; |
| |
| Rewriter Rewrite(SM, LO); |
| if (!applyAllReplacements(Repls, Rewrite)) { |
| llvm::errs() << "An error occured during applying fix-it.\n"; |
| } |
| |
| Rewrite.overwriteChangedFiles(); |
| } |
| }; |
| } // end anonymous namespace |
| |
| void ento::createTextPathDiagnosticConsumer( |
| PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
| const std::string &Prefix, const Preprocessor &PP, |
| const cross_tu::CrossTranslationUnitContext &CTU, |
| const MacroExpansionContext &MacroExpansions) { |
| C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), |
| PP.getLangOpts(), |
| /*ShouldDisplayPathNotes=*/true)); |
| } |
| |
| void ento::createTextMinimalPathDiagnosticConsumer( |
| PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
| const std::string &Prefix, const Preprocessor &PP, |
| const cross_tu::CrossTranslationUnitContext &CTU, |
| const MacroExpansionContext &MacroExpansions) { |
| C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), |
| PP.getLangOpts(), |
| /*ShouldDisplayPathNotes=*/false)); |
| } |