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