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