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
~PlistDiagnostics()61 ~PlistDiagnostics() override {}
62
63 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
64 FilesMade *filesMade) override;
65
getName() const66 StringRef getName() const override {
67 return "PlistDiagnostics";
68 }
69
getGenerationScheme() const70 PathGenerationScheme getGenerationScheme() const override {
71 return Extensive;
72 }
supportsLogicalOpControlFlow() const73 bool supportsLogicalOpControlFlow() const override { return true; }
supportsCrossFileDiagnostics() const74 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:
PlistPrinter(const FIDMap & FM,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)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
ReportDiag(raw_ostream & o,const PathDiagnosticPiece & P)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:
ReportPiece(raw_ostream & o,const PathDiagnosticPiece & P,unsigned indent,unsigned depth,bool includeControlFlow,bool isKeyEvent=false)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, cast<PathDiagnosticControlFlowPiece>(P), indent);
116 break;
117 case PathDiagnosticPiece::Call:
118 ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent,
119 depth);
120 break;
121 case PathDiagnosticPiece::Event:
122 ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth,
123 isKeyEvent);
124 break;
125 case PathDiagnosticPiece::Macro:
126 ReportMacroSubPieces(o, cast<PathDiagnosticMacroPiece>(P), indent,
127 depth);
128 break;
129 case PathDiagnosticPiece::Note:
130 ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent);
131 break;
132 case PathDiagnosticPiece::PopUp:
133 ReportPopUp(o, cast<PathDiagnosticPopUpPiece>(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
EmitRanges(raw_ostream & o,const ArrayRef<SourceRange> Ranges,unsigned indent)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 Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts),
194 FM, indent + 1);
195 --indent;
196 Indent(o, indent) << "</array>\n";
197 }
198
EmitMessage(raw_ostream & o,StringRef Message,unsigned indent)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, 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, Message) << '\n';
212 }
213
EmitFixits(raw_ostream & o,ArrayRef<FixItHint> fixits,unsigned indent)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, Lexer::getAsCharRange(fixit.RemoveRange, SM, LangOpts),
232 FM, indent + 2);
233 Indent(o, indent) << " <key>insert_string</key>";
234 EmitString(o, fixit.CodeToInsert);
235 o << "\n";
236 Indent(o, indent) << " </dict>\n";
237 }
238 Indent(o, indent) << "</array>\n";
239 }
240
ReportControlFlow(raw_ostream & o,const PathDiagnosticControlFlowPiece & P,unsigned indent)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(I->getStart().asRange().getBegin()));
269 EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM,
270 indent + 1);
271
272 Indent(o, indent) << "<key>end</key>\n";
273 SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin()));
274 EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM,
275 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
ReportEvent(raw_ostream & o,const PathDiagnosticEventPiece & P,unsigned indent,unsigned depth,bool isKeyEvent)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, depth) << '\n';
326
327 // Output the text.
328 EmitMessage(o, P.getString(), indent);
329
330 // Output the fixits.
331 EmitFixits(o, P.getFixits(), indent);
332
333 // Finish up.
334 --indent;
335 Indent(o, indent); o << "</dict>\n";
336 }
337
ReportCall(raw_ostream & o,const PathDiagnosticCallPiece & P,unsigned indent,unsigned depth)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, *callEnter, indent, depth, /*includeControlFlow*/ true,
344 P.isLastInMainSourceFile());
345
346
347 ++depth;
348
349 if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent())
350 ReportPiece(o, *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, **I, indent, depth, /*includeControlFlow*/ true);
355
356 --depth;
357
358 if (auto callExit = P.getCallExitEvent())
359 ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true);
360
361 assert(P.getFixits().size() == 0 &&
362 "Fixits on call pieces are not implemented yet!");
363 }
364
ReportMacroSubPieces(raw_ostream & o,const PathDiagnosticMacroPiece & P,unsigned indent,unsigned depth)365 void PlistPrinter::ReportMacroSubPieces(raw_ostream &o,
366 const PathDiagnosticMacroPiece& P,
367 unsigned indent, unsigned depth) {
368 MacroPieces.push_back(&P);
369
370 for (PathPieces::const_iterator I = P.subPieces.begin(),
371 E = P.subPieces.end();
372 I != E; ++I) {
373 ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false);
374 }
375
376 assert(P.getFixits().size() == 0 &&
377 "Fixits on constrol flow pieces are not implemented yet!");
378 }
379
ReportMacroExpansions(raw_ostream & o,unsigned indent)380 void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) {
381
382 for (const PathDiagnosticMacroPiece *P : MacroPieces) {
383 const SourceManager &SM = PP.getSourceManager();
384
385 SourceLocation MacroExpansionLoc =
386 P->getLocation().asLocation().getExpansionLoc();
387
388 const std::optional<StringRef> MacroName =
389 MacroExpansions.getOriginalText(MacroExpansionLoc);
390 const std::optional<StringRef> ExpansionText =
391 getExpandedMacro(MacroExpansionLoc, CTU, MacroExpansions, SM);
392
393 if (!MacroName || !ExpansionText)
394 continue;
395
396 Indent(o, indent) << "<dict>\n";
397 ++indent;
398
399 // Output the location.
400 FullSourceLoc L = P->getLocation().asLocation();
401
402 Indent(o, indent) << "<key>location</key>\n";
403 EmitLocation(o, SM, L, FM, indent);
404
405 // Output the ranges (if any).
406 ArrayRef<SourceRange> Ranges = P->getRanges();
407 EmitRanges(o, Ranges, indent);
408
409 // Output the macro name.
410 Indent(o, indent) << "<key>name</key>";
411 EmitString(o, *MacroName) << '\n';
412
413 // Output what it expands into.
414 Indent(o, indent) << "<key>expansion</key>";
415 EmitString(o, *ExpansionText) << '\n';
416
417 // Finish up.
418 --indent;
419 Indent(o, indent);
420 o << "</dict>\n";
421 }
422 }
423
ReportNote(raw_ostream & o,const PathDiagnosticNotePiece & P,unsigned indent)424 void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
425 unsigned indent) {
426
427 const SourceManager &SM = PP.getSourceManager();
428
429 Indent(o, indent) << "<dict>\n";
430 ++indent;
431
432 // Output the location.
433 FullSourceLoc L = P.getLocation().asLocation();
434
435 Indent(o, indent) << "<key>location</key>\n";
436 EmitLocation(o, SM, L, FM, indent);
437
438 // Output the ranges (if any).
439 ArrayRef<SourceRange> Ranges = P.getRanges();
440 EmitRanges(o, Ranges, indent);
441
442 // Output the text.
443 EmitMessage(o, P.getString(), indent);
444
445 // Output the fixits.
446 EmitFixits(o, P.getFixits(), indent);
447
448 // Finish up.
449 --indent;
450 Indent(o, indent); o << "</dict>\n";
451 }
452
ReportPopUp(raw_ostream & o,const PathDiagnosticPopUpPiece & P,unsigned indent)453 void PlistPrinter::ReportPopUp(raw_ostream &o,
454 const PathDiagnosticPopUpPiece &P,
455 unsigned indent) {
456 const SourceManager &SM = PP.getSourceManager();
457
458 Indent(o, indent) << "<dict>\n";
459 ++indent;
460
461 Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n";
462
463 // Output the location.
464 FullSourceLoc L = P.getLocation().asLocation();
465
466 Indent(o, indent) << "<key>location</key>\n";
467 EmitLocation(o, SM, L, FM, indent);
468
469 // Output the ranges (if any).
470 ArrayRef<SourceRange> Ranges = P.getRanges();
471 EmitRanges(o, Ranges, indent);
472
473 // Output the text.
474 EmitMessage(o, P.getString(), indent);
475
476 assert(P.getFixits().size() == 0 &&
477 "Fixits on pop-up pieces are not implemented yet!");
478
479 // Finish up.
480 --indent;
481 Indent(o, indent) << "</dict>\n";
482 }
483
484 //===----------------------------------------------------------------------===//
485 // Static function definitions.
486 //===----------------------------------------------------------------------===//
487
488 /// Print coverage information to output stream @c o.
489 /// May modify the used list of files @c Fids by inserting new ones.
printCoverage(const PathDiagnostic * D,unsigned InputIndentLevel,SmallVectorImpl<FileID> & Fids,FIDMap & FM,llvm::raw_fd_ostream & o)490 static void printCoverage(const PathDiagnostic *D,
491 unsigned InputIndentLevel,
492 SmallVectorImpl<FileID> &Fids,
493 FIDMap &FM,
494 llvm::raw_fd_ostream &o) {
495 unsigned IndentLevel = InputIndentLevel;
496
497 Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n";
498 Indent(o, IndentLevel) << "<dict>\n";
499 IndentLevel++;
500
501 // Mapping from file IDs to executed lines.
502 const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines();
503 for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) {
504 unsigned FileKey = AddFID(FM, Fids, I->first);
505 Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n";
506 Indent(o, IndentLevel) << "<array>\n";
507 IndentLevel++;
508 for (unsigned LineNo : I->second) {
509 Indent(o, IndentLevel);
510 EmitInteger(o, LineNo) << "\n";
511 }
512 IndentLevel--;
513 Indent(o, IndentLevel) << "</array>\n";
514 }
515 IndentLevel--;
516 Indent(o, IndentLevel) << "</dict>\n";
517
518 assert(IndentLevel == InputIndentLevel);
519 }
520
521 //===----------------------------------------------------------------------===//
522 // Methods of PlistDiagnostics.
523 //===----------------------------------------------------------------------===//
524
PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts,const std::string & output,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions,bool supportsMultipleFiles)525 PlistDiagnostics::PlistDiagnostics(
526 PathDiagnosticConsumerOptions DiagOpts, const std::string &output,
527 const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU,
528 const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles)
529 : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU),
530 MacroExpansions(MacroExpansions),
531 SupportsCrossFileDiagnostics(supportsMultipleFiles) {
532 // FIXME: Will be used by a later planned change.
533 (void)this->CTU;
534 }
535
createPlistDiagnosticConsumer(PathDiagnosticConsumerOptions DiagOpts,PathDiagnosticConsumers & C,const std::string & OutputFile,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)536 void ento::createPlistDiagnosticConsumer(
537 PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
538 const std::string &OutputFile, const Preprocessor &PP,
539 const cross_tu::CrossTranslationUnitContext &CTU,
540 const MacroExpansionContext &MacroExpansions) {
541
542 // TODO: Emit an error here.
543 if (OutputFile.empty())
544 return;
545
546 C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU,
547 MacroExpansions,
548 /*supportsMultipleFiles=*/false));
549 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile,
550 PP, CTU, MacroExpansions);
551 }
552
createPlistMultiFileDiagnosticConsumer(PathDiagnosticConsumerOptions DiagOpts,PathDiagnosticConsumers & C,const std::string & OutputFile,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)553 void ento::createPlistMultiFileDiagnosticConsumer(
554 PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
555 const std::string &OutputFile, const Preprocessor &PP,
556 const cross_tu::CrossTranslationUnitContext &CTU,
557 const MacroExpansionContext &MacroExpansions) {
558
559 // TODO: Emit an error here.
560 if (OutputFile.empty())
561 return;
562
563 C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU,
564 MacroExpansions,
565 /*supportsMultipleFiles=*/true));
566 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile,
567 PP, CTU, MacroExpansions);
568 }
569
printBugPath(llvm::raw_ostream & o,const FIDMap & FM,const PathPieces & Path)570 void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM,
571 const PathPieces &Path) {
572 PlistPrinter Printer(FM, PP, CTU, MacroExpansions);
573 assert(std::is_partitioned(Path.begin(), Path.end(),
574 [](const PathDiagnosticPieceRef &E) {
575 return E->getKind() == PathDiagnosticPiece::Note;
576 }) &&
577 "PathDiagnostic is not partitioned so that notes precede the rest");
578
579 PathPieces::const_iterator FirstNonNote = std::partition_point(
580 Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) {
581 return E->getKind() == PathDiagnosticPiece::Note;
582 });
583
584 PathPieces::const_iterator I = Path.begin();
585
586 if (FirstNonNote != Path.begin()) {
587 o << " <key>notes</key>\n"
588 " <array>\n";
589
590 for (; I != FirstNonNote; ++I)
591 Printer.ReportDiag(o, **I);
592
593 o << " </array>\n";
594 }
595
596 o << " <key>path</key>\n";
597
598 o << " <array>\n";
599
600 for (PathPieces::const_iterator E = Path.end(); I != E; ++I)
601 Printer.ReportDiag(o, **I);
602
603 o << " </array>\n";
604
605 if (!DiagOpts.ShouldDisplayMacroExpansions)
606 return;
607
608 o << " <key>macro_expansions</key>\n"
609 " <array>\n";
610 Printer.ReportMacroExpansions(o, /* indent */ 4);
611 o << " </array>\n";
612 }
613
FlushDiagnosticsImpl(std::vector<const PathDiagnostic * > & Diags,FilesMade * filesMade)614 void PlistDiagnostics::FlushDiagnosticsImpl(
615 std::vector<const PathDiagnostic *> &Diags,
616 FilesMade *filesMade) {
617 // Build up a set of FIDs that we use by scanning the locations and
618 // ranges of the diagnostics.
619 FIDMap FM;
620 SmallVector<FileID, 10> Fids;
621 const SourceManager& SM = PP.getSourceManager();
622 const LangOptions &LangOpts = PP.getLangOpts();
623
624 auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) {
625 AddFID(FM, Fids, SM, Piece.getLocation().asLocation());
626 ArrayRef<SourceRange> Ranges = Piece.getRanges();
627 for (const SourceRange &Range : Ranges) {
628 AddFID(FM, Fids, SM, Range.getBegin());
629 AddFID(FM, Fids, SM, Range.getEnd());
630 }
631 };
632
633 for (const PathDiagnostic *D : Diags) {
634
635 SmallVector<const PathPieces *, 5> WorkList;
636 WorkList.push_back(&D->path);
637
638 while (!WorkList.empty()) {
639 const PathPieces &Path = *WorkList.pop_back_val();
640
641 for (const auto &Iter : Path) {
642 const PathDiagnosticPiece &Piece = *Iter;
643 AddPieceFID(Piece);
644
645 if (const PathDiagnosticCallPiece *Call =
646 dyn_cast<PathDiagnosticCallPiece>(&Piece)) {
647 if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent())
648 AddPieceFID(*CallEnterWithin);
649
650 if (auto CallEnterEvent = Call->getCallEnterEvent())
651 AddPieceFID(*CallEnterEvent);
652
653 WorkList.push_back(&Call->path);
654 } else if (const PathDiagnosticMacroPiece *Macro =
655 dyn_cast<PathDiagnosticMacroPiece>(&Piece)) {
656 WorkList.push_back(&Macro->subPieces);
657 }
658 }
659 }
660 }
661
662 // Open the file.
663 std::error_code EC;
664 llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
665 if (EC) {
666 llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
667 return;
668 }
669
670 EmitPlistHeader(o);
671
672 // Write the root object: a <dict> containing...
673 // - "clang_version", the string representation of clang version
674 // - "files", an <array> mapping from FIDs to file names
675 // - "diagnostics", an <array> containing the path diagnostics
676 o << "<dict>\n" <<
677 " <key>clang_version</key>\n";
678 EmitString(o, getClangFullVersion()) << '\n';
679 o << " <key>diagnostics</key>\n"
680 " <array>\n";
681
682 for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
683 DE = Diags.end(); DI!=DE; ++DI) {
684
685 o << " <dict>\n";
686
687 const PathDiagnostic *D = *DI;
688 printBugPath(o, FM, D->path);
689
690 // Output the bug type and bug category.
691 o << " <key>description</key>";
692 EmitString(o, D->getShortDescription()) << '\n';
693 o << " <key>category</key>";
694 EmitString(o, D->getCategory()) << '\n';
695 o << " <key>type</key>";
696 EmitString(o, D->getBugType()) << '\n';
697 o << " <key>check_name</key>";
698 EmitString(o, D->getCheckerName()) << '\n';
699
700 o << " <!-- This hash is experimental and going to change! -->\n";
701 o << " <key>issue_hash_content_of_line_in_context</key>";
702 PathDiagnosticLocation UPDLoc = D->getUniqueingLoc();
703 FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid()
704 ? UPDLoc.asLocation()
705 : D->getLocation().asLocation()),
706 SM);
707 const Decl *DeclWithIssue = D->getDeclWithIssue();
708 EmitString(o, getIssueHash(L, D->getCheckerName(), D->getBugType(),
709 DeclWithIssue, LangOpts))
710 << '\n';
711
712 // Output information about the semantic context where
713 // the issue occurred.
714 if (const Decl *DeclWithIssue = D->getDeclWithIssue()) {
715 // FIXME: handle blocks, which have no name.
716 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
717 StringRef declKind;
718 switch (ND->getKind()) {
719 case Decl::CXXRecord:
720 declKind = "C++ class";
721 break;
722 case Decl::CXXMethod:
723 declKind = "C++ method";
724 break;
725 case Decl::ObjCMethod:
726 declKind = "Objective-C method";
727 break;
728 case Decl::Function:
729 declKind = "function";
730 break;
731 default:
732 break;
733 }
734 if (!declKind.empty()) {
735 const std::string &declName = ND->getDeclName().getAsString();
736 o << " <key>issue_context_kind</key>";
737 EmitString(o, declKind) << '\n';
738 o << " <key>issue_context</key>";
739 EmitString(o, declName) << '\n';
740 }
741
742 // Output the bug hash for issue unique-ing. Currently, it's just an
743 // offset from the beginning of the function.
744 if (const Stmt *Body = DeclWithIssue->getBody()) {
745
746 // If the bug uniqueing location exists, use it for the hash.
747 // For example, this ensures that two leaks reported on the same line
748 // will have different issue_hashes and that the hash will identify
749 // the leak location even after code is added between the allocation
750 // site and the end of scope (leak report location).
751 if (UPDLoc.isValid()) {
752 FullSourceLoc UFunL(
753 SM.getExpansionLoc(
754 D->getUniqueingDecl()->getBody()->getBeginLoc()),
755 SM);
756 o << " <key>issue_hash_function_offset</key><string>"
757 << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber()
758 << "</string>\n";
759
760 // Otherwise, use the location on which the bug is reported.
761 } else {
762 FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM);
763 o << " <key>issue_hash_function_offset</key><string>"
764 << L.getExpansionLineNumber() - FunL.getExpansionLineNumber()
765 << "</string>\n";
766 }
767
768 }
769 }
770 }
771
772 // Output the location of the bug.
773 o << " <key>location</key>\n";
774 EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2);
775
776 // Output the diagnostic to the sub-diagnostic client, if any.
777 if (!filesMade->empty()) {
778 StringRef lastName;
779 PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D);
780 if (files) {
781 for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(),
782 CE = files->end(); CI != CE; ++CI) {
783 StringRef newName = CI->first;
784 if (newName != lastName) {
785 if (!lastName.empty()) {
786 o << " </array>\n";
787 }
788 lastName = newName;
789 o << " <key>" << lastName << "_files</key>\n";
790 o << " <array>\n";
791 }
792 o << " <string>" << CI->second << "</string>\n";
793 }
794 o << " </array>\n";
795 }
796 }
797
798 printCoverage(D, /*IndentLevel=*/2, Fids, FM, o);
799
800 // Close up the entry.
801 o << " </dict>\n";
802 }
803
804 o << " </array>\n";
805
806 o << " <key>files</key>\n"
807 " <array>\n";
808 for (FileID FID : Fids)
809 EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n';
810 o << " </array>\n";
811
812 if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) {
813 o << " <key>statistics</key>\n";
814 std::string stats;
815 llvm::raw_string_ostream os(stats);
816 llvm::PrintStatisticsJSON(os);
817 os.flush();
818 EmitString(o, html::EscapeText(stats)) << '\n';
819 }
820
821 // Finish.
822 o << "</dict>\n</plist>\n";
823 }
824
825 //===----------------------------------------------------------------------===//
826 // Definitions of helper functions and methods for expanding macros.
827 //===----------------------------------------------------------------------===//
828
829 static std::optional<StringRef>
getExpandedMacro(SourceLocation MacroExpansionLoc,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions,const SourceManager & SM)830 getExpandedMacro(SourceLocation MacroExpansionLoc,
831 const cross_tu::CrossTranslationUnitContext &CTU,
832 const MacroExpansionContext &MacroExpansions,
833 const SourceManager &SM) {
834 if (auto CTUMacroExpCtx =
835 CTU.getMacroExpansionContextForSourceLocation(MacroExpansionLoc)) {
836 return CTUMacroExpCtx->getExpandedText(MacroExpansionLoc);
837 }
838 return MacroExpansions.getExpandedText(MacroExpansionLoc);
839 }
840