/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- SarifDiagnostics.cpp - Sarif 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 SarifDiagnostics object. |
10 | | // |
11 | | //===----------------------------------------------------------------------===// |
12 | | |
13 | | #include "clang/Analysis/MacroExpansionContext.h" |
14 | | #include "clang/Analysis/PathDiagnostic.h" |
15 | | #include "clang/Basic/FileManager.h" |
16 | | #include "clang/Basic/Sarif.h" |
17 | | #include "clang/Basic/SourceManager.h" |
18 | | #include "clang/Basic/Version.h" |
19 | | #include "clang/Lex/Preprocessor.h" |
20 | | #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
21 | | #include "llvm/ADT/STLExtras.h" |
22 | | #include "llvm/ADT/StringMap.h" |
23 | | #include "llvm/Support/ConvertUTF.h" |
24 | | #include "llvm/Support/JSON.h" |
25 | | #include "llvm/Support/Path.h" |
26 | | |
27 | | using namespace llvm; |
28 | | using namespace clang; |
29 | | using namespace ento; |
30 | | |
31 | | namespace { |
32 | | class SarifDiagnostics : public PathDiagnosticConsumer { |
33 | | std::string OutputFile; |
34 | | const LangOptions &LO; |
35 | | SarifDocumentWriter SarifWriter; |
36 | | |
37 | | public: |
38 | | SarifDiagnostics(const std::string &Output, const LangOptions &LO, |
39 | | const SourceManager &SM) |
40 | 2 | : OutputFile(Output), LO(LO), SarifWriter(SM) {} |
41 | 2 | ~SarifDiagnostics() override = default; |
42 | | |
43 | | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
44 | | FilesMade *FM) override; |
45 | | |
46 | 0 | StringRef getName() const override { return "SarifDiagnostics"; } |
47 | 175 | PathGenerationScheme getGenerationScheme() const override { return Minimal; } |
48 | 0 | bool supportsLogicalOpControlFlow() const override { return true; } |
49 | 6 | bool supportsCrossFileDiagnostics() const override { return true; } |
50 | | }; |
51 | | } // end anonymous namespace |
52 | | |
53 | | void ento::createSarifDiagnosticConsumer( |
54 | | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
55 | | const std::string &Output, const Preprocessor &PP, |
56 | | const cross_tu::CrossTranslationUnitContext &CTU, |
57 | 2 | const MacroExpansionContext &MacroExpansions) { |
58 | | |
59 | | // TODO: Emit an error here. |
60 | 2 | if (Output.empty()) |
61 | 0 | return; |
62 | | |
63 | 2 | C.push_back( |
64 | 2 | new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager())); |
65 | 2 | createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP, |
66 | 2 | CTU, MacroExpansions); |
67 | 2 | } |
68 | | |
69 | 5 | static StringRef getRuleDescription(StringRef CheckName) { |
70 | 5 | return llvm::StringSwitch<StringRef>(CheckName) |
71 | 5 | #define GET_CHECKERS |
72 | 5 | #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ |
73 | 965 | .Case(FULLNAME, HELPTEXT) |
74 | 5 | #include "clang/StaticAnalyzer/Checkers/Checkers.inc" |
75 | 5 | #undef CHECKER |
76 | 5 | #undef GET_CHECKERS |
77 | 5 | ; |
78 | 5 | } |
79 | | |
80 | 5 | static StringRef getRuleHelpURIStr(StringRef CheckName) { |
81 | 5 | return llvm::StringSwitch<StringRef>(CheckName) |
82 | 5 | #define GET_CHECKERS |
83 | 5 | #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ |
84 | 965 | .Case(FULLNAME, DOC_URI) |
85 | 5 | #include "clang/StaticAnalyzer/Checkers/Checkers.inc" |
86 | 5 | #undef CHECKER |
87 | 5 | #undef GET_CHECKERS |
88 | 5 | ; |
89 | 5 | } |
90 | | |
91 | | static ThreadFlowImportance |
92 | 16 | calculateImportance(const PathDiagnosticPiece &Piece) { |
93 | 16 | switch (Piece.getKind()) { |
94 | 0 | case PathDiagnosticPiece::Call: |
95 | 0 | case PathDiagnosticPiece::Macro: |
96 | 0 | case PathDiagnosticPiece::Note: |
97 | 0 | case PathDiagnosticPiece::PopUp: |
98 | | // FIXME: What should be reported here? |
99 | 0 | break; |
100 | 14 | case PathDiagnosticPiece::Event: |
101 | 14 | return Piece.getTagStr() == "ConditionBRVisitor" |
102 | 14 | ? ThreadFlowImportance::Important2 |
103 | 14 | : ThreadFlowImportance::Essential12 ; |
104 | 2 | case PathDiagnosticPiece::ControlFlow: |
105 | 2 | return ThreadFlowImportance::Unimportant; |
106 | 16 | } |
107 | 0 | return ThreadFlowImportance::Unimportant; |
108 | 16 | } |
109 | | |
110 | | /// Accepts a SourceRange corresponding to a pair of the first and last tokens |
111 | | /// and converts to a Character granular CharSourceRange. |
112 | | static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R, |
113 | | const SourceManager &SM, |
114 | 22 | const LangOptions &LO) { |
115 | | // Caret diagnostics have the first and last locations pointed at the same |
116 | | // location, return these as-is. |
117 | 22 | if (R.getBegin() == R.getEnd()) |
118 | 8 | return CharSourceRange::getCharRange(R); |
119 | | |
120 | 14 | SourceLocation BeginCharLoc = R.getBegin(); |
121 | | // For token ranges, the raw end SLoc points at the first character of the |
122 | | // last token in the range. This must be moved to one past the end of the |
123 | | // last character using the lexer. |
124 | 14 | SourceLocation EndCharLoc = |
125 | 14 | Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO); |
126 | 14 | return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc); |
127 | 22 | } |
128 | | |
129 | | static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag, |
130 | 6 | const LangOptions &LO) { |
131 | 6 | SmallVector<ThreadFlow, 8> Flows; |
132 | 6 | const PathPieces &Pieces = Diag->path.flatten(false); |
133 | 16 | for (const auto &Piece : Pieces) { |
134 | 16 | auto Range = convertTokenRangeToCharRange( |
135 | 16 | Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO); |
136 | 16 | auto Flow = ThreadFlow::create() |
137 | 16 | .setImportance(calculateImportance(*Piece)) |
138 | 16 | .setRange(Range) |
139 | 16 | .setMessage(Piece->getString()); |
140 | 16 | Flows.push_back(Flow); |
141 | 16 | } |
142 | 6 | return Flows; |
143 | 6 | } |
144 | | |
145 | | static StringMap<uint32_t> |
146 | | createRuleMapping(const std::vector<const PathDiagnostic *> &Diags, |
147 | 2 | SarifDocumentWriter &SarifWriter) { |
148 | 2 | StringMap<uint32_t> RuleMapping; |
149 | 2 | llvm::StringSet<> Seen; |
150 | | |
151 | 6 | for (const PathDiagnostic *D : Diags) { |
152 | 6 | StringRef CheckName = D->getCheckerName(); |
153 | 6 | std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName); |
154 | 6 | if (P.second) { |
155 | 5 | auto Rule = SarifRule::create() |
156 | 5 | .setName(CheckName) |
157 | 5 | .setRuleId(CheckName) |
158 | 5 | .setDescription(getRuleDescription(CheckName)) |
159 | 5 | .setHelpURI(getRuleHelpURIStr(CheckName)); |
160 | 5 | size_t RuleIdx = SarifWriter.createRule(Rule); |
161 | 5 | RuleMapping[CheckName] = RuleIdx; |
162 | 5 | } |
163 | 6 | } |
164 | 2 | return RuleMapping; |
165 | 2 | } |
166 | | |
167 | | static SarifResult createResult(const PathDiagnostic *Diag, |
168 | | const StringMap<uint32_t> &RuleMapping, |
169 | 6 | const LangOptions &LO) { |
170 | | |
171 | 6 | StringRef CheckName = Diag->getCheckerName(); |
172 | 6 | uint32_t RuleIdx = RuleMapping.lookup(CheckName); |
173 | 6 | auto Range = convertTokenRangeToCharRange( |
174 | 6 | Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO); |
175 | | |
176 | 6 | SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO); |
177 | 6 | auto Result = SarifResult::create(RuleIdx) |
178 | 6 | .setRuleId(CheckName) |
179 | 6 | .setDiagnosticMessage(Diag->getVerboseDescription()) |
180 | 6 | .setDiagnosticLevel(SarifResultLevel::Warning) |
181 | 6 | .setLocations({Range}) |
182 | 6 | .setThreadFlows(Flows); |
183 | 6 | return Result; |
184 | 6 | } |
185 | | |
186 | | void SarifDiagnostics::FlushDiagnosticsImpl( |
187 | 2 | std::vector<const PathDiagnostic *> &Diags, FilesMade *) { |
188 | | // We currently overwrite the file if it already exists. However, it may be |
189 | | // useful to add a feature someday that allows the user to append a run to an |
190 | | // existing SARIF file. One danger from that approach is that the size of the |
191 | | // file can become large very quickly, so decoding into JSON to append a run |
192 | | // may be an expensive operation. |
193 | 2 | std::error_code EC; |
194 | 2 | llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
195 | 2 | if (EC) { |
196 | 0 | llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; |
197 | 0 | return; |
198 | 0 | } |
199 | | |
200 | 2 | std::string ToolVersion = getClangFullVersion(); |
201 | 2 | SarifWriter.createRun("clang", "clang static analyzer", ToolVersion); |
202 | 2 | StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter); |
203 | 6 | for (const PathDiagnostic *D : Diags) { |
204 | 6 | SarifResult Result = createResult(D, RuleMapping, LO); |
205 | 6 | SarifWriter.appendResult(Result); |
206 | 6 | } |
207 | 2 | auto Document = SarifWriter.createDocument(); |
208 | 2 | OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document))); |
209 | 2 | } |