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 | ~PlistDiagnostics() override {} |
62 | |
63 | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
64 | FilesMade *filesMade) override; |
65 | |
66 | StringRef getName() const override { |
67 | return "PlistDiagnostics" ; |
68 | } |
69 | |
70 | PathGenerationScheme getGenerationScheme() const override { |
71 | return Extensive; |
72 | } |
73 | bool supportsLogicalOpControlFlow() const override { return true; } |
74 | bool supportsCrossFileDiagnostics() const override { |
75 | return SupportsCrossFileDiagnostics; |
76 | } |
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 | : FM(FM), PP(PP), CTU(CTU), MacroExpansions(MacroExpansions) {} |
95 | |
96 | void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { |
97 | ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); |
98 | } |
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 | bool isKeyEvent = false) { |
112 | switch (P.getKind()) { |
113 | case PathDiagnosticPiece::ControlFlow: |
114 | if (includeControlFlow) |
115 | ReportControlFlow(o, P: cast<PathDiagnosticControlFlowPiece>(Val: P), indent); |
116 | break; |
117 | case PathDiagnosticPiece::Call: |
118 | ReportCall(o, P: cast<PathDiagnosticCallPiece>(Val: P), indent, |
119 | depth); |
120 | break; |
121 | case PathDiagnosticPiece::Event: |
122 | ReportEvent(o, P: cast<PathDiagnosticEventPiece>(Val: P), indent, depth, |
123 | isKeyEvent); |
124 | break; |
125 | case PathDiagnosticPiece::Macro: |
126 | ReportMacroSubPieces(o, P: cast<PathDiagnosticMacroPiece>(Val: P), indent, |
127 | depth); |
128 | break; |
129 | case PathDiagnosticPiece::Note: |
130 | ReportNote(o, P: cast<PathDiagnosticNotePiece>(Val: P), indent); |
131 | break; |
132 | case PathDiagnosticPiece::PopUp: |
133 | ReportPopUp(o, P: cast<PathDiagnosticPopUpPiece>(Val: P), indent); |
134 | break; |
135 | } |
136 | } |
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 | unsigned indent) { |
180 | |
181 | if (Ranges.empty()) |
182 | return; |
183 | |
184 | Indent(o, indent) << "<key>ranges</key>\n" ; |
185 | Indent(o, indent) << "<array>\n" ; |
186 | ++indent; |
187 | |
188 | const SourceManager &SM = PP.getSourceManager(); |
189 | const LangOptions &LangOpts = PP.getLangOpts(); |
190 | |
191 | for (auto &R : Ranges) |
192 | EmitRange(o, SM, |
193 | R: Lexer::getAsCharRange(Range: SM.getExpansionRange(Range: R), SM, LangOpts), |
194 | FM, indent: indent + 1); |
195 | --indent; |
196 | Indent(o, indent) << "</array>\n" ; |
197 | } |
198 | |
199 | void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, |
200 | unsigned indent) { |
201 | // Output the text. |
202 | assert(!Message.empty()); |
203 | Indent(o, indent) << "<key>extended_message</key>\n" ; |
204 | Indent(o, indent); |
205 | EmitString(o, s: Message) << '\n'; |
206 | |
207 | // Output the short text. |
208 | // FIXME: Really use a short string. |
209 | Indent(o, indent) << "<key>message</key>\n" ; |
210 | Indent(o, indent); |
211 | EmitString(o, s: Message) << '\n'; |
212 | } |
213 | |
214 | void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, |
215 | unsigned indent) { |
216 | if (fixits.size() == 0) |
217 | return; |
218 | |
219 | const SourceManager &SM = PP.getSourceManager(); |
220 | const LangOptions &LangOpts = PP.getLangOpts(); |
221 | |
222 | Indent(o, indent) << "<key>fixits</key>\n" ; |
223 | Indent(o, indent) << "<array>\n" ; |
224 | for (const auto &fixit : fixits) { |
225 | assert(!fixit.isNull()); |
226 | // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. |
227 | assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!" ); |
228 | assert(!fixit.BeforePreviousInsertions && "Not implemented yet!" ); |
229 | Indent(o, indent) << " <dict>\n" ; |
230 | Indent(o, indent) << " <key>remove_range</key>\n" ; |
231 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: fixit.RemoveRange, SM, LangOpts), |
232 | FM, indent: indent + 2); |
233 | Indent(o, indent) << " <key>insert_string</key>" ; |
234 | EmitString(o, s: fixit.CodeToInsert); |
235 | o << "\n" ; |
236 | Indent(o, indent) << " </dict>\n" ; |
237 | } |
238 | Indent(o, indent) << "</array>\n" ; |
239 | } |
240 | |
241 | void PlistPrinter::ReportControlFlow(raw_ostream &o, |
242 | const PathDiagnosticControlFlowPiece& P, |
243 | unsigned indent) { |
244 | |
245 | const SourceManager &SM = PP.getSourceManager(); |
246 | const LangOptions &LangOpts = PP.getLangOpts(); |
247 | |
248 | Indent(o, indent) << "<dict>\n" ; |
249 | ++indent; |
250 | |
251 | Indent(o, indent) << "<key>kind</key><string>control</string>\n" ; |
252 | |
253 | // Emit edges. |
254 | Indent(o, indent) << "<key>edges</key>\n" ; |
255 | ++indent; |
256 | Indent(o, indent) << "<array>\n" ; |
257 | ++indent; |
258 | for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); |
259 | I!=E; ++I) { |
260 | Indent(o, indent) << "<dict>\n" ; |
261 | ++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 | Indent(o, indent) << "<key>start</key>\n" ; |
267 | SourceRange StartEdge( |
268 | SM.getExpansionLoc(Loc: I->getStart().asRange().getBegin())); |
269 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: StartEdge, SM, LangOpts), FM, |
270 | indent: indent + 1); |
271 | |
272 | Indent(o, indent) << "<key>end</key>\n" ; |
273 | SourceRange EndEdge(SM.getExpansionLoc(Loc: I->getEnd().asRange().getBegin())); |
274 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: EndEdge, SM, LangOpts), FM, |
275 | indent: indent + 1); |
276 | |
277 | --indent; |
278 | Indent(o, indent) << "</dict>\n" ; |
279 | } |
280 | --indent; |
281 | Indent(o, indent) << "</array>\n" ; |
282 | --indent; |
283 | |
284 | // Output any helper text. |
285 | const auto &s = P.getString(); |
286 | if (!s.empty()) { |
287 | Indent(o, indent) << "<key>alternate</key>" ; |
288 | EmitString(o, s) << '\n'; |
289 | } |
290 | |
291 | assert(P.getFixits().size() == 0 && |
292 | "Fixits on constrol flow pieces are not implemented yet!" ); |
293 | |
294 | --indent; |
295 | Indent(o, indent) << "</dict>\n" ; |
296 | } |
297 | |
298 | void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
299 | unsigned indent, unsigned depth, |
300 | bool isKeyEvent) { |
301 | |
302 | const SourceManager &SM = PP.getSourceManager(); |
303 | |
304 | Indent(o, indent) << "<dict>\n" ; |
305 | ++indent; |
306 | |
307 | Indent(o, indent) << "<key>kind</key><string>event</string>\n" ; |
308 | |
309 | if (isKeyEvent) { |
310 | Indent(o, indent) << "<key>key_event</key><true/>\n" ; |
311 | } |
312 | |
313 | // Output the location. |
314 | FullSourceLoc L = P.getLocation().asLocation(); |
315 | |
316 | Indent(o, indent) << "<key>location</key>\n" ; |
317 | EmitLocation(o, SM, L, FM, indent); |
318 | |
319 | // Output the ranges (if any). |
320 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
321 | EmitRanges(o, Ranges, indent); |
322 | |
323 | // Output the call depth. |
324 | Indent(o, indent) << "<key>depth</key>" ; |
325 | EmitInteger(o, value: depth) << '\n'; |
326 | |
327 | // Output the text. |
328 | EmitMessage(o, Message: P.getString(), indent); |
329 | |
330 | // Output the fixits. |
331 | EmitFixits(o, fixits: P.getFixits(), indent); |
332 | |
333 | // Finish up. |
334 | --indent; |
335 | Indent(o, indent); o << "</dict>\n" ; |
336 | } |
337 | |
338 | void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
339 | unsigned indent, |
340 | unsigned depth) { |
341 | |
342 | if (auto callEnter = P.getCallEnterEvent()) |
343 | ReportPiece(o, P: *callEnter, indent, depth, /*includeControlFlow*/ true, |
344 | isKeyEvent: P.isLastInMainSourceFile()); |
345 | |
346 | |
347 | ++depth; |
348 | |
349 | if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) |
350 | ReportPiece(o, P: *callEnterWithinCaller, indent, depth, |
351 | /*includeControlFlow*/ true); |
352 | |
353 | for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) |
354 | ReportPiece(o, P: **I, indent, depth, /*includeControlFlow*/ true); |
355 | |
356 | --depth; |
357 | |
358 | if (auto callExit = P.getCallExitEvent()) |
359 | ReportPiece(o, P: *callExit, indent, depth, /*includeControlFlow*/ true); |
360 | |
361 | assert(P.getFixits().size() == 0 && |
362 | "Fixits on call pieces are not implemented yet!" ); |
363 | } |
364 | |
365 | void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, |
366 | const PathDiagnosticMacroPiece& P, |
367 | unsigned indent, unsigned depth) { |
368 | MacroPieces.push_back(Elt: &P); |
369 | |
370 | for (const auto &SubPiece : P.subPieces) { |
371 | ReportPiece(o, P: *SubPiece, indent, depth, /*includeControlFlow*/ false); |
372 | } |
373 | |
374 | assert(P.getFixits().size() == 0 && |
375 | "Fixits on constrol flow pieces are not implemented yet!" ); |
376 | } |
377 | |
378 | void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { |
379 | |
380 | for (const PathDiagnosticMacroPiece *P : MacroPieces) { |
381 | const SourceManager &SM = PP.getSourceManager(); |
382 | |
383 | SourceLocation MacroExpansionLoc = |
384 | P->getLocation().asLocation().getExpansionLoc(); |
385 | |
386 | const std::optional<StringRef> MacroName = |
387 | MacroExpansions.getOriginalText(MacroExpansionLoc); |
388 | const std::optional<StringRef> ExpansionText = |
389 | getExpandedMacro(MacroLoc: MacroExpansionLoc, CTU, MacroExpansions, SM); |
390 | |
391 | if (!MacroName || !ExpansionText) |
392 | continue; |
393 | |
394 | Indent(o, indent) << "<dict>\n" ; |
395 | ++indent; |
396 | |
397 | // Output the location. |
398 | FullSourceLoc L = P->getLocation().asLocation(); |
399 | |
400 | Indent(o, indent) << "<key>location</key>\n" ; |
401 | EmitLocation(o, SM, L, FM, indent); |
402 | |
403 | // Output the ranges (if any). |
404 | ArrayRef<SourceRange> Ranges = P->getRanges(); |
405 | EmitRanges(o, Ranges, indent); |
406 | |
407 | // Output the macro name. |
408 | Indent(o, indent) << "<key>name</key>" ; |
409 | EmitString(o, s: *MacroName) << '\n'; |
410 | |
411 | // Output what it expands into. |
412 | Indent(o, indent) << "<key>expansion</key>" ; |
413 | EmitString(o, s: *ExpansionText) << '\n'; |
414 | |
415 | // Finish up. |
416 | --indent; |
417 | Indent(o, indent); |
418 | o << "</dict>\n" ; |
419 | } |
420 | } |
421 | |
422 | void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
423 | unsigned indent) { |
424 | |
425 | const SourceManager &SM = PP.getSourceManager(); |
426 | |
427 | Indent(o, indent) << "<dict>\n" ; |
428 | ++indent; |
429 | |
430 | // Output the location. |
431 | FullSourceLoc L = P.getLocation().asLocation(); |
432 | |
433 | Indent(o, indent) << "<key>location</key>\n" ; |
434 | EmitLocation(o, SM, L, FM, indent); |
435 | |
436 | // Output the ranges (if any). |
437 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
438 | EmitRanges(o, Ranges, indent); |
439 | |
440 | // Output the text. |
441 | EmitMessage(o, Message: P.getString(), indent); |
442 | |
443 | // Output the fixits. |
444 | EmitFixits(o, fixits: P.getFixits(), indent); |
445 | |
446 | // Finish up. |
447 | --indent; |
448 | Indent(o, indent); o << "</dict>\n" ; |
449 | } |
450 | |
451 | void PlistPrinter::(raw_ostream &o, |
452 | const PathDiagnosticPopUpPiece &P, |
453 | unsigned indent) { |
454 | const SourceManager &SM = PP.getSourceManager(); |
455 | |
456 | Indent(o, indent) << "<dict>\n" ; |
457 | ++indent; |
458 | |
459 | Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n" ; |
460 | |
461 | // Output the location. |
462 | FullSourceLoc L = P.getLocation().asLocation(); |
463 | |
464 | Indent(o, indent) << "<key>location</key>\n" ; |
465 | EmitLocation(o, SM, L, FM, indent); |
466 | |
467 | // Output the ranges (if any). |
468 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
469 | EmitRanges(o, Ranges, indent); |
470 | |
471 | // Output the text. |
472 | EmitMessage(o, Message: P.getString(), indent); |
473 | |
474 | assert(P.getFixits().size() == 0 && |
475 | "Fixits on pop-up pieces are not implemented yet!" ); |
476 | |
477 | // Finish up. |
478 | --indent; |
479 | Indent(o, indent) << "</dict>\n" ; |
480 | } |
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 | llvm::raw_fd_ostream &o) { |
493 | unsigned IndentLevel = InputIndentLevel; |
494 | |
495 | Indent(o, indent: IndentLevel) << "<key>ExecutedLines</key>\n" ; |
496 | Indent(o, indent: IndentLevel) << "<dict>\n" ; |
497 | IndentLevel++; |
498 | |
499 | // Mapping from file IDs to executed lines. |
500 | const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); |
501 | for (const auto &[FID, Lines] : ExecutedLines) { |
502 | unsigned FileKey = AddFID(FIDs&: FM, V&: Fids, FID); |
503 | Indent(o, indent: IndentLevel) << "<key>" << FileKey << "</key>\n" ; |
504 | Indent(o, indent: IndentLevel) << "<array>\n" ; |
505 | IndentLevel++; |
506 | for (unsigned LineNo : Lines) { |
507 | Indent(o, indent: IndentLevel); |
508 | EmitInteger(o, value: LineNo) << "\n" ; |
509 | } |
510 | IndentLevel--; |
511 | Indent(o, indent: IndentLevel) << "</array>\n" ; |
512 | } |
513 | IndentLevel--; |
514 | Indent(o, indent: IndentLevel) << "</dict>\n" ; |
515 | |
516 | assert(IndentLevel == InputIndentLevel); |
517 | } |
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 | : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), |
528 | MacroExpansions(MacroExpansions), |
529 | SupportsCrossFileDiagnostics(supportsMultipleFiles) { |
530 | // FIXME: Will be used by a later planned change. |
531 | (void)this->CTU; |
532 | } |
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 | const MacroExpansionContext &MacroExpansions) { |
539 | |
540 | // TODO: Emit an error here. |
541 | if (OutputFile.empty()) |
542 | return; |
543 | |
544 | C.push_back(x: new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, |
545 | MacroExpansions, |
546 | /*supportsMultipleFiles=*/false)); |
547 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
548 | PP, CTU, MacroExpansions); |
549 | } |
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 | const MacroExpansionContext &MacroExpansions) { |
556 | |
557 | // TODO: Emit an error here. |
558 | if (OutputFile.empty()) |
559 | return; |
560 | |
561 | C.push_back(x: new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, |
562 | MacroExpansions, |
563 | /*supportsMultipleFiles=*/true)); |
564 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
565 | PP, CTU, MacroExpansions); |
566 | } |
567 | |
568 | void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
569 | const PathPieces &Path) { |
570 | PlistPrinter Printer(FM, PP, CTU, MacroExpansions); |
571 | assert(std::is_partitioned(Path.begin(), Path.end(), |
572 | [](const PathDiagnosticPieceRef &E) { |
573 | return E->getKind() == PathDiagnosticPiece::Note; |
574 | }) && |
575 | "PathDiagnostic is not partitioned so that notes precede the rest" ); |
576 | |
577 | PathPieces::const_iterator FirstNonNote = std::partition_point( |
578 | first: Path.begin(), last: Path.end(), pred: [](const PathDiagnosticPieceRef &E) { |
579 | return E->getKind() == PathDiagnosticPiece::Note; |
580 | }); |
581 | |
582 | PathPieces::const_iterator I = Path.begin(); |
583 | |
584 | if (FirstNonNote != Path.begin()) { |
585 | o << " <key>notes</key>\n" |
586 | " <array>\n" ; |
587 | |
588 | for (; I != FirstNonNote; ++I) |
589 | Printer.ReportDiag(o, P: **I); |
590 | |
591 | o << " </array>\n" ; |
592 | } |
593 | |
594 | o << " <key>path</key>\n" ; |
595 | |
596 | o << " <array>\n" ; |
597 | |
598 | for (const auto &Piece : llvm::make_range(x: I, y: Path.end())) |
599 | Printer.ReportDiag(o, P: *Piece); |
600 | |
601 | o << " </array>\n" ; |
602 | |
603 | if (!DiagOpts.ShouldDisplayMacroExpansions) |
604 | return; |
605 | |
606 | o << " <key>macro_expansions</key>\n" |
607 | " <array>\n" ; |
608 | Printer.ReportMacroExpansions(o, /* indent */ 4); |
609 | o << " </array>\n" ; |
610 | } |
611 | |
612 | void PlistDiagnostics::FlushDiagnosticsImpl( |
613 | std::vector<const PathDiagnostic *> &Diags, |
614 | FilesMade *filesMade) { |
615 | // Build up a set of FIDs that we use by scanning the locations and |
616 | // ranges of the diagnostics. |
617 | FIDMap FM; |
618 | SmallVector<FileID, 10> Fids; |
619 | const SourceManager& SM = PP.getSourceManager(); |
620 | const LangOptions &LangOpts = PP.getLangOpts(); |
621 | |
622 | auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { |
623 | AddFID(FIDs&: FM, V&: Fids, SM, L: Piece.getLocation().asLocation()); |
624 | ArrayRef<SourceRange> Ranges = Piece.getRanges(); |
625 | for (const SourceRange &Range : Ranges) { |
626 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getBegin()); |
627 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getEnd()); |
628 | } |
629 | }; |
630 | |
631 | for (const PathDiagnostic *D : Diags) { |
632 | |
633 | SmallVector<const PathPieces *, 5> WorkList; |
634 | WorkList.push_back(Elt: &D->path); |
635 | |
636 | while (!WorkList.empty()) { |
637 | const PathPieces &Path = *WorkList.pop_back_val(); |
638 | |
639 | for (const auto &Iter : Path) { |
640 | const PathDiagnosticPiece &Piece = *Iter; |
641 | AddPieceFID(Piece); |
642 | |
643 | if (const PathDiagnosticCallPiece *Call = |
644 | dyn_cast<PathDiagnosticCallPiece>(Val: &Piece)) { |
645 | if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) |
646 | AddPieceFID(*CallEnterWithin); |
647 | |
648 | if (auto CallEnterEvent = Call->getCallEnterEvent()) |
649 | AddPieceFID(*CallEnterEvent); |
650 | |
651 | WorkList.push_back(Elt: &Call->path); |
652 | } else if (const PathDiagnosticMacroPiece *Macro = |
653 | dyn_cast<PathDiagnosticMacroPiece>(Val: &Piece)) { |
654 | WorkList.push_back(Elt: &Macro->subPieces); |
655 | } |
656 | } |
657 | } |
658 | } |
659 | |
660 | // Open the file. |
661 | std::error_code EC; |
662 | llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
663 | if (EC) { |
664 | llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; |
665 | return; |
666 | } |
667 | |
668 | 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 | o << "<dict>\n" << |
675 | " <key>clang_version</key>\n" ; |
676 | EmitString(o, s: getClangFullVersion()) << '\n'; |
677 | o << " <key>diagnostics</key>\n" |
678 | " <array>\n" ; |
679 | |
680 | for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), |
681 | DE = Diags.end(); DI!=DE; ++DI) { |
682 | |
683 | o << " <dict>\n" ; |
684 | |
685 | const PathDiagnostic *D = *DI; |
686 | printBugPath(o, FM, Path: D->path); |
687 | |
688 | // Output the bug type and bug category. |
689 | o << " <key>description</key>" ; |
690 | EmitString(o, s: D->getShortDescription()) << '\n'; |
691 | o << " <key>category</key>" ; |
692 | EmitString(o, s: D->getCategory()) << '\n'; |
693 | o << " <key>type</key>" ; |
694 | EmitString(o, s: D->getBugType()) << '\n'; |
695 | o << " <key>check_name</key>" ; |
696 | EmitString(o, s: D->getCheckerName()) << '\n'; |
697 | |
698 | o << " <!-- This hash is experimental and going to change! -->\n" ; |
699 | o << " <key>issue_hash_content_of_line_in_context</key>" ; |
700 | PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); |
701 | FullSourceLoc L(SM.getExpansionLoc(Loc: UPDLoc.isValid() |
702 | ? UPDLoc.asLocation() |
703 | : D->getLocation().asLocation()), |
704 | SM); |
705 | const Decl *DeclWithIssue = D->getDeclWithIssue(); |
706 | EmitString(o, s: getIssueHash(IssueLoc: L, CheckerName: D->getCheckerName(), WarningMessage: D->getBugType(), |
707 | IssueDecl: DeclWithIssue, LangOpts)) |
708 | << '\n'; |
709 | |
710 | // Output information about the semantic context where |
711 | // the issue occurred. |
712 | if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { |
713 | // FIXME: handle blocks, which have no name. |
714 | if (const NamedDecl *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) { |
715 | StringRef declKind; |
716 | switch (ND->getKind()) { |
717 | case Decl::CXXRecord: |
718 | declKind = "C++ class" ; |
719 | break; |
720 | case Decl::CXXMethod: |
721 | declKind = "C++ method" ; |
722 | break; |
723 | case Decl::ObjCMethod: |
724 | declKind = "Objective-C method" ; |
725 | break; |
726 | case Decl::Function: |
727 | declKind = "function" ; |
728 | break; |
729 | default: |
730 | break; |
731 | } |
732 | if (!declKind.empty()) { |
733 | const std::string &declName = ND->getDeclName().getAsString(); |
734 | o << " <key>issue_context_kind</key>" ; |
735 | EmitString(o, s: declKind) << '\n'; |
736 | o << " <key>issue_context</key>" ; |
737 | EmitString(o, s: declName) << '\n'; |
738 | } |
739 | |
740 | // Output the bug hash for issue unique-ing. Currently, it's just an |
741 | // offset from the beginning of the function. |
742 | 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 | if (UPDLoc.isValid()) { |
750 | FullSourceLoc UFunL( |
751 | SM.getExpansionLoc( |
752 | Loc: D->getUniqueingDecl()->getBody()->getBeginLoc()), |
753 | SM); |
754 | o << " <key>issue_hash_function_offset</key><string>" |
755 | << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() |
756 | << "</string>\n" ; |
757 | |
758 | // Otherwise, use the location on which the bug is reported. |
759 | } else { |
760 | FullSourceLoc FunL(SM.getExpansionLoc(Loc: Body->getBeginLoc()), SM); |
761 | o << " <key>issue_hash_function_offset</key><string>" |
762 | << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() |
763 | << "</string>\n" ; |
764 | } |
765 | |
766 | } |
767 | } |
768 | } |
769 | |
770 | // Output the location of the bug. |
771 | o << " <key>location</key>\n" ; |
772 | EmitLocation(o, SM, L: D->getLocation().asLocation(), FM, indent: 2); |
773 | |
774 | // Output the diagnostic to the sub-diagnostic client, if any. |
775 | if (!filesMade->empty()) { |
776 | StringRef lastName; |
777 | PDFileEntry::ConsumerFiles *files = filesMade->getFiles(PD: *D); |
778 | if (files) { |
779 | for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), |
780 | CE = files->end(); CI != CE; ++CI) { |
781 | StringRef newName = CI->first; |
782 | if (newName != lastName) { |
783 | if (!lastName.empty()) { |
784 | o << " </array>\n" ; |
785 | } |
786 | lastName = newName; |
787 | o << " <key>" << lastName << "_files</key>\n" ; |
788 | o << " <array>\n" ; |
789 | } |
790 | o << " <string>" << CI->second << "</string>\n" ; |
791 | } |
792 | o << " </array>\n" ; |
793 | } |
794 | } |
795 | |
796 | printCoverage(D, /*IndentLevel=*/InputIndentLevel: 2, Fids, FM, o); |
797 | |
798 | // Close up the entry. |
799 | o << " </dict>\n" ; |
800 | } |
801 | |
802 | o << " </array>\n" ; |
803 | |
804 | o << " <key>files</key>\n" |
805 | " <array>\n" ; |
806 | for (FileID FID : Fids) |
807 | EmitString(o&: o << " " , s: SM.getFileEntryRefForID(FID)->getName()) << '\n'; |
808 | o << " </array>\n" ; |
809 | |
810 | if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { |
811 | o << " <key>statistics</key>\n" ; |
812 | std::string stats; |
813 | llvm::raw_string_ostream os(stats); |
814 | llvm::PrintStatisticsJSON(OS&: os); |
815 | os.flush(); |
816 | EmitString(o, s: html::EscapeText(s: stats)) << '\n'; |
817 | } |
818 | |
819 | // Finish. |
820 | o << "</dict>\n</plist>\n" ; |
821 | } |
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 | const SourceManager &SM) { |
832 | if (auto CTUMacroExpCtx = |
833 | CTU.getMacroExpansionContextForSourceLocation(ToLoc: MacroExpansionLoc)) { |
834 | return CTUMacroExpCtx->getExpandedText(MacroExpansionLoc); |
835 | } |
836 | return MacroExpansions.getExpandedText(MacroExpansionLoc); |
837 | } |
838 | |