1 //===--- TextDiagnostics.cpp - Text Diagnostics for Paths -------*- C++ -*-===//
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 //  This file defines the TextDiagnostics object.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/Analysis/PathDiagnostic.h"
14 #include "clang/Basic/SourceManager.h"
15 #include "clang/Basic/Version.h"
16 #include "clang/CrossTU/CrossTranslationUnit.h"
17 #include "clang/Frontend/ASTUnit.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Rewrite/Core/Rewriter.h"
20 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
21 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
22 #include "clang/Tooling/Core/Replacement.h"
23 #include "clang/Tooling/Tooling.h"
24 #include "llvm/ADT/SmallPtrSet.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/Support/Casting.h"
27 
28 using namespace clang;
29 using namespace ento;
30 using namespace tooling;
31 
32 namespace {
33 /// Emitsd minimal diagnostics (report message + notes) for the 'none' output
34 /// type to the standard error, or to to compliment many others. Emits detailed
35 /// diagnostics in textual format for the 'text' output type.
36 class TextDiagnostics : public PathDiagnosticConsumer {
37   DiagnosticsEngine &DiagEng;
38   const LangOptions &LO;
39   const bool IncludePath = false;
40   const bool ShouldEmitAsError = false;
41   const bool ApplyFixIts = false;
42   const bool ShouldDisplayCheckerName = false;
43 
44 public:
45   TextDiagnostics(DiagnosticsEngine &DiagEng, const LangOptions &LO,
46                   bool ShouldIncludePath, const AnalyzerOptions &AnOpts)
47       : DiagEng(DiagEng), LO(LO), IncludePath(ShouldIncludePath),
48         ShouldEmitAsError(AnOpts.AnalyzerWerror),
49         ApplyFixIts(AnOpts.ShouldApplyFixIts),
50         ShouldDisplayCheckerName(AnOpts.ShouldDisplayCheckerNameForText) {}
51   ~TextDiagnostics() override {}
52 
53   StringRef getName() const override { return "TextDiagnostics"; }
54 
55   bool supportsLogicalOpControlFlow() const override { return true; }
56   bool supportsCrossFileDiagnostics() const override { return true; }
57 
58   PathGenerationScheme getGenerationScheme() const override {
59     return IncludePath ? Minimal : None;
60   }
61 
62   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
63                             FilesMade *filesMade) override {
64     unsigned WarnID =
65         ShouldEmitAsError
66             ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0")
67             : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0");
68     unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0");
69     SourceManager &SM = DiagEng.getSourceManager();
70 
71     Replacements Repls;
72     auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String,
73                            ArrayRef<SourceRange> Ranges,
74                            ArrayRef<FixItHint> Fixits) {
75       if (!ApplyFixIts) {
76         DiagEng.Report(Loc, ID) << String << Ranges << Fixits;
77         return;
78       }
79 
80       DiagEng.Report(Loc, ID) << String << Ranges;
81       for (const FixItHint &Hint : Fixits) {
82         Replacement Repl(SM, Hint.RemoveRange, Hint.CodeToInsert);
83 
84         if (llvm::Error Err = Repls.add(Repl)) {
85           llvm::errs() << "Error applying replacement " << Repl.toString()
86                        << ": " << Err << "\n";
87         }
88       }
89     };
90 
91     for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(),
92          E = Diags.end();
93          I != E; ++I) {
94       const PathDiagnostic *PD = *I;
95       std::string WarningMsg =
96           (ShouldDisplayCheckerName ? " [" + PD->getCheckerName() + "]" : "")
97               .str();
98 
99       reportPiece(WarnID, PD->getLocation().asLocation(),
100                   (PD->getShortDescription() + WarningMsg).str(),
101                   PD->path.back()->getRanges(), PD->path.back()->getFixits());
102 
103       // First, add extra notes, even if paths should not be included.
104       for (const auto &Piece : PD->path) {
105         if (!isa<PathDiagnosticNotePiece>(Piece.get()))
106           continue;
107 
108         reportPiece(NoteID, Piece->getLocation().asLocation(),
109                     Piece->getString(), Piece->getRanges(),
110                     Piece->getFixits());
111       }
112 
113       if (!IncludePath)
114         continue;
115 
116       // Then, add the path notes if necessary.
117       PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true);
118       for (const auto &Piece : FlatPath) {
119         if (isa<PathDiagnosticNotePiece>(Piece.get()))
120           continue;
121 
122         reportPiece(NoteID, Piece->getLocation().asLocation(),
123                     Piece->getString(), Piece->getRanges(),
124                     Piece->getFixits());
125       }
126     }
127 
128     if (!ApplyFixIts || Repls.empty())
129       return;
130 
131     Rewriter Rewrite(SM, LO);
132     if (!applyAllReplacements(Repls, Rewrite)) {
133       llvm::errs() << "An error occured during applying fix-it.\n";
134     }
135 
136     Rewrite.overwriteChangedFiles();
137   }
138 };
139 } // end anonymous namespace
140 
141 void ento::createTextPathDiagnosticConsumer(
142     AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C,
143     const std::string &Prefix, const clang::Preprocessor &PP,
144     const cross_tu::CrossTranslationUnitContext &CTU) {
145   C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(),
146                                      /*ShouldIncludePath*/ true, AnalyzerOpts));
147 }
148 
149 void ento::createTextMinimalPathDiagnosticConsumer(
150     AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C,
151     const std::string &Prefix, const clang::Preprocessor &PP,
152     const cross_tu::CrossTranslationUnitContext &CTU) {
153   C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(),
154                                      /*ShouldIncludePath*/ false,
155                                      AnalyzerOpts));
156 }
157