1 //===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 //  This file defines the PlistDiagnostics object.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "clang/Basic/FileManager.h"
15 #include "clang/Basic/PlistSupport.h"
16 #include "clang/Basic/SourceManager.h"
17 #include "clang/Basic/Version.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Rewrite/Core/HTMLRewrite.h"
20 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
21 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
22 #include "clang/StaticAnalyzer/Core/IssueHash.h"
23 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
24 #include "llvm/ADT/Statistic.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/Support/Casting.h"
27 
28 using namespace clang;
29 using namespace ento;
30 using namespace markup;
31 
32 //===----------------------------------------------------------------------===//
33 // Declarations of helper classes and functions for emitting bug reports in
34 // plist format.
35 //===----------------------------------------------------------------------===//
36 
37 namespace {
38   class PlistDiagnostics : public PathDiagnosticConsumer {
39     const std::string OutputFile;
40     const Preprocessor &PP;
41     AnalyzerOptions &AnOpts;
42     const bool SupportsCrossFileDiagnostics;
43   public:
44     PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
45                      const std::string& prefix,
46                      const Preprocessor &PP,
47                      bool supportsMultipleFiles);
48 
49     ~PlistDiagnostics() override {}
50 
51     void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
52                               FilesMade *filesMade) override;
53 
54     StringRef getName() const override {
55       return "PlistDiagnostics";
56     }
57 
58     PathGenerationScheme getGenerationScheme() const override {
59       return Extensive;
60     }
61     bool supportsLogicalOpControlFlow() const override { return true; }
62     bool supportsCrossFileDiagnostics() const override {
63       return SupportsCrossFileDiagnostics;
64     }
65   };
66 } // end anonymous namespace
67 
68 namespace {
69 
70 /// A helper class for emitting a single report.
71 class PlistPrinter {
72   const FIDMap& FM;
73   AnalyzerOptions &AnOpts;
74   const Preprocessor &PP;
75 
76 public:
77   PlistPrinter(const FIDMap& FM, AnalyzerOptions &AnOpts,
78                const Preprocessor &PP)
79     : FM(FM), AnOpts(AnOpts), PP(PP) {
80   }
81 
82   void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) {
83     ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true);
84 
85     // Don't emit a warning about an unused private field.
86     (void)AnOpts;
87   }
88 
89 private:
90   void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P,
91                    unsigned indent, unsigned depth, bool includeControlFlow,
92                    bool isKeyEvent = false) {
93     switch (P.getKind()) {
94       case PathDiagnosticPiece::ControlFlow:
95         if (includeControlFlow)
96           ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), indent);
97         break;
98       case PathDiagnosticPiece::Call:
99         ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent,
100                    depth);
101         break;
102       case PathDiagnosticPiece::Event:
103         ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth,
104                     isKeyEvent);
105         break;
106       case PathDiagnosticPiece::Macro:
107         ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), indent, depth);
108         break;
109       case PathDiagnosticPiece::Note:
110         ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent);
111         break;
112     }
113   }
114 
115   void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges,
116                   unsigned indent);
117   void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent);
118 
119   void ReportControlFlow(raw_ostream &o,
120                          const PathDiagnosticControlFlowPiece& P,
121                          unsigned indent);
122   void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
123                    unsigned indent, unsigned depth, bool isKeyEvent = false);
124   void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P,
125                   unsigned indent, unsigned depth);
126   void ReportMacro(raw_ostream &o, const PathDiagnosticMacroPiece& P,
127                    unsigned indent, unsigned depth);
128   void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
129                   unsigned indent);
130 };
131 
132 } // end of anonymous namespace
133 
134 static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM,
135                          AnalyzerOptions &AnOpts,
136                          const Preprocessor &PP,
137                          const PathPieces &Path);
138 
139 /// Print coverage information to output stream {@code o}.
140 /// May modify the used list of files {@code Fids} by inserting new ones.
141 static void printCoverage(const PathDiagnostic *D,
142                           unsigned InputIndentLevel,
143                           SmallVectorImpl<FileID> &Fids,
144                           FIDMap &FM,
145                           llvm::raw_fd_ostream &o);
146 //===----------------------------------------------------------------------===//
147 // Methods of PlistPrinter.
148 //===----------------------------------------------------------------------===//
149 
150 void PlistPrinter::EmitRanges(raw_ostream &o,
151                               const ArrayRef<SourceRange> Ranges,
152                               unsigned indent) {
153 
154   if (Ranges.empty())
155     return;
156 
157   Indent(o, indent) << "<key>ranges</key>\n";
158   Indent(o, indent) << "<array>\n";
159   ++indent;
160 
161   const SourceManager &SM = PP.getSourceManager();
162   const LangOptions &LangOpts = PP.getLangOpts();
163 
164   for (auto &R : Ranges)
165     EmitRange(o, SM,
166               Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts),
167               FM, indent + 1);
168   --indent;
169   Indent(o, indent) << "</array>\n";
170 }
171 
172 void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message,
173                                unsigned indent) {
174   // Output the text.
175   assert(!Message.empty());
176   Indent(o, indent) << "<key>extended_message</key>\n";
177   Indent(o, indent);
178   EmitString(o, Message) << '\n';
179 
180   // Output the short text.
181   // FIXME: Really use a short string.
182   Indent(o, indent) << "<key>message</key>\n";
183   Indent(o, indent);
184   EmitString(o, Message) << '\n';
185 }
186 
187 void PlistPrinter::ReportControlFlow(raw_ostream &o,
188                                      const PathDiagnosticControlFlowPiece& P,
189                                      unsigned indent) {
190 
191   const SourceManager &SM = PP.getSourceManager();
192   const LangOptions &LangOpts = PP.getLangOpts();
193 
194   Indent(o, indent) << "<dict>\n";
195   ++indent;
196 
197   Indent(o, indent) << "<key>kind</key><string>control</string>\n";
198 
199   // Emit edges.
200   Indent(o, indent) << "<key>edges</key>\n";
201   ++indent;
202   Indent(o, indent) << "<array>\n";
203   ++indent;
204   for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end();
205        I!=E; ++I) {
206     Indent(o, indent) << "<dict>\n";
207     ++indent;
208 
209     // Make the ranges of the start and end point self-consistent with adjacent edges
210     // by forcing to use only the beginning of the range.  This simplifies the layout
211     // logic for clients.
212     Indent(o, indent) << "<key>start</key>\n";
213     SourceRange StartEdge(
214         SM.getExpansionLoc(I->getStart().asRange().getBegin()));
215     EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM,
216               indent + 1);
217 
218     Indent(o, indent) << "<key>end</key>\n";
219     SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin()));
220     EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM,
221               indent + 1);
222 
223     --indent;
224     Indent(o, indent) << "</dict>\n";
225   }
226   --indent;
227   Indent(o, indent) << "</array>\n";
228   --indent;
229 
230   // Output any helper text.
231   const auto &s = P.getString();
232   if (!s.empty()) {
233     Indent(o, indent) << "<key>alternate</key>";
234     EmitString(o, s) << '\n';
235   }
236 
237   --indent;
238   Indent(o, indent) << "</dict>\n";
239 }
240 
241 void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
242                                unsigned indent, unsigned depth,
243                                bool isKeyEvent) {
244 
245   const SourceManager &SM = PP.getSourceManager();
246 
247   Indent(o, indent) << "<dict>\n";
248   ++indent;
249 
250   Indent(o, indent) << "<key>kind</key><string>event</string>\n";
251 
252   if (isKeyEvent) {
253     Indent(o, indent) << "<key>key_event</key><true/>\n";
254   }
255 
256   // Output the location.
257   FullSourceLoc L = P.getLocation().asLocation();
258 
259   Indent(o, indent) << "<key>location</key>\n";
260   EmitLocation(o, SM, L, FM, indent);
261 
262   // Output the ranges (if any).
263   ArrayRef<SourceRange> Ranges = P.getRanges();
264   EmitRanges(o, Ranges, indent);
265 
266   // Output the call depth.
267   Indent(o, indent) << "<key>depth</key>";
268   EmitInteger(o, depth) << '\n';
269 
270   // Output the text.
271   EmitMessage(o, P.getString(), indent);
272 
273   // Finish up.
274   --indent;
275   Indent(o, indent); o << "</dict>\n";
276 }
277 
278 void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P,
279                               unsigned indent,
280                               unsigned depth) {
281 
282   if (auto callEnter = P.getCallEnterEvent())
283     ReportPiece(o, *callEnter, indent, depth, /*includeControlFlow*/ true,
284                 P.isLastInMainSourceFile());
285 
286 
287   ++depth;
288 
289   if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent())
290     ReportPiece(o, *callEnterWithinCaller, indent, depth,
291                 /*includeControlFlow*/ true);
292 
293   for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I)
294     ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ true);
295 
296   --depth;
297 
298   if (auto callExit = P.getCallExitEvent())
299     ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true);
300 }
301 
302 void PlistPrinter::ReportMacro(raw_ostream &o,
303                                const PathDiagnosticMacroPiece& P,
304                                unsigned indent, unsigned depth) {
305 
306   for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end();
307        I!=E; ++I) {
308     ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false);
309   }
310 }
311 
312 void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
313                               unsigned indent) {
314 
315   const SourceManager &SM = PP.getSourceManager();
316 
317   Indent(o, indent) << "<dict>\n";
318   ++indent;
319 
320   // Output the location.
321   FullSourceLoc L = P.getLocation().asLocation();
322 
323   Indent(o, indent) << "<key>location</key>\n";
324   EmitLocation(o, SM, L, FM, indent);
325 
326   // Output the ranges (if any).
327   ArrayRef<SourceRange> Ranges = P.getRanges();
328   EmitRanges(o, Ranges, indent);
329 
330   // Output the text.
331   EmitMessage(o, P.getString(), indent);
332 
333   // Finish up.
334   --indent;
335   Indent(o, indent); o << "</dict>\n";
336 }
337 
338 //===----------------------------------------------------------------------===//
339 // Static function definitions.
340 //===----------------------------------------------------------------------===//
341 
342 /// Print coverage information to output stream {@code o}.
343 /// May modify the used list of files {@code Fids} by inserting new ones.
344 static void printCoverage(const PathDiagnostic *D,
345                           unsigned InputIndentLevel,
346                           SmallVectorImpl<FileID> &Fids,
347                           FIDMap &FM,
348                           llvm::raw_fd_ostream &o) {
349   unsigned IndentLevel = InputIndentLevel;
350 
351   Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n";
352   Indent(o, IndentLevel) << "<dict>\n";
353   IndentLevel++;
354 
355   // Mapping from file IDs to executed lines.
356   const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines();
357   for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) {
358     unsigned FileKey = AddFID(FM, Fids, I->first);
359     Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n";
360     Indent(o, IndentLevel) << "<array>\n";
361     IndentLevel++;
362     for (unsigned LineNo : I->second) {
363       Indent(o, IndentLevel);
364       EmitInteger(o, LineNo) << "\n";
365     }
366     IndentLevel--;
367     Indent(o, IndentLevel) << "</array>\n";
368   }
369   IndentLevel--;
370   Indent(o, IndentLevel) << "</dict>\n";
371 
372   assert(IndentLevel == InputIndentLevel);
373 }
374 
375 static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM,
376                          AnalyzerOptions &AnOpts,
377                          const Preprocessor &PP,
378                          const PathPieces &Path) {
379   PlistPrinter Printer(FM, AnOpts, PP);
380   assert(std::is_partitioned(
381            Path.begin(), Path.end(),
382            [](const std::shared_ptr<PathDiagnosticPiece> &E)
383              { return E->getKind() == PathDiagnosticPiece::Note; }) &&
384          "PathDiagnostic is not partitioned so that notes precede the rest");
385 
386   PathPieces::const_iterator FirstNonNote = std::partition_point(
387       Path.begin(), Path.end(),
388       [](const std::shared_ptr<PathDiagnosticPiece> &E)
389         { return E->getKind() == PathDiagnosticPiece::Note; });
390 
391   PathPieces::const_iterator I = Path.begin();
392 
393   if (FirstNonNote != Path.begin()) {
394     o << "   <key>notes</key>\n"
395          "   <array>\n";
396 
397     for (; I != FirstNonNote; ++I)
398       Printer.ReportDiag(o, **I);
399 
400     o << "   </array>\n";
401   }
402 
403   o << "   <key>path</key>\n";
404 
405   o << "   <array>\n";
406 
407   for (PathPieces::const_iterator E = Path.end(); I != E; ++I)
408     Printer.ReportDiag(o, **I);
409 
410   o << "   </array>\n";
411 }
412 
413 //===----------------------------------------------------------------------===//
414 // Methods of PlistDiagnostics.
415 //===----------------------------------------------------------------------===//
416 
417 PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
418                                    const std::string& output,
419                                    const Preprocessor &PP,
420                                    bool supportsMultipleFiles)
421   : OutputFile(output), PP(PP), AnOpts(AnalyzerOpts),
422     SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
423 
424 void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
425                                          PathDiagnosticConsumers &C,
426                                          const std::string& s,
427                                          const Preprocessor &PP) {
428   C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
429                                    /*supportsMultipleFiles*/ false));
430 }
431 
432 void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
433                                                   PathDiagnosticConsumers &C,
434                                                   const std::string &s,
435                                                   const Preprocessor &PP) {
436   C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
437                                    /*supportsMultipleFiles*/ true));
438 }
439 void PlistDiagnostics::FlushDiagnosticsImpl(
440                                     std::vector<const PathDiagnostic *> &Diags,
441                                     FilesMade *filesMade) {
442   // Build up a set of FIDs that we use by scanning the locations and
443   // ranges of the diagnostics.
444   FIDMap FM;
445   SmallVector<FileID, 10> Fids;
446   const SourceManager& SM = PP.getSourceManager();
447   const LangOptions &LangOpts = PP.getLangOpts();
448 
449   auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) {
450     AddFID(FM, Fids, SM, Piece.getLocation().asLocation());
451     ArrayRef<SourceRange> Ranges = Piece.getRanges();
452     for (const SourceRange &Range : Ranges) {
453       AddFID(FM, Fids, SM, Range.getBegin());
454       AddFID(FM, Fids, SM, Range.getEnd());
455     }
456   };
457 
458   for (const PathDiagnostic *D : Diags) {
459 
460     SmallVector<const PathPieces *, 5> WorkList;
461     WorkList.push_back(&D->path);
462 
463     while (!WorkList.empty()) {
464       const PathPieces &Path = *WorkList.pop_back_val();
465 
466       for (const auto &Iter : Path) {
467         const PathDiagnosticPiece &Piece = *Iter;
468         AddPieceFID(Piece);
469 
470         if (const PathDiagnosticCallPiece *Call =
471                 dyn_cast<PathDiagnosticCallPiece>(&Piece)) {
472           if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent())
473             AddPieceFID(*CallEnterWithin);
474 
475           if (auto CallEnterEvent = Call->getCallEnterEvent())
476             AddPieceFID(*CallEnterEvent);
477 
478           WorkList.push_back(&Call->path);
479         } else if (const PathDiagnosticMacroPiece *Macro =
480                        dyn_cast<PathDiagnosticMacroPiece>(&Piece)) {
481           WorkList.push_back(&Macro->subPieces);
482         }
483       }
484     }
485   }
486 
487   // Open the file.
488   std::error_code EC;
489   llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::F_Text);
490   if (EC) {
491     llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
492     return;
493   }
494 
495   EmitPlistHeader(o);
496 
497   // Write the root object: a <dict> containing...
498   //  - "clang_version", the string representation of clang version
499   //  - "files", an <array> mapping from FIDs to file names
500   //  - "diagnostics", an <array> containing the path diagnostics
501   o << "<dict>\n" <<
502        " <key>clang_version</key>\n";
503   EmitString(o, getClangFullVersion()) << '\n';
504   o << " <key>diagnostics</key>\n"
505        " <array>\n";
506 
507   for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
508        DE = Diags.end(); DI!=DE; ++DI) {
509 
510     o << "  <dict>\n";
511 
512     const PathDiagnostic *D = *DI;
513     printBugPath(o, FM, AnOpts, PP, D->path);
514 
515     // Output the bug type and bug category.
516     o << "   <key>description</key>";
517     EmitString(o, D->getShortDescription()) << '\n';
518     o << "   <key>category</key>";
519     EmitString(o, D->getCategory()) << '\n';
520     o << "   <key>type</key>";
521     EmitString(o, D->getBugType()) << '\n';
522     o << "   <key>check_name</key>";
523     EmitString(o, D->getCheckName()) << '\n';
524 
525     o << "   <!-- This hash is experimental and going to change! -->\n";
526     o << "   <key>issue_hash_content_of_line_in_context</key>";
527     PathDiagnosticLocation UPDLoc = D->getUniqueingLoc();
528     FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid()
529                                             ? UPDLoc.asLocation()
530                                             : D->getLocation().asLocation()),
531                     SM);
532     const Decl *DeclWithIssue = D->getDeclWithIssue();
533     EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(),
534                                DeclWithIssue, LangOpts))
535         << '\n';
536 
537     // Output information about the semantic context where
538     // the issue occurred.
539     if (const Decl *DeclWithIssue = D->getDeclWithIssue()) {
540       // FIXME: handle blocks, which have no name.
541       if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
542         StringRef declKind;
543         switch (ND->getKind()) {
544           case Decl::CXXRecord:
545             declKind = "C++ class";
546             break;
547           case Decl::CXXMethod:
548             declKind = "C++ method";
549             break;
550           case Decl::ObjCMethod:
551             declKind = "Objective-C method";
552             break;
553           case Decl::Function:
554             declKind = "function";
555             break;
556           default:
557             break;
558         }
559         if (!declKind.empty()) {
560           const std::string &declName = ND->getDeclName().getAsString();
561           o << "  <key>issue_context_kind</key>";
562           EmitString(o, declKind) << '\n';
563           o << "  <key>issue_context</key>";
564           EmitString(o, declName) << '\n';
565         }
566 
567         // Output the bug hash for issue unique-ing. Currently, it's just an
568         // offset from the beginning of the function.
569         if (const Stmt *Body = DeclWithIssue->getBody()) {
570 
571           // If the bug uniqueing location exists, use it for the hash.
572           // For example, this ensures that two leaks reported on the same line
573           // will have different issue_hashes and that the hash will identify
574           // the leak location even after code is added between the allocation
575           // site and the end of scope (leak report location).
576           if (UPDLoc.isValid()) {
577             FullSourceLoc UFunL(
578                 SM.getExpansionLoc(
579                     D->getUniqueingDecl()->getBody()->getBeginLoc()),
580                 SM);
581             o << "  <key>issue_hash_function_offset</key><string>"
582               << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber()
583               << "</string>\n";
584 
585           // Otherwise, use the location on which the bug is reported.
586           } else {
587             FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM);
588             o << "  <key>issue_hash_function_offset</key><string>"
589               << L.getExpansionLineNumber() - FunL.getExpansionLineNumber()
590               << "</string>\n";
591           }
592 
593         }
594       }
595     }
596 
597     // Output the location of the bug.
598     o << "  <key>location</key>\n";
599     EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2);
600 
601     // Output the diagnostic to the sub-diagnostic client, if any.
602     if (!filesMade->empty()) {
603       StringRef lastName;
604       PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D);
605       if (files) {
606         for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(),
607                 CE = files->end(); CI != CE; ++CI) {
608           StringRef newName = CI->first;
609           if (newName != lastName) {
610             if (!lastName.empty()) {
611               o << "  </array>\n";
612             }
613             lastName = newName;
614             o <<  "  <key>" << lastName << "_files</key>\n";
615             o << "  <array>\n";
616           }
617           o << "   <string>" << CI->second << "</string>\n";
618         }
619         o << "  </array>\n";
620       }
621     }
622 
623     printCoverage(D, /*IndentLevel=*/2, Fids, FM, o);
624 
625     // Close up the entry.
626     o << "  </dict>\n";
627   }
628 
629   o << " </array>\n";
630 
631   o << " <key>files</key>\n"
632        " <array>\n";
633   for (FileID FID : Fids)
634     EmitString(o << "  ", SM.getFileEntryForID(FID)->getName()) << '\n';
635   o << " </array>\n";
636 
637   if (llvm::AreStatisticsEnabled() && AnOpts.shouldSerializeStats()) {
638     o << " <key>statistics</key>\n";
639     std::string stats;
640     llvm::raw_string_ostream os(stats);
641     llvm::PrintStatisticsJSON(os);
642     os.flush();
643     EmitString(o, html::EscapeText(stats)) << '\n';
644   }
645 
646   // Finish.
647   o << "</dict>\n</plist>";
648 }
649