PlistDiagnostics.cpp revision 3a46f5fd1709f6df03bbb8b0abf84052dc0f39ff
1afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===//
23484964a86451e86dcf04be9bd8c0d76ee04f081rossberg@chromium.org//
33484964a86451e86dcf04be9bd8c0d76ee04f081rossberg@chromium.org//                     The LLVM Compiler Infrastructure
4afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//
5196eb601290dc49c3754da728dc58700dff2de1bmachenbach@chromium.org// This file is distributed under the University of Illinois Open Source
6196eb601290dc49c3754da728dc58700dff2de1bmachenbach@chromium.org// License. See LICENSE.TXT for details.
7afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//
8afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//===----------------------------------------------------------------------===//
9afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//
10afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//  This file defines the PlistDiagnostics object.
11afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//
12afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org//===----------------------------------------------------------------------===//
13afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
14afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
15afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
16afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "clang/Basic/SourceManager.h"
17afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "clang/Basic/FileManager.h"
18afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "clang/Lex/Preprocessor.h"
19afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "llvm/Support/raw_ostream.h"
20afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "llvm/Support/Casting.h"
21afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "llvm/ADT/DenseMap.h"
22afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org#include "llvm/ADT/SmallVector.h"
23afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.orgusing namespace clang;
244f99be9ff2091451687891a05d99cc31990de709hpayer@chromium.orgusing namespace ento;
25afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
26afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.orgtypedef llvm::DenseMap<FileID, unsigned> FIDMap;
27afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
284f99be9ff2091451687891a05d99cc31990de709hpayer@chromium.org
29afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.orgnamespace {
30afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org  class PlistDiagnostics : public PathDiagnosticConsumer {
31afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    const std::string OutputFile;
32afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    const LangOptions &LangOpts;
334f99be9ff2091451687891a05d99cc31990de709hpayer@chromium.org    const bool SupportsCrossFileDiagnostics;
34afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org  public:
35afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    PlistDiagnostics(const std::string& prefix, const LangOptions &LangOpts,
36afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org                     bool supportsMultipleFiles);
37afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
38afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    virtual ~PlistDiagnostics() {}
39afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
40afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
41afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org                              FilesMade *filesMade);
42afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
43afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    virtual StringRef getName() const {
44afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org      return "PlistDiagnostics";
45afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    }
46afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
47e3c177a423baa3c30225c4e422b6f6c76d38b951machenbach@chromium.org    PathGenerationScheme getGenerationScheme() const { return Extensive; }
48afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    bool supportsLogicalOpControlFlow() const { return true; }
49afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    bool supportsAllBlockEdges() const { return true; }
50afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    virtual bool supportsCrossFileDiagnostics() const {
51afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org      return SupportsCrossFileDiagnostics;
52afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org    }
53e3c177a423baa3c30225c4e422b6f6c76d38b951machenbach@chromium.org  };
54afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org} // end anonymous namespace
55afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.org
56afbdadc5f06365a7889e7c1c1fdb7dbf596cce68machenbach@chromium.orgPlistDiagnostics::PlistDiagnostics(const std::string& output,
57                                   const LangOptions &LO,
58                                   bool supportsMultipleFiles)
59  : OutputFile(output), LangOpts(LO),
60    SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
61
62void ento::createPlistDiagnosticConsumer(PathDiagnosticConsumers &C,
63                                         const std::string& s,
64                                         const Preprocessor &PP) {
65  C.push_back(new PlistDiagnostics(s, PP.getLangOpts(), false));
66}
67
68void ento::createPlistMultiFileDiagnosticConsumer(PathDiagnosticConsumers &C,
69                                                  const std::string &s,
70                                                  const Preprocessor &PP) {
71  C.push_back(new PlistDiagnostics(s, PP.getLangOpts(), true));
72}
73
74static void AddFID(FIDMap &FIDs, SmallVectorImpl<FileID> &V,
75                   const SourceManager* SM, SourceLocation L) {
76
77  FileID FID = SM->getFileID(SM->getExpansionLoc(L));
78  FIDMap::iterator I = FIDs.find(FID);
79  if (I != FIDs.end()) return;
80  FIDs[FID] = V.size();
81  V.push_back(FID);
82}
83
84static unsigned GetFID(const FIDMap& FIDs, const SourceManager &SM,
85                       SourceLocation L) {
86  FileID FID = SM.getFileID(SM.getExpansionLoc(L));
87  FIDMap::const_iterator I = FIDs.find(FID);
88  assert(I != FIDs.end());
89  return I->second;
90}
91
92static raw_ostream &Indent(raw_ostream &o, const unsigned indent) {
93  for (unsigned i = 0; i < indent; ++i) o << ' ';
94  return o;
95}
96
97static void EmitLocation(raw_ostream &o, const SourceManager &SM,
98                         const LangOptions &LangOpts,
99                         SourceLocation L, const FIDMap &FM,
100                         unsigned indent, bool extend = false) {
101
102  FullSourceLoc Loc(SM.getExpansionLoc(L), const_cast<SourceManager&>(SM));
103
104  // Add in the length of the token, so that we cover multi-char tokens.
105  unsigned offset =
106    extend ? Lexer::MeasureTokenLength(Loc, SM, LangOpts) - 1 : 0;
107
108  Indent(o, indent) << "<dict>\n";
109  Indent(o, indent) << " <key>line</key><integer>"
110                    << Loc.getExpansionLineNumber() << "</integer>\n";
111  Indent(o, indent) << " <key>col</key><integer>"
112                    << Loc.getExpansionColumnNumber() + offset << "</integer>\n";
113  Indent(o, indent) << " <key>file</key><integer>"
114                    << GetFID(FM, SM, Loc) << "</integer>\n";
115  Indent(o, indent) << "</dict>\n";
116}
117
118static void EmitLocation(raw_ostream &o, const SourceManager &SM,
119                         const LangOptions &LangOpts,
120                         const PathDiagnosticLocation &L, const FIDMap& FM,
121                         unsigned indent, bool extend = false) {
122  EmitLocation(o, SM, LangOpts, L.asLocation(), FM, indent, extend);
123}
124
125static void EmitRange(raw_ostream &o, const SourceManager &SM,
126                      const LangOptions &LangOpts,
127                      PathDiagnosticRange R, const FIDMap &FM,
128                      unsigned indent) {
129  Indent(o, indent) << "<array>\n";
130  EmitLocation(o, SM, LangOpts, R.getBegin(), FM, indent+1);
131  EmitLocation(o, SM, LangOpts, R.getEnd(), FM, indent+1, !R.isPoint);
132  Indent(o, indent) << "</array>\n";
133}
134
135static raw_ostream &EmitString(raw_ostream &o, StringRef s) {
136  o << "<string>";
137  for (StringRef::const_iterator I = s.begin(), E = s.end(); I != E; ++I) {
138    char c = *I;
139    switch (c) {
140    default:   o << c; break;
141    case '&':  o << "&amp;"; break;
142    case '<':  o << "&lt;"; break;
143    case '>':  o << "&gt;"; break;
144    case '\'': o << "&apos;"; break;
145    case '\"': o << "&quot;"; break;
146    }
147  }
148  o << "</string>";
149  return o;
150}
151
152static void ReportControlFlow(raw_ostream &o,
153                              const PathDiagnosticControlFlowPiece& P,
154                              const FIDMap& FM,
155                              const SourceManager &SM,
156                              const LangOptions &LangOpts,
157                              unsigned indent) {
158
159  Indent(o, indent) << "<dict>\n";
160  ++indent;
161
162  Indent(o, indent) << "<key>kind</key><string>control</string>\n";
163
164  // Emit edges.
165  Indent(o, indent) << "<key>edges</key>\n";
166  ++indent;
167  Indent(o, indent) << "<array>\n";
168  ++indent;
169  for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end();
170       I!=E; ++I) {
171    Indent(o, indent) << "<dict>\n";
172    ++indent;
173
174    // Make the ranges of the start and end point self-consistent with adjacent edges
175    // by forcing to use only the beginning of the range.  This simplifies the layout
176    // logic for clients.
177    Indent(o, indent) << "<key>start</key>\n";
178    SourceLocation StartEdge = I->getStart().asRange().getBegin();
179    EmitRange(o, SM, LangOpts, SourceRange(StartEdge, StartEdge), FM, indent+1);
180
181    Indent(o, indent) << "<key>end</key>\n";
182    SourceLocation EndEdge = I->getEnd().asRange().getBegin();
183    EmitRange(o, SM, LangOpts, SourceRange(EndEdge, EndEdge), FM, indent+1);
184
185    --indent;
186    Indent(o, indent) << "</dict>\n";
187  }
188  --indent;
189  Indent(o, indent) << "</array>\n";
190  --indent;
191
192  // Output any helper text.
193  const std::string& s = P.getString();
194  if (!s.empty()) {
195    Indent(o, indent) << "<key>alternate</key>";
196    EmitString(o, s) << '\n';
197  }
198
199  --indent;
200  Indent(o, indent) << "</dict>\n";
201}
202
203static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P,
204                        const FIDMap& FM,
205                        const SourceManager &SM,
206                        const LangOptions &LangOpts,
207                        unsigned indent,
208                        unsigned depth) {
209
210  Indent(o, indent) << "<dict>\n";
211  ++indent;
212
213  Indent(o, indent) << "<key>kind</key><string>event</string>\n";
214
215  // Output the location.
216  FullSourceLoc L = P.getLocation().asLocation();
217
218  Indent(o, indent) << "<key>location</key>\n";
219  EmitLocation(o, SM, LangOpts, L, FM, indent);
220
221  // Output the ranges (if any).
222  ArrayRef<SourceRange> Ranges = P.getRanges();
223
224  if (!Ranges.empty()) {
225    Indent(o, indent) << "<key>ranges</key>\n";
226    Indent(o, indent) << "<array>\n";
227    ++indent;
228    for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end();
229         I != E; ++I) {
230      EmitRange(o, SM, LangOpts, *I, FM, indent+1);
231    }
232    --indent;
233    Indent(o, indent) << "</array>\n";
234  }
235
236  // Output the call depth.
237  Indent(o, indent) << "<key>depth</key>"
238                    << "<integer>" << depth << "</integer>\n";
239
240  // Output the text.
241  assert(!P.getString().empty());
242  Indent(o, indent) << "<key>extended_message</key>\n";
243  Indent(o, indent);
244  EmitString(o, P.getString()) << '\n';
245
246  // Output the short text.
247  // FIXME: Really use a short string.
248  Indent(o, indent) << "<key>message</key>\n";
249  EmitString(o, P.getString()) << '\n';
250
251  // Finish up.
252  --indent;
253  Indent(o, indent); o << "</dict>\n";
254}
255
256static void ReportPiece(raw_ostream &o,
257                        const PathDiagnosticPiece &P,
258                        const FIDMap& FM, const SourceManager &SM,
259                        const LangOptions &LangOpts,
260                        unsigned indent,
261                        unsigned depth,
262                        bool includeControlFlow);
263
264static void ReportCall(raw_ostream &o,
265                       const PathDiagnosticCallPiece &P,
266                       const FIDMap& FM, const SourceManager &SM,
267                       const LangOptions &LangOpts,
268                       unsigned indent,
269                       unsigned depth) {
270
271  IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnter =
272    P.getCallEnterEvent();
273
274  if (callEnter)
275    ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true);
276
277  IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnterWithinCaller =
278    P.getCallEnterWithinCallerEvent();
279
280  ++depth;
281
282  if (callEnterWithinCaller)
283    ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts,
284                indent, depth, true);
285
286  for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I)
287    ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true);
288
289  IntrusiveRefCntPtr<PathDiagnosticEventPiece> callExit =
290    P.getCallExitEvent();
291
292  if (callExit)
293    ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true);
294}
295
296static void ReportMacro(raw_ostream &o,
297                        const PathDiagnosticMacroPiece& P,
298                        const FIDMap& FM, const SourceManager &SM,
299                        const LangOptions &LangOpts,
300                        unsigned indent,
301                        unsigned depth) {
302
303  for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end();
304       I!=E; ++I) {
305    ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false);
306  }
307}
308
309static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P,
310                       const FIDMap& FM, const SourceManager &SM,
311                       const LangOptions &LangOpts) {
312  ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true);
313}
314
315static void ReportPiece(raw_ostream &o,
316                        const PathDiagnosticPiece &P,
317                        const FIDMap& FM, const SourceManager &SM,
318                        const LangOptions &LangOpts,
319                        unsigned indent,
320                        unsigned depth,
321                        bool includeControlFlow) {
322  switch (P.getKind()) {
323    case PathDiagnosticPiece::ControlFlow:
324      if (includeControlFlow)
325        ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM,
326                          LangOpts, indent);
327      break;
328    case PathDiagnosticPiece::Call:
329      ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts,
330                 indent, depth);
331      break;
332    case PathDiagnosticPiece::Event:
333      ReportEvent(o, cast<PathDiagnosticSpotPiece>(P), FM, SM, LangOpts,
334                  indent, depth);
335      break;
336    case PathDiagnosticPiece::Macro:
337      ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts,
338                  indent, depth);
339      break;
340  }
341}
342
343void PlistDiagnostics::FlushDiagnosticsImpl(
344                                    std::vector<const PathDiagnostic *> &Diags,
345                                    FilesMade *filesMade) {
346  // Build up a set of FIDs that we use by scanning the locations and
347  // ranges of the diagnostics.
348  FIDMap FM;
349  SmallVector<FileID, 10> Fids;
350  const SourceManager* SM = 0;
351
352  if (!Diags.empty())
353    SM = &(*(*Diags.begin())->path.begin())->getLocation().getManager();
354
355
356  for (std::vector<const PathDiagnostic*>::iterator DI = Diags.begin(),
357       DE = Diags.end(); DI != DE; ++DI) {
358
359    const PathDiagnostic *D = *DI;
360
361    llvm::SmallVector<const PathPieces *, 5> WorkList;
362    WorkList.push_back(&D->path);
363
364    while (!WorkList.empty()) {
365      const PathPieces &path = *WorkList.back();
366      WorkList.pop_back();
367
368      for (PathPieces::const_iterator I = path.begin(), E = path.end();
369           I!=E; ++I) {
370        const PathDiagnosticPiece *piece = I->getPtr();
371        AddFID(FM, Fids, SM, piece->getLocation().asLocation());
372        ArrayRef<SourceRange> Ranges = piece->getRanges();
373        for (ArrayRef<SourceRange>::iterator I = Ranges.begin(),
374                                             E = Ranges.end(); I != E; ++I) {
375          AddFID(FM, Fids, SM, I->getBegin());
376          AddFID(FM, Fids, SM, I->getEnd());
377        }
378
379        if (const PathDiagnosticCallPiece *call =
380            dyn_cast<PathDiagnosticCallPiece>(piece)) {
381          IntrusiveRefCntPtr<PathDiagnosticEventPiece>
382            callEnterWithin = call->getCallEnterWithinCallerEvent();
383          if (callEnterWithin)
384            AddFID(FM, Fids, SM, callEnterWithin->getLocation().asLocation());
385
386          WorkList.push_back(&call->path);
387        }
388        else if (const PathDiagnosticMacroPiece *macro =
389                 dyn_cast<PathDiagnosticMacroPiece>(piece)) {
390          WorkList.push_back(&macro->subPieces);
391        }
392      }
393    }
394  }
395
396  // Open the file.
397  std::string ErrMsg;
398  llvm::raw_fd_ostream o(OutputFile.c_str(), ErrMsg);
399  if (!ErrMsg.empty()) {
400    llvm::errs() << "warning: could not create file: " << OutputFile << '\n';
401    return;
402  }
403
404  // Write the plist header.
405  o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
406  "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
407  "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
408  "<plist version=\"1.0\">\n";
409
410  // Write the root object: a <dict> containing...
411  //  - "files", an <array> mapping from FIDs to file names
412  //  - "diagnostics", an <array> containing the path diagnostics
413  o << "<dict>\n"
414       " <key>files</key>\n"
415       " <array>\n";
416
417  for (SmallVectorImpl<FileID>::iterator I=Fids.begin(), E=Fids.end();
418       I!=E; ++I) {
419    o << "  ";
420    EmitString(o, SM->getFileEntryForID(*I)->getName()) << '\n';
421  }
422
423  o << " </array>\n"
424       " <key>diagnostics</key>\n"
425       " <array>\n";
426
427  for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
428       DE = Diags.end(); DI!=DE; ++DI) {
429
430    o << "  <dict>\n"
431         "   <key>path</key>\n";
432
433    const PathDiagnostic *D = *DI;
434
435    o << "   <array>\n";
436
437    for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end();
438         I != E; ++I)
439      ReportDiag(o, **I, FM, *SM, LangOpts);
440
441    o << "   </array>\n";
442
443    // Output the bug type and bug category.
444    o << "   <key>description</key>";
445    EmitString(o, D->getShortDescription()) << '\n';
446    o << "   <key>category</key>";
447    EmitString(o, D->getCategory()) << '\n';
448    o << "   <key>type</key>";
449    EmitString(o, D->getBugType()) << '\n';
450
451    // Output information about the semantic context where
452    // the issue occurred.
453    if (const Decl *DeclWithIssue = D->getDeclWithIssue()) {
454      // FIXME: handle blocks, which have no name.
455      if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
456        StringRef declKind;
457        switch (ND->getKind()) {
458          case Decl::CXXRecord:
459            declKind = "C++ class";
460            break;
461          case Decl::CXXMethod:
462            declKind = "C++ method";
463            break;
464          case Decl::ObjCMethod:
465            declKind = "Objective-C method";
466            break;
467          case Decl::Function:
468            declKind = "function";
469            break;
470          default:
471            break;
472        }
473        if (!declKind.empty()) {
474          const std::string &declName = ND->getDeclName().getAsString();
475          o << "  <key>issue_context_kind</key>";
476          EmitString(o, declKind) << '\n';
477          o << "  <key>issue_context</key>";
478          EmitString(o, declName) << '\n';
479        }
480
481        // Output the bug hash for issue unique-ing. Currently, it's just an
482        // offset from the beginning of the function.
483        if (const Stmt *Body = DeclWithIssue->getBody()) {
484          FullSourceLoc Loc(SM->getExpansionLoc(D->getLocation().asLocation()),
485                            *SM);
486          FullSourceLoc FunLoc(SM->getExpansionLoc(Body->getLocStart()), *SM);
487          o << "  <key>issue_hash</key><integer>"
488              << Loc.getExpansionLineNumber() - FunLoc.getExpansionLineNumber()
489              << "</integer>\n";
490        }
491      }
492    }
493
494    // Output the location of the bug.
495    o << "  <key>location</key>\n";
496    EmitLocation(o, *SM, LangOpts, D->getLocation(), FM, 2);
497
498    // Output the diagnostic to the sub-diagnostic client, if any.
499    if (!filesMade->empty()) {
500      StringRef lastName;
501      PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D);
502      if (files) {
503        for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(),
504                CE = files->end(); CI != CE; ++CI) {
505          StringRef newName = CI->first;
506          if (newName != lastName) {
507            if (!lastName.empty()) {
508              o << "  </array>\n";
509            }
510            lastName = newName;
511            o <<  "  <key>" << lastName << "_files</key>\n";
512            o << "  <array>\n";
513          }
514          o << "   <string>" << CI->second << "</string>\n";
515        }
516        o << "  </array>\n";
517      }
518    }
519
520    // Close up the entry.
521    o << "  </dict>\n";
522  }
523
524  o << " </array>\n";
525
526  // Finish.
527  o << "</dict>\n</plist>";
528}
529