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