/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- PlistDiagnostics.cpp - Plist 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 PlistDiagnostics object. |
10 | | // |
11 | | //===----------------------------------------------------------------------===// |
12 | | |
13 | | #include "clang/Analysis/IssueHash.h" |
14 | | #include "clang/Analysis/MacroExpansionContext.h" |
15 | | #include "clang/Analysis/PathDiagnostic.h" |
16 | | #include "clang/Basic/FileManager.h" |
17 | | #include "clang/Basic/PlistSupport.h" |
18 | | #include "clang/Basic/SourceManager.h" |
19 | | #include "clang/Basic/Version.h" |
20 | | #include "clang/CrossTU/CrossTranslationUnit.h" |
21 | | #include "clang/Frontend/ASTUnit.h" |
22 | | #include "clang/Lex/Preprocessor.h" |
23 | | #include "clang/Lex/TokenConcatenation.h" |
24 | | #include "clang/Rewrite/Core/HTMLRewrite.h" |
25 | | #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
26 | | #include "llvm/ADT/SmallPtrSet.h" |
27 | | #include "llvm/ADT/SmallVector.h" |
28 | | #include "llvm/ADT/Statistic.h" |
29 | | #include "llvm/Support/Casting.h" |
30 | | #include <memory> |
31 | | #include <optional> |
32 | | |
33 | | using namespace clang; |
34 | | using namespace ento; |
35 | | using namespace markup; |
36 | | |
37 | | //===----------------------------------------------------------------------===// |
38 | | // Declarations of helper classes and functions for emitting bug reports in |
39 | | // plist format. |
40 | | //===----------------------------------------------------------------------===// |
41 | | |
42 | | namespace { |
43 | | class PlistDiagnostics : public PathDiagnosticConsumer { |
44 | | PathDiagnosticConsumerOptions DiagOpts; |
45 | | const std::string OutputFile; |
46 | | const Preprocessor &PP; |
47 | | const cross_tu::CrossTranslationUnitContext &CTU; |
48 | | const MacroExpansionContext &MacroExpansions; |
49 | | const bool SupportsCrossFileDiagnostics; |
50 | | |
51 | | void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
52 | | const PathPieces &Path); |
53 | | |
54 | | public: |
55 | | PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, |
56 | | const std::string &OutputFile, const Preprocessor &PP, |
57 | | const cross_tu::CrossTranslationUnitContext &CTU, |
58 | | const MacroExpansionContext &MacroExpansions, |
59 | | bool supportsMultipleFiles); |
60 | | |
61 | 69 | ~PlistDiagnostics() override {} |
62 | | |
63 | | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
64 | | FilesMade *filesMade) override; |
65 | | |
66 | 0 | StringRef getName() const override { |
67 | 0 | return "PlistDiagnostics"; |
68 | 0 | } |
69 | | |
70 | 27.8k | PathGenerationScheme getGenerationScheme() const override { |
71 | 27.8k | return Extensive; |
72 | 27.8k | } |
73 | 0 | bool supportsLogicalOpControlFlow() const override { return true; } |
74 | 639 | bool supportsCrossFileDiagnostics() const override { |
75 | 639 | return SupportsCrossFileDiagnostics; |
76 | 639 | } |
77 | | }; |
78 | | } // end anonymous namespace |
79 | | |
80 | | namespace { |
81 | | |
82 | | /// A helper class for emitting a single report. |
83 | | class PlistPrinter { |
84 | | const FIDMap& FM; |
85 | | const Preprocessor &PP; |
86 | | const cross_tu::CrossTranslationUnitContext &CTU; |
87 | | const MacroExpansionContext &MacroExpansions; |
88 | | llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; |
89 | | |
90 | | public: |
91 | | PlistPrinter(const FIDMap &FM, const Preprocessor &PP, |
92 | | const cross_tu::CrossTranslationUnitContext &CTU, |
93 | | const MacroExpansionContext &MacroExpansions) |
94 | 633 | : FM(FM), PP(PP), CTU(CTU), MacroExpansions(MacroExpansions) {} |
95 | | |
96 | 3.55k | void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { |
97 | 3.55k | ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); |
98 | 3.55k | } |
99 | | |
100 | | /// Print the expansions of the collected macro pieces. |
101 | | /// |
102 | | /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one |
103 | | /// is found through a call piece, etc), it's subpieces are reported, and the |
104 | | /// piece itself is collected. Call this function after the entire bugpath |
105 | | /// was reported. |
106 | | void ReportMacroExpansions(raw_ostream &o, unsigned indent); |
107 | | |
108 | | private: |
109 | | void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, |
110 | | unsigned indent, unsigned depth, bool includeControlFlow, |
111 | 4.53k | bool isKeyEvent = false) { |
112 | 4.53k | switch (P.getKind()) { |
113 | 2.25k | case PathDiagnosticPiece::ControlFlow: |
114 | 2.25k | if (includeControlFlow) |
115 | 2.21k | ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), indent); |
116 | 2.25k | break; |
117 | 153 | case PathDiagnosticPiece::Call: |
118 | 153 | ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent, |
119 | 153 | depth); |
120 | 153 | break; |
121 | 2.04k | case PathDiagnosticPiece::Event: |
122 | 2.04k | ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth, |
123 | 2.04k | isKeyEvent); |
124 | 2.04k | break; |
125 | 46 | case PathDiagnosticPiece::Macro: |
126 | 46 | ReportMacroSubPieces(o, cast<PathDiagnosticMacroPiece>(P), indent, |
127 | 46 | depth); |
128 | 46 | break; |
129 | 1 | case PathDiagnosticPiece::Note: |
130 | 1 | ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent); |
131 | 1 | break; |
132 | 33 | case PathDiagnosticPiece::PopUp: |
133 | 33 | ReportPopUp(o, cast<PathDiagnosticPopUpPiece>(P), indent); |
134 | 33 | break; |
135 | 4.53k | } |
136 | 4.53k | } |
137 | | |
138 | | void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges, |
139 | | unsigned indent); |
140 | | void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); |
141 | | void EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, unsigned indent); |
142 | | |
143 | | void ReportControlFlow(raw_ostream &o, |
144 | | const PathDiagnosticControlFlowPiece& P, |
145 | | unsigned indent); |
146 | | void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
147 | | unsigned indent, unsigned depth, bool isKeyEvent = false); |
148 | | void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
149 | | unsigned indent, unsigned depth); |
150 | | void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P, |
151 | | unsigned indent, unsigned depth); |
152 | | void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
153 | | unsigned indent); |
154 | | |
155 | | void ReportPopUp(raw_ostream &o, const PathDiagnosticPopUpPiece &P, |
156 | | unsigned indent); |
157 | | }; |
158 | | |
159 | | } // end of anonymous namespace |
160 | | |
161 | | /// Print coverage information to output stream @c o. |
162 | | /// May modify the used list of files @c Fids by inserting new ones. |
163 | | static void printCoverage(const PathDiagnostic *D, |
164 | | unsigned InputIndentLevel, |
165 | | SmallVectorImpl<FileID> &Fids, |
166 | | FIDMap &FM, |
167 | | llvm::raw_fd_ostream &o); |
168 | | |
169 | | static std::optional<StringRef> getExpandedMacro( |
170 | | SourceLocation MacroLoc, const cross_tu::CrossTranslationUnitContext &CTU, |
171 | | const MacroExpansionContext &MacroExpansions, const SourceManager &SM); |
172 | | |
173 | | //===----------------------------------------------------------------------===// |
174 | | // Methods of PlistPrinter. |
175 | | //===----------------------------------------------------------------------===// |
176 | | |
177 | | void PlistPrinter::EmitRanges(raw_ostream &o, |
178 | | const ArrayRef<SourceRange> Ranges, |
179 | 2.11k | unsigned indent) { |
180 | | |
181 | 2.11k | if (Ranges.empty()) |
182 | 293 | return; |
183 | | |
184 | 1.82k | Indent(o, indent) << "<key>ranges</key>\n"; |
185 | 1.82k | Indent(o, indent) << "<array>\n"; |
186 | 1.82k | ++indent; |
187 | | |
188 | 1.82k | const SourceManager &SM = PP.getSourceManager(); |
189 | 1.82k | const LangOptions &LangOpts = PP.getLangOpts(); |
190 | | |
191 | 1.82k | for (auto &R : Ranges) |
192 | 1.94k | EmitRange(o, SM, |
193 | 1.94k | Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), |
194 | 1.94k | FM, indent + 1); |
195 | 1.82k | --indent; |
196 | 1.82k | Indent(o, indent) << "</array>\n"; |
197 | 1.82k | } |
198 | | |
199 | | void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, |
200 | 2.07k | unsigned indent) { |
201 | | // Output the text. |
202 | 2.07k | assert(!Message.empty()); |
203 | 2.07k | Indent(o, indent) << "<key>extended_message</key>\n"; |
204 | 2.07k | Indent(o, indent); |
205 | 2.07k | EmitString(o, Message) << '\n'; |
206 | | |
207 | | // Output the short text. |
208 | | // FIXME: Really use a short string. |
209 | 2.07k | Indent(o, indent) << "<key>message</key>\n"; |
210 | 2.07k | Indent(o, indent); |
211 | 2.07k | EmitString(o, Message) << '\n'; |
212 | 2.07k | } |
213 | | |
214 | | void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, |
215 | 2.04k | unsigned indent) { |
216 | 2.04k | if (fixits.size() == 0) |
217 | 2.04k | return; |
218 | | |
219 | 1 | const SourceManager &SM = PP.getSourceManager(); |
220 | 1 | const LangOptions &LangOpts = PP.getLangOpts(); |
221 | | |
222 | 1 | Indent(o, indent) << "<key>fixits</key>\n"; |
223 | 1 | Indent(o, indent) << "<array>\n"; |
224 | 1 | for (const auto &fixit : fixits) { |
225 | 1 | assert(!fixit.isNull()); |
226 | | // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. |
227 | 1 | assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!"); |
228 | 1 | assert(!fixit.BeforePreviousInsertions && "Not implemented yet!"); |
229 | 1 | Indent(o, indent) << " <dict>\n"; |
230 | 1 | Indent(o, indent) << " <key>remove_range</key>\n"; |
231 | 1 | EmitRange(o, SM, Lexer::getAsCharRange(fixit.RemoveRange, SM, LangOpts), |
232 | 1 | FM, indent + 2); |
233 | 1 | Indent(o, indent) << " <key>insert_string</key>"; |
234 | 1 | EmitString(o, fixit.CodeToInsert); |
235 | 1 | o << "\n"; |
236 | 1 | Indent(o, indent) << " </dict>\n"; |
237 | 1 | } |
238 | 1 | Indent(o, indent) << "</array>\n"; |
239 | 1 | } |
240 | | |
241 | | void PlistPrinter::ReportControlFlow(raw_ostream &o, |
242 | | const PathDiagnosticControlFlowPiece& P, |
243 | 2.21k | unsigned indent) { |
244 | | |
245 | 2.21k | const SourceManager &SM = PP.getSourceManager(); |
246 | 2.21k | const LangOptions &LangOpts = PP.getLangOpts(); |
247 | | |
248 | 2.21k | Indent(o, indent) << "<dict>\n"; |
249 | 2.21k | ++indent; |
250 | | |
251 | 2.21k | Indent(o, indent) << "<key>kind</key><string>control</string>\n"; |
252 | | |
253 | | // Emit edges. |
254 | 2.21k | Indent(o, indent) << "<key>edges</key>\n"; |
255 | 2.21k | ++indent; |
256 | 2.21k | Indent(o, indent) << "<array>\n"; |
257 | 2.21k | ++indent; |
258 | 2.21k | for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); |
259 | 4.42k | I!=E; ++I2.21k ) { |
260 | 2.21k | Indent(o, indent) << "<dict>\n"; |
261 | 2.21k | ++indent; |
262 | | |
263 | | // Make the ranges of the start and end point self-consistent with adjacent edges |
264 | | // by forcing to use only the beginning of the range. This simplifies the layout |
265 | | // logic for clients. |
266 | 2.21k | Indent(o, indent) << "<key>start</key>\n"; |
267 | 2.21k | SourceRange StartEdge( |
268 | 2.21k | SM.getExpansionLoc(I->getStart().asRange().getBegin())); |
269 | 2.21k | EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM, |
270 | 2.21k | indent + 1); |
271 | | |
272 | 2.21k | Indent(o, indent) << "<key>end</key>\n"; |
273 | 2.21k | SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin())); |
274 | 2.21k | EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM, |
275 | 2.21k | indent + 1); |
276 | | |
277 | 2.21k | --indent; |
278 | 2.21k | Indent(o, indent) << "</dict>\n"; |
279 | 2.21k | } |
280 | 2.21k | --indent; |
281 | 2.21k | Indent(o, indent) << "</array>\n"; |
282 | 2.21k | --indent; |
283 | | |
284 | | // Output any helper text. |
285 | 2.21k | const auto &s = P.getString(); |
286 | 2.21k | if (!s.empty()) { |
287 | 0 | Indent(o, indent) << "<key>alternate</key>"; |
288 | 0 | EmitString(o, s) << '\n'; |
289 | 0 | } |
290 | | |
291 | 2.21k | assert(P.getFixits().size() == 0 && |
292 | 2.21k | "Fixits on constrol flow pieces are not implemented yet!"); |
293 | | |
294 | 2.21k | --indent; |
295 | 2.21k | Indent(o, indent) << "</dict>\n"; |
296 | 2.21k | } |
297 | | |
298 | | void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
299 | | unsigned indent, unsigned depth, |
300 | 2.04k | bool isKeyEvent) { |
301 | | |
302 | 2.04k | const SourceManager &SM = PP.getSourceManager(); |
303 | | |
304 | 2.04k | Indent(o, indent) << "<dict>\n"; |
305 | 2.04k | ++indent; |
306 | | |
307 | 2.04k | Indent(o, indent) << "<key>kind</key><string>event</string>\n"; |
308 | | |
309 | 2.04k | if (isKeyEvent) { |
310 | 2 | Indent(o, indent) << "<key>key_event</key><true/>\n"; |
311 | 2 | } |
312 | | |
313 | | // Output the location. |
314 | 2.04k | FullSourceLoc L = P.getLocation().asLocation(); |
315 | | |
316 | 2.04k | Indent(o, indent) << "<key>location</key>\n"; |
317 | 2.04k | EmitLocation(o, SM, L, FM, indent); |
318 | | |
319 | | // Output the ranges (if any). |
320 | 2.04k | ArrayRef<SourceRange> Ranges = P.getRanges(); |
321 | 2.04k | EmitRanges(o, Ranges, indent); |
322 | | |
323 | | // Output the call depth. |
324 | 2.04k | Indent(o, indent) << "<key>depth</key>"; |
325 | 2.04k | EmitInteger(o, depth) << '\n'; |
326 | | |
327 | | // Output the text. |
328 | 2.04k | EmitMessage(o, P.getString(), indent); |
329 | | |
330 | | // Output the fixits. |
331 | 2.04k | EmitFixits(o, P.getFixits(), indent); |
332 | | |
333 | | // Finish up. |
334 | 2.04k | --indent; |
335 | 2.04k | Indent(o, indent); o << "</dict>\n"; |
336 | 2.04k | } |
337 | | |
338 | | void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
339 | | unsigned indent, |
340 | 153 | unsigned depth) { |
341 | | |
342 | 153 | if (auto callEnter = P.getCallEnterEvent()) |
343 | 152 | ReportPiece(o, *callEnter, indent, depth, /*includeControlFlow*/ true, |
344 | 152 | P.isLastInMainSourceFile()); |
345 | | |
346 | | |
347 | 153 | ++depth; |
348 | | |
349 | 153 | if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) |
350 | 141 | ReportPiece(o, *callEnterWithinCaller, indent, depth, |
351 | 141 | /*includeControlFlow*/ true); |
352 | | |
353 | 664 | for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I511 ) |
354 | 511 | ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ true); |
355 | | |
356 | 153 | --depth; |
357 | | |
358 | 153 | if (auto callExit = P.getCallExitEvent()) |
359 | 80 | ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true); |
360 | | |
361 | 153 | assert(P.getFixits().size() == 0 && |
362 | 153 | "Fixits on call pieces are not implemented yet!"); |
363 | 153 | } |
364 | | |
365 | | void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, |
366 | | const PathDiagnosticMacroPiece& P, |
367 | 46 | unsigned indent, unsigned depth) { |
368 | 46 | MacroPieces.push_back(&P); |
369 | | |
370 | 94 | for (const auto &SubPiece : P.subPieces) { |
371 | 94 | ReportPiece(o, *SubPiece, indent, depth, /*includeControlFlow*/ false); |
372 | 94 | } |
373 | | |
374 | 46 | assert(P.getFixits().size() == 0 && |
375 | 46 | "Fixits on constrol flow pieces are not implemented yet!"); |
376 | 46 | } |
377 | | |
378 | 42 | void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { |
379 | | |
380 | 46 | for (const PathDiagnosticMacroPiece *P : MacroPieces) { |
381 | 46 | const SourceManager &SM = PP.getSourceManager(); |
382 | | |
383 | 46 | SourceLocation MacroExpansionLoc = |
384 | 46 | P->getLocation().asLocation().getExpansionLoc(); |
385 | | |
386 | 46 | const std::optional<StringRef> MacroName = |
387 | 46 | MacroExpansions.getOriginalText(MacroExpansionLoc); |
388 | 46 | const std::optional<StringRef> ExpansionText = |
389 | 46 | getExpandedMacro(MacroExpansionLoc, CTU, MacroExpansions, SM); |
390 | | |
391 | 46 | if (!MacroName || !ExpansionText40 ) |
392 | 6 | continue; |
393 | | |
394 | 40 | Indent(o, indent) << "<dict>\n"; |
395 | 40 | ++indent; |
396 | | |
397 | | // Output the location. |
398 | 40 | FullSourceLoc L = P->getLocation().asLocation(); |
399 | | |
400 | 40 | Indent(o, indent) << "<key>location</key>\n"; |
401 | 40 | EmitLocation(o, SM, L, FM, indent); |
402 | | |
403 | | // Output the ranges (if any). |
404 | 40 | ArrayRef<SourceRange> Ranges = P->getRanges(); |
405 | 40 | EmitRanges(o, Ranges, indent); |
406 | | |
407 | | // Output the macro name. |
408 | 40 | Indent(o, indent) << "<key>name</key>"; |
409 | 40 | EmitString(o, *MacroName) << '\n'; |
410 | | |
411 | | // Output what it expands into. |
412 | 40 | Indent(o, indent) << "<key>expansion</key>"; |
413 | 40 | EmitString(o, *ExpansionText) << '\n'; |
414 | | |
415 | | // Finish up. |
416 | 40 | --indent; |
417 | 40 | Indent(o, indent); |
418 | 40 | o << "</dict>\n"; |
419 | 40 | } |
420 | 42 | } |
421 | | |
422 | | void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
423 | 1 | unsigned indent) { |
424 | | |
425 | 1 | const SourceManager &SM = PP.getSourceManager(); |
426 | | |
427 | 1 | Indent(o, indent) << "<dict>\n"; |
428 | 1 | ++indent; |
429 | | |
430 | | // Output the location. |
431 | 1 | FullSourceLoc L = P.getLocation().asLocation(); |
432 | | |
433 | 1 | Indent(o, indent) << "<key>location</key>\n"; |
434 | 1 | EmitLocation(o, SM, L, FM, indent); |
435 | | |
436 | | // Output the ranges (if any). |
437 | 1 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
438 | 1 | EmitRanges(o, Ranges, indent); |
439 | | |
440 | | // Output the text. |
441 | 1 | EmitMessage(o, P.getString(), indent); |
442 | | |
443 | | // Output the fixits. |
444 | 1 | EmitFixits(o, P.getFixits(), indent); |
445 | | |
446 | | // Finish up. |
447 | 1 | --indent; |
448 | 1 | Indent(o, indent); o << "</dict>\n"; |
449 | 1 | } |
450 | | |
451 | | void PlistPrinter::ReportPopUp(raw_ostream &o, |
452 | | const PathDiagnosticPopUpPiece &P, |
453 | 33 | unsigned indent) { |
454 | 33 | const SourceManager &SM = PP.getSourceManager(); |
455 | | |
456 | 33 | Indent(o, indent) << "<dict>\n"; |
457 | 33 | ++indent; |
458 | | |
459 | 33 | Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n"; |
460 | | |
461 | | // Output the location. |
462 | 33 | FullSourceLoc L = P.getLocation().asLocation(); |
463 | | |
464 | 33 | Indent(o, indent) << "<key>location</key>\n"; |
465 | 33 | EmitLocation(o, SM, L, FM, indent); |
466 | | |
467 | | // Output the ranges (if any). |
468 | 33 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
469 | 33 | EmitRanges(o, Ranges, indent); |
470 | | |
471 | | // Output the text. |
472 | 33 | EmitMessage(o, P.getString(), indent); |
473 | | |
474 | 33 | assert(P.getFixits().size() == 0 && |
475 | 33 | "Fixits on pop-up pieces are not implemented yet!"); |
476 | | |
477 | | // Finish up. |
478 | 33 | --indent; |
479 | 33 | Indent(o, indent) << "</dict>\n"; |
480 | 33 | } |
481 | | |
482 | | //===----------------------------------------------------------------------===// |
483 | | // Static function definitions. |
484 | | //===----------------------------------------------------------------------===// |
485 | | |
486 | | /// Print coverage information to output stream @c o. |
487 | | /// May modify the used list of files @c Fids by inserting new ones. |
488 | | static void printCoverage(const PathDiagnostic *D, |
489 | | unsigned InputIndentLevel, |
490 | | SmallVectorImpl<FileID> &Fids, |
491 | | FIDMap &FM, |
492 | 633 | llvm::raw_fd_ostream &o) { |
493 | 633 | unsigned IndentLevel = InputIndentLevel; |
494 | | |
495 | 633 | Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n"; |
496 | 633 | Indent(o, IndentLevel) << "<dict>\n"; |
497 | 633 | IndentLevel++; |
498 | | |
499 | | // Mapping from file IDs to executed lines. |
500 | 633 | const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); |
501 | 649 | for (const auto &[FID, Lines] : ExecutedLines) { |
502 | 649 | unsigned FileKey = AddFID(FM, Fids, FID); |
503 | 649 | Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n"; |
504 | 649 | Indent(o, IndentLevel) << "<array>\n"; |
505 | 649 | IndentLevel++; |
506 | 3.44k | for (unsigned LineNo : Lines) { |
507 | 3.44k | Indent(o, IndentLevel); |
508 | 3.44k | EmitInteger(o, LineNo) << "\n"; |
509 | 3.44k | } |
510 | 649 | IndentLevel--; |
511 | 649 | Indent(o, IndentLevel) << "</array>\n"; |
512 | 649 | } |
513 | 633 | IndentLevel--; |
514 | 633 | Indent(o, IndentLevel) << "</dict>\n"; |
515 | | |
516 | 633 | assert(IndentLevel == InputIndentLevel); |
517 | 633 | } |
518 | | |
519 | | //===----------------------------------------------------------------------===// |
520 | | // Methods of PlistDiagnostics. |
521 | | //===----------------------------------------------------------------------===// |
522 | | |
523 | | PlistDiagnostics::PlistDiagnostics( |
524 | | PathDiagnosticConsumerOptions DiagOpts, const std::string &output, |
525 | | const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, |
526 | | const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles) |
527 | 69 | : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), |
528 | 69 | MacroExpansions(MacroExpansions), |
529 | 69 | SupportsCrossFileDiagnostics(supportsMultipleFiles) { |
530 | | // FIXME: Will be used by a later planned change. |
531 | 69 | (void)this->CTU; |
532 | 69 | } |
533 | | |
534 | | void ento::createPlistDiagnosticConsumer( |
535 | | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
536 | | const std::string &OutputFile, const Preprocessor &PP, |
537 | | const cross_tu::CrossTranslationUnitContext &CTU, |
538 | 47 | const MacroExpansionContext &MacroExpansions) { |
539 | | |
540 | | // TODO: Emit an error here. |
541 | 47 | if (OutputFile.empty()) |
542 | 0 | return; |
543 | | |
544 | 47 | C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, |
545 | 47 | MacroExpansions, |
546 | 47 | /*supportsMultipleFiles=*/false)); |
547 | 47 | createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, |
548 | 47 | PP, CTU, MacroExpansions); |
549 | 47 | } |
550 | | |
551 | | void ento::createPlistMultiFileDiagnosticConsumer( |
552 | | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
553 | | const std::string &OutputFile, const Preprocessor &PP, |
554 | | const cross_tu::CrossTranslationUnitContext &CTU, |
555 | 22 | const MacroExpansionContext &MacroExpansions) { |
556 | | |
557 | | // TODO: Emit an error here. |
558 | 22 | if (OutputFile.empty()) |
559 | 0 | return; |
560 | | |
561 | 22 | C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, |
562 | 22 | MacroExpansions, |
563 | 22 | /*supportsMultipleFiles=*/true)); |
564 | 22 | createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, |
565 | 22 | PP, CTU, MacroExpansions); |
566 | 22 | } |
567 | | |
568 | | void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
569 | 633 | const PathPieces &Path) { |
570 | 633 | PlistPrinter Printer(FM, PP, CTU, MacroExpansions); |
571 | 633 | assert(std::is_partitioned(Path.begin(), Path.end(), |
572 | 633 | [](const PathDiagnosticPieceRef &E) { |
573 | 633 | return E->getKind() == PathDiagnosticPiece::Note; |
574 | 633 | }) && |
575 | 633 | "PathDiagnostic is not partitioned so that notes precede the rest"); |
576 | | |
577 | 633 | PathPieces::const_iterator FirstNonNote = std::partition_point( |
578 | 1.79k | Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) { |
579 | 1.79k | return E->getKind() == PathDiagnosticPiece::Note; |
580 | 1.79k | }); |
581 | | |
582 | 633 | PathPieces::const_iterator I = Path.begin(); |
583 | | |
584 | 633 | if (FirstNonNote != Path.begin()) { |
585 | 1 | o << " <key>notes</key>\n" |
586 | 1 | " <array>\n"; |
587 | | |
588 | 2 | for (; I != FirstNonNote; ++I1 ) |
589 | 1 | Printer.ReportDiag(o, **I); |
590 | | |
591 | 1 | o << " </array>\n"; |
592 | 1 | } |
593 | | |
594 | 633 | o << " <key>path</key>\n"; |
595 | | |
596 | 633 | o << " <array>\n"; |
597 | | |
598 | 633 | for (const auto &Piece : llvm::make_range(I, Path.end())) |
599 | 3.55k | Printer.ReportDiag(o, *Piece); |
600 | | |
601 | 633 | o << " </array>\n"; |
602 | | |
603 | 633 | if (!DiagOpts.ShouldDisplayMacroExpansions) |
604 | 591 | return; |
605 | | |
606 | 42 | o << " <key>macro_expansions</key>\n" |
607 | 42 | " <array>\n"; |
608 | 42 | Printer.ReportMacroExpansions(o, /* indent */ 4); |
609 | 42 | o << " </array>\n"; |
610 | 42 | } |
611 | | |
612 | | void PlistDiagnostics::FlushDiagnosticsImpl( |
613 | | std::vector<const PathDiagnostic *> &Diags, |
614 | 69 | FilesMade *filesMade) { |
615 | | // Build up a set of FIDs that we use by scanning the locations and |
616 | | // ranges of the diagnostics. |
617 | 69 | FIDMap FM; |
618 | 69 | SmallVector<FileID, 10> Fids; |
619 | 69 | const SourceManager& SM = PP.getSourceManager(); |
620 | 69 | const LangOptions &LangOpts = PP.getLangOpts(); |
621 | | |
622 | 4.45k | auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { |
623 | 4.45k | AddFID(FM, Fids, SM, Piece.getLocation().asLocation()); |
624 | 4.45k | ArrayRef<SourceRange> Ranges = Piece.getRanges(); |
625 | 4.45k | for (const SourceRange &Range : Ranges) { |
626 | 1.86k | AddFID(FM, Fids, SM, Range.getBegin()); |
627 | 1.86k | AddFID(FM, Fids, SM, Range.getEnd()); |
628 | 1.86k | } |
629 | 4.45k | }; |
630 | | |
631 | 633 | for (const PathDiagnostic *D : Diags) { |
632 | | |
633 | 633 | SmallVector<const PathPieces *, 5> WorkList; |
634 | 633 | WorkList.push_back(&D->path); |
635 | | |
636 | 1.46k | while (!WorkList.empty()) { |
637 | 832 | const PathPieces &Path = *WorkList.pop_back_val(); |
638 | | |
639 | 4.15k | for (const auto &Iter : Path) { |
640 | 4.15k | const PathDiagnosticPiece &Piece = *Iter; |
641 | 4.15k | AddPieceFID(Piece); |
642 | | |
643 | 4.15k | if (const PathDiagnosticCallPiece *Call = |
644 | 4.15k | dyn_cast<PathDiagnosticCallPiece>(&Piece)) { |
645 | 153 | if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) |
646 | 141 | AddPieceFID(*CallEnterWithin); |
647 | | |
648 | 153 | if (auto CallEnterEvent = Call->getCallEnterEvent()) |
649 | 152 | AddPieceFID(*CallEnterEvent); |
650 | | |
651 | 153 | WorkList.push_back(&Call->path); |
652 | 4.00k | } else if (const PathDiagnosticMacroPiece *Macro = |
653 | 4.00k | dyn_cast<PathDiagnosticMacroPiece>(&Piece)) { |
654 | 46 | WorkList.push_back(&Macro->subPieces); |
655 | 46 | } |
656 | 4.15k | } |
657 | 832 | } |
658 | 633 | } |
659 | | |
660 | | // Open the file. |
661 | 69 | std::error_code EC; |
662 | 69 | llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
663 | 69 | if (EC) { |
664 | 0 | llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; |
665 | 0 | return; |
666 | 0 | } |
667 | | |
668 | 69 | EmitPlistHeader(o); |
669 | | |
670 | | // Write the root object: a <dict> containing... |
671 | | // - "clang_version", the string representation of clang version |
672 | | // - "files", an <array> mapping from FIDs to file names |
673 | | // - "diagnostics", an <array> containing the path diagnostics |
674 | 69 | o << "<dict>\n" << |
675 | 69 | " <key>clang_version</key>\n"; |
676 | 69 | EmitString(o, getClangFullVersion()) << '\n'; |
677 | 69 | o << " <key>diagnostics</key>\n" |
678 | 69 | " <array>\n"; |
679 | | |
680 | 69 | for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), |
681 | 702 | DE = Diags.end(); DI!=DE; ++DI633 ) { |
682 | | |
683 | 633 | o << " <dict>\n"; |
684 | | |
685 | 633 | const PathDiagnostic *D = *DI; |
686 | 633 | printBugPath(o, FM, D->path); |
687 | | |
688 | | // Output the bug type and bug category. |
689 | 633 | o << " <key>description</key>"; |
690 | 633 | EmitString(o, D->getShortDescription()) << '\n'; |
691 | 633 | o << " <key>category</key>"; |
692 | 633 | EmitString(o, D->getCategory()) << '\n'; |
693 | 633 | o << " <key>type</key>"; |
694 | 633 | EmitString(o, D->getBugType()) << '\n'; |
695 | 633 | o << " <key>check_name</key>"; |
696 | 633 | EmitString(o, D->getCheckerName()) << '\n'; |
697 | | |
698 | 633 | o << " <!-- This hash is experimental and going to change! -->\n"; |
699 | 633 | o << " <key>issue_hash_content_of_line_in_context</key>"; |
700 | 633 | PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); |
701 | 633 | FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid() |
702 | 633 | ? UPDLoc.asLocation()212 |
703 | 633 | : D->getLocation().asLocation()421 ), |
704 | 633 | SM); |
705 | 633 | const Decl *DeclWithIssue = D->getDeclWithIssue(); |
706 | 633 | EmitString(o, getIssueHash(L, D->getCheckerName(), D->getBugType(), |
707 | 633 | DeclWithIssue, LangOpts)) |
708 | 633 | << '\n'; |
709 | | |
710 | | // Output information about the semantic context where |
711 | | // the issue occurred. |
712 | 633 | if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { |
713 | | // FIXME: handle blocks, which have no name. |
714 | 631 | if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { |
715 | 626 | StringRef declKind; |
716 | 626 | switch (ND->getKind()) { |
717 | 0 | case Decl::CXXRecord: |
718 | 0 | declKind = "C++ class"; |
719 | 0 | break; |
720 | 7 | case Decl::CXXMethod: |
721 | 7 | declKind = "C++ method"; |
722 | 7 | break; |
723 | 55 | case Decl::ObjCMethod: |
724 | 55 | declKind = "Objective-C method"; |
725 | 55 | break; |
726 | 548 | case Decl::Function: |
727 | 548 | declKind = "function"; |
728 | 548 | break; |
729 | 16 | default: |
730 | 16 | break; |
731 | 626 | } |
732 | 626 | if (!declKind.empty()) { |
733 | 610 | const std::string &declName = ND->getDeclName().getAsString(); |
734 | 610 | o << " <key>issue_context_kind</key>"; |
735 | 610 | EmitString(o, declKind) << '\n'; |
736 | 610 | o << " <key>issue_context</key>"; |
737 | 610 | EmitString(o, declName) << '\n'; |
738 | 610 | } |
739 | | |
740 | | // Output the bug hash for issue unique-ing. Currently, it's just an |
741 | | // offset from the beginning of the function. |
742 | 626 | if (const Stmt *Body = DeclWithIssue->getBody()) { |
743 | | |
744 | | // If the bug uniqueing location exists, use it for the hash. |
745 | | // For example, this ensures that two leaks reported on the same line |
746 | | // will have different issue_hashes and that the hash will identify |
747 | | // the leak location even after code is added between the allocation |
748 | | // site and the end of scope (leak report location). |
749 | 626 | if (UPDLoc.isValid()) { |
750 | 209 | FullSourceLoc UFunL( |
751 | 209 | SM.getExpansionLoc( |
752 | 209 | D->getUniqueingDecl()->getBody()->getBeginLoc()), |
753 | 209 | SM); |
754 | 209 | o << " <key>issue_hash_function_offset</key><string>" |
755 | 209 | << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() |
756 | 209 | << "</string>\n"; |
757 | | |
758 | | // Otherwise, use the location on which the bug is reported. |
759 | 417 | } else { |
760 | 417 | FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM); |
761 | 417 | o << " <key>issue_hash_function_offset</key><string>" |
762 | 417 | << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() |
763 | 417 | << "</string>\n"; |
764 | 417 | } |
765 | | |
766 | 626 | } |
767 | 626 | } |
768 | 631 | } |
769 | | |
770 | | // Output the location of the bug. |
771 | 633 | o << " <key>location</key>\n"; |
772 | 633 | EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2); |
773 | | |
774 | | // Output the diagnostic to the sub-diagnostic client, if any. |
775 | 633 | if (!filesMade->empty()) { |
776 | 2 | StringRef lastName; |
777 | 2 | PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D); |
778 | 2 | if (files) { |
779 | 2 | for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), |
780 | 4 | CE = files->end(); CI != CE; ++CI2 ) { |
781 | 2 | StringRef newName = CI->first; |
782 | 2 | if (newName != lastName) { |
783 | 2 | if (!lastName.empty()) { |
784 | 0 | o << " </array>\n"; |
785 | 0 | } |
786 | 2 | lastName = newName; |
787 | 2 | o << " <key>" << lastName << "_files</key>\n"; |
788 | 2 | o << " <array>\n"; |
789 | 2 | } |
790 | 2 | o << " <string>" << CI->second << "</string>\n"; |
791 | 2 | } |
792 | 2 | o << " </array>\n"; |
793 | 2 | } |
794 | 2 | } |
795 | | |
796 | 633 | printCoverage(D, /*IndentLevel=*/2, Fids, FM, o); |
797 | | |
798 | | // Close up the entry. |
799 | 633 | o << " </dict>\n"; |
800 | 633 | } |
801 | | |
802 | 69 | o << " </array>\n"; |
803 | | |
804 | 69 | o << " <key>files</key>\n" |
805 | 69 | " <array>\n"; |
806 | 69 | for (FileID FID : Fids) |
807 | 70 | EmitString(o << " ", SM.getFileEntryRefForID(FID)->getName()) << '\n'; |
808 | 69 | o << " </array>\n"; |
809 | | |
810 | 69 | if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats1 ) { |
811 | 1 | o << " <key>statistics</key>\n"; |
812 | 1 | std::string stats; |
813 | 1 | llvm::raw_string_ostream os(stats); |
814 | 1 | llvm::PrintStatisticsJSON(os); |
815 | 1 | os.flush(); |
816 | 1 | EmitString(o, html::EscapeText(stats)) << '\n'; |
817 | 1 | } |
818 | | |
819 | | // Finish. |
820 | 69 | o << "</dict>\n</plist>\n"; |
821 | 69 | } |
822 | | |
823 | | //===----------------------------------------------------------------------===// |
824 | | // Definitions of helper functions and methods for expanding macros. |
825 | | //===----------------------------------------------------------------------===// |
826 | | |
827 | | static std::optional<StringRef> |
828 | | getExpandedMacro(SourceLocation MacroExpansionLoc, |
829 | | const cross_tu::CrossTranslationUnitContext &CTU, |
830 | | const MacroExpansionContext &MacroExpansions, |
831 | 46 | const SourceManager &SM) { |
832 | 46 | if (auto CTUMacroExpCtx = |
833 | 46 | CTU.getMacroExpansionContextForSourceLocation(MacroExpansionLoc)) { |
834 | 0 | return CTUMacroExpCtx->getExpandedText(MacroExpansionLoc); |
835 | 0 | } |
836 | 46 | return MacroExpansions.getExpandedText(MacroExpansionLoc); |
837 | 46 | } |