/Users/buildslave/jenkins/sharedspace/clang-stage2-coverage-R@2/llvm/tools/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
Line | Count | Source (jump to first uncovered line) |
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/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" |
20 | | #include "clang/StaticAnalyzer/Core/IssueHash.h" |
21 | | #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
22 | | #include "llvm/ADT/SmallVector.h" |
23 | | #include "llvm/Support/Casting.h" |
24 | | using namespace clang; |
25 | | using namespace ento; |
26 | | using namespace markup; |
27 | | |
28 | | namespace { |
29 | | class PlistDiagnostics : public PathDiagnosticConsumer { |
30 | | const std::string OutputFile; |
31 | | const LangOptions &LangOpts; |
32 | | const bool SupportsCrossFileDiagnostics; |
33 | | public: |
34 | | PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, |
35 | | const std::string& prefix, |
36 | | const LangOptions &LangOpts, |
37 | | bool supportsMultipleFiles); |
38 | | |
39 | 41 | ~PlistDiagnostics() override {} |
40 | | |
41 | | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
42 | | FilesMade *filesMade) override; |
43 | | |
44 | 0 | StringRef getName() const override { |
45 | 0 | return "PlistDiagnostics"; |
46 | 0 | } |
47 | | |
48 | 555 | PathGenerationScheme getGenerationScheme() const override { |
49 | 555 | return Extensive; |
50 | 555 | } |
51 | 0 | bool supportsLogicalOpControlFlow() const override { return true; } |
52 | 570 | bool supportsCrossFileDiagnostics() const override { |
53 | 570 | return SupportsCrossFileDiagnostics; |
54 | 570 | } |
55 | | }; |
56 | | } // end anonymous namespace |
57 | | |
58 | | PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, |
59 | | const std::string& output, |
60 | | const LangOptions &LO, |
61 | | bool supportsMultipleFiles) |
62 | | : OutputFile(output), |
63 | | LangOpts(LO), |
64 | 41 | SupportsCrossFileDiagnostics(supportsMultipleFiles) {} |
65 | | |
66 | | void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, |
67 | | PathDiagnosticConsumers &C, |
68 | | const std::string& s, |
69 | 22 | const Preprocessor &PP) { |
70 | 22 | C.push_back(new PlistDiagnostics(AnalyzerOpts, s, |
71 | 22 | PP.getLangOpts(), false)); |
72 | 22 | } |
73 | | |
74 | | void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, |
75 | | PathDiagnosticConsumers &C, |
76 | | const std::string &s, |
77 | 19 | const Preprocessor &PP) { |
78 | 19 | C.push_back(new PlistDiagnostics(AnalyzerOpts, s, |
79 | 19 | PP.getLangOpts(), true)); |
80 | 19 | } |
81 | | |
82 | | static void ReportControlFlow(raw_ostream &o, |
83 | | const PathDiagnosticControlFlowPiece& P, |
84 | | const FIDMap& FM, |
85 | | const SourceManager &SM, |
86 | | const LangOptions &LangOpts, |
87 | 2.01k | unsigned indent) { |
88 | 2.01k | |
89 | 2.01k | Indent(o, indent) << "<dict>\n"; |
90 | 2.01k | ++indent; |
91 | 2.01k | |
92 | 2.01k | Indent(o, indent) << "<key>kind</key><string>control</string>\n"; |
93 | 2.01k | |
94 | 2.01k | // Emit edges. |
95 | 2.01k | Indent(o, indent) << "<key>edges</key>\n"; |
96 | 2.01k | ++indent; |
97 | 2.01k | Indent(o, indent) << "<array>\n"; |
98 | 2.01k | ++indent; |
99 | 2.01k | for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); |
100 | 4.02k | I!=E4.02k ; ++I2.01k ) { |
101 | 2.01k | Indent(o, indent) << "<dict>\n"; |
102 | 2.01k | ++indent; |
103 | 2.01k | |
104 | 2.01k | // Make the ranges of the start and end point self-consistent with adjacent edges |
105 | 2.01k | // by forcing to use only the beginning of the range. This simplifies the layout |
106 | 2.01k | // logic for clients. |
107 | 2.01k | Indent(o, indent) << "<key>start</key>\n"; |
108 | 2.01k | SourceRange StartEdge( |
109 | 2.01k | SM.getExpansionLoc(I->getStart().asRange().getBegin())); |
110 | 2.01k | EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM, |
111 | 2.01k | indent + 1); |
112 | 2.01k | |
113 | 2.01k | Indent(o, indent) << "<key>end</key>\n"; |
114 | 2.01k | SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin())); |
115 | 2.01k | EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM, |
116 | 2.01k | indent + 1); |
117 | 2.01k | |
118 | 2.01k | --indent; |
119 | 2.01k | Indent(o, indent) << "</dict>\n"; |
120 | 2.01k | } |
121 | 2.01k | --indent; |
122 | 2.01k | Indent(o, indent) << "</array>\n"; |
123 | 2.01k | --indent; |
124 | 2.01k | |
125 | 2.01k | // Output any helper text. |
126 | 2.01k | const auto &s = P.getString(); |
127 | 2.01k | if (!s.empty()2.01k ) { |
128 | 0 | Indent(o, indent) << "<key>alternate</key>"; |
129 | 0 | EmitString(o, s) << '\n'; |
130 | 0 | } |
131 | 2.01k | |
132 | 2.01k | --indent; |
133 | 2.01k | Indent(o, indent) << "</dict>\n"; |
134 | 2.01k | } |
135 | | |
136 | | static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, |
137 | | const FIDMap& FM, |
138 | | const SourceManager &SM, |
139 | | const LangOptions &LangOpts, |
140 | | unsigned indent, |
141 | | unsigned depth, |
142 | 1.79k | bool isKeyEvent = false) { |
143 | 1.79k | |
144 | 1.79k | Indent(o, indent) << "<dict>\n"; |
145 | 1.79k | ++indent; |
146 | 1.79k | |
147 | 1.79k | Indent(o, indent) << "<key>kind</key><string>event</string>\n"; |
148 | 1.79k | |
149 | 1.79k | if (isKeyEvent1.79k ) { |
150 | 2 | Indent(o, indent) << "<key>key_event</key><true/>\n"; |
151 | 2 | } |
152 | 1.79k | |
153 | 1.79k | // Output the location. |
154 | 1.79k | FullSourceLoc L = P.getLocation().asLocation(); |
155 | 1.79k | |
156 | 1.79k | Indent(o, indent) << "<key>location</key>\n"; |
157 | 1.79k | EmitLocation(o, SM, L, FM, indent); |
158 | 1.79k | |
159 | 1.79k | // Output the ranges (if any). |
160 | 1.79k | ArrayRef<SourceRange> Ranges = P.getRanges(); |
161 | 1.79k | |
162 | 1.79k | if (!Ranges.empty()1.79k ) { |
163 | 1.57k | Indent(o, indent) << "<key>ranges</key>\n"; |
164 | 1.57k | Indent(o, indent) << "<array>\n"; |
165 | 1.57k | ++indent; |
166 | 1.57k | for (auto &R : Ranges) |
167 | 1.74k | EmitRange(o, SM, |
168 | 1.74k | Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), |
169 | 1.74k | FM, indent + 1); |
170 | 1.57k | --indent; |
171 | 1.57k | Indent(o, indent) << "</array>\n"; |
172 | 1.57k | } |
173 | 1.79k | |
174 | 1.79k | // Output the call depth. |
175 | 1.79k | Indent(o, indent) << "<key>depth</key>"; |
176 | 1.79k | EmitInteger(o, depth) << '\n'; |
177 | 1.79k | |
178 | 1.79k | // Output the text. |
179 | 1.79k | assert(!P.getString().empty()); |
180 | 1.79k | Indent(o, indent) << "<key>extended_message</key>\n"; |
181 | 1.79k | Indent(o, indent); |
182 | 1.79k | EmitString(o, P.getString()) << '\n'; |
183 | 1.79k | |
184 | 1.79k | // Output the short text. |
185 | 1.79k | // FIXME: Really use a short string. |
186 | 1.79k | Indent(o, indent) << "<key>message</key>\n"; |
187 | 1.79k | Indent(o, indent); |
188 | 1.79k | EmitString(o, P.getString()) << '\n'; |
189 | 1.79k | |
190 | 1.79k | // Finish up. |
191 | 1.79k | --indent; |
192 | 1.79k | Indent(o, indent); o << "</dict>\n"; |
193 | 1.79k | } |
194 | | |
195 | | static void ReportPiece(raw_ostream &o, |
196 | | const PathDiagnosticPiece &P, |
197 | | const FIDMap& FM, const SourceManager &SM, |
198 | | const LangOptions &LangOpts, |
199 | | unsigned indent, |
200 | | unsigned depth, |
201 | | bool includeControlFlow, |
202 | | bool isKeyEvent = false); |
203 | | |
204 | | static void ReportCall(raw_ostream &o, |
205 | | const PathDiagnosticCallPiece &P, |
206 | | const FIDMap& FM, const SourceManager &SM, |
207 | | const LangOptions &LangOpts, |
208 | | unsigned indent, |
209 | 121 | unsigned depth) { |
210 | 121 | |
211 | 121 | if (auto callEnter = P.getCallEnterEvent()) |
212 | 120 | ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true, |
213 | 120 | P.isLastInMainSourceFile()); |
214 | 121 | |
215 | 121 | |
216 | 121 | ++depth; |
217 | 121 | |
218 | 121 | if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) |
219 | 110 | ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts, |
220 | 110 | indent, depth, true); |
221 | 121 | |
222 | 526 | for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E526 ;++I405 ) |
223 | 405 | ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true); |
224 | 121 | |
225 | 121 | --depth; |
226 | 121 | |
227 | 121 | if (auto callExit = P.getCallExitEvent()) |
228 | 53 | ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true); |
229 | 121 | } |
230 | | |
231 | | static void ReportMacro(raw_ostream &o, |
232 | | const PathDiagnosticMacroPiece& P, |
233 | | const FIDMap& FM, const SourceManager &SM, |
234 | | const LangOptions &LangOpts, |
235 | | unsigned indent, |
236 | 0 | unsigned depth) { |
237 | 0 |
|
238 | 0 | for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); |
239 | 0 | I!=E0 ; ++I0 ) { |
240 | 0 | ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false); |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P, |
245 | | const FIDMap& FM, const SourceManager &SM, |
246 | 3.24k | const LangOptions &LangOpts) { |
247 | 3.24k | ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true); |
248 | 3.24k | } |
249 | | |
250 | | static void ReportPiece(raw_ostream &o, |
251 | | const PathDiagnosticPiece &P, |
252 | | const FIDMap& FM, const SourceManager &SM, |
253 | | const LangOptions &LangOpts, |
254 | | unsigned indent, |
255 | | unsigned depth, |
256 | | bool includeControlFlow, |
257 | 3.92k | bool isKeyEvent) { |
258 | 3.92k | switch (P.getKind()) { |
259 | 2.01k | case PathDiagnosticPiece::ControlFlow: |
260 | 2.01k | if (includeControlFlow) |
261 | 2.01k | ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM, |
262 | 2.01k | LangOpts, indent); |
263 | 2.01k | break; |
264 | 121 | case PathDiagnosticPiece::Call: |
265 | 121 | ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts, |
266 | 121 | indent, depth); |
267 | 121 | break; |
268 | 1.79k | case PathDiagnosticPiece::Event: |
269 | 1.79k | ReportEvent(o, cast<PathDiagnosticSpotPiece>(P), FM, SM, LangOpts, |
270 | 1.79k | indent, depth, isKeyEvent); |
271 | 1.79k | break; |
272 | 0 | case PathDiagnosticPiece::Macro: |
273 | 0 | ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts, |
274 | 0 | indent, depth); |
275 | 0 | break; |
276 | 1 | case PathDiagnosticPiece::Note: |
277 | 1 | // FIXME: Extend the plist format to support those. |
278 | 1 | break; |
279 | 3.92k | } |
280 | 3.92k | } |
281 | | |
282 | | void PlistDiagnostics::FlushDiagnosticsImpl( |
283 | | std::vector<const PathDiagnostic *> &Diags, |
284 | 41 | FilesMade *filesMade) { |
285 | 41 | // Build up a set of FIDs that we use by scanning the locations and |
286 | 41 | // ranges of the diagnostics. |
287 | 41 | FIDMap FM; |
288 | 41 | SmallVector<FileID, 10> Fids; |
289 | 41 | const SourceManager* SM = nullptr; |
290 | 41 | |
291 | 41 | if (!Diags.empty()) |
292 | 41 | SM = &Diags.front()->path.front()->getLocation().getManager(); |
293 | 41 | |
294 | 3.87k | auto AddPieceFID = [&FM, &Fids, SM](const PathDiagnosticPiece &Piece) { |
295 | 3.87k | AddFID(FM, Fids, *SM, Piece.getLocation().asLocation()); |
296 | 3.87k | ArrayRef<SourceRange> Ranges = Piece.getRanges(); |
297 | 1.69k | for (const SourceRange &Range : Ranges) { |
298 | 1.69k | AddFID(FM, Fids, *SM, Range.getBegin()); |
299 | 1.69k | AddFID(FM, Fids, *SM, Range.getEnd()); |
300 | 1.69k | } |
301 | 3.87k | }; |
302 | 41 | |
303 | 565 | for (const PathDiagnostic *D : Diags) { |
304 | 565 | |
305 | 565 | SmallVector<const PathPieces *, 5> WorkList; |
306 | 565 | WorkList.push_back(&D->path); |
307 | 565 | |
308 | 1.25k | while (!WorkList.empty()1.25k ) { |
309 | 686 | const PathPieces &Path = *WorkList.pop_back_val(); |
310 | 686 | |
311 | 3.64k | for (const auto &Iter : Path) { |
312 | 3.64k | const PathDiagnosticPiece &Piece = *Iter; |
313 | 3.64k | AddPieceFID(Piece); |
314 | 3.64k | |
315 | 3.64k | if (const PathDiagnosticCallPiece *Call = |
316 | 121 | dyn_cast<PathDiagnosticCallPiece>(&Piece)) { |
317 | 121 | if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) |
318 | 110 | AddPieceFID(*CallEnterWithin); |
319 | 121 | |
320 | 121 | if (auto CallEnterEvent = Call->getCallEnterEvent()) |
321 | 120 | AddPieceFID(*CallEnterEvent); |
322 | 121 | |
323 | 121 | WorkList.push_back(&Call->path); |
324 | 3.64k | } else if (const PathDiagnosticMacroPiece *3.52k Macro3.52k = |
325 | 0 | dyn_cast<PathDiagnosticMacroPiece>(&Piece)) { |
326 | 0 | WorkList.push_back(&Macro->subPieces); |
327 | 0 | } |
328 | 3.64k | } |
329 | 686 | } |
330 | 565 | } |
331 | 41 | |
332 | 41 | // Open the file. |
333 | 41 | std::error_code EC; |
334 | 41 | llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::F_Text); |
335 | 41 | if (EC41 ) { |
336 | 0 | llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; |
337 | 0 | return; |
338 | 0 | } |
339 | 41 | |
340 | 41 | EmitPlistHeader(o); |
341 | 41 | |
342 | 41 | // Write the root object: a <dict> containing... |
343 | 41 | // - "clang_version", the string representation of clang version |
344 | 41 | // - "files", an <array> mapping from FIDs to file names |
345 | 41 | // - "diagnostics", an <array> containing the path diagnostics |
346 | 41 | o << "<dict>\n" << |
347 | 41 | " <key>clang_version</key>\n"; |
348 | 41 | EmitString(o, getClangFullVersion()) << '\n'; |
349 | 41 | o << " <key>files</key>\n" |
350 | 41 | " <array>\n"; |
351 | 41 | |
352 | 41 | for (FileID FID : Fids) |
353 | 44 | EmitString(o << " ", SM->getFileEntryForID(FID)->getName()) << '\n'; |
354 | 41 | |
355 | 41 | o << " </array>\n" |
356 | 41 | " <key>diagnostics</key>\n" |
357 | 41 | " <array>\n"; |
358 | 41 | |
359 | 41 | for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), |
360 | 606 | DE = Diags.end(); DI!=DE606 ; ++DI565 ) { |
361 | 565 | |
362 | 565 | o << " <dict>\n" |
363 | 565 | " <key>path</key>\n"; |
364 | 565 | |
365 | 565 | const PathDiagnostic *D = *DI; |
366 | 565 | |
367 | 565 | o << " <array>\n"; |
368 | 565 | |
369 | 565 | for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); |
370 | 3.80k | I != E3.80k ; ++I3.24k ) |
371 | 3.24k | ReportDiag(o, **I, FM, *SM, LangOpts); |
372 | 565 | |
373 | 565 | o << " </array>\n"; |
374 | 565 | |
375 | 565 | // Output the bug type and bug category. |
376 | 565 | o << " <key>description</key>"; |
377 | 565 | EmitString(o, D->getShortDescription()) << '\n'; |
378 | 565 | o << " <key>category</key>"; |
379 | 565 | EmitString(o, D->getCategory()) << '\n'; |
380 | 565 | o << " <key>type</key>"; |
381 | 565 | EmitString(o, D->getBugType()) << '\n'; |
382 | 565 | o << " <key>check_name</key>"; |
383 | 565 | EmitString(o, D->getCheckName()) << '\n'; |
384 | 565 | |
385 | 565 | o << " <!-- This hash is experimental and going to change! -->\n"; |
386 | 565 | o << " <key>issue_hash_content_of_line_in_context</key>"; |
387 | 565 | PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); |
388 | 565 | FullSourceLoc L(SM->getExpansionLoc(UPDLoc.isValid() |
389 | 192 | ? UPDLoc.asLocation() |
390 | 373 | : D->getLocation().asLocation()), |
391 | 565 | *SM); |
392 | 565 | const Decl *DeclWithIssue = D->getDeclWithIssue(); |
393 | 565 | EmitString(o, GetIssueHash(*SM, L, D->getCheckName(), D->getBugType(), |
394 | 565 | DeclWithIssue, LangOpts)) |
395 | 565 | << '\n'; |
396 | 565 | |
397 | 565 | // Output information about the semantic context where |
398 | 565 | // the issue occurred. |
399 | 565 | if (const Decl *DeclWithIssue565 = D->getDeclWithIssue()) { |
400 | 563 | // FIXME: handle blocks, which have no name. |
401 | 563 | if (const NamedDecl *ND563 = dyn_cast<NamedDecl>(DeclWithIssue)) { |
402 | 554 | StringRef declKind; |
403 | 554 | switch (ND->getKind()) { |
404 | 0 | case Decl::CXXRecord: |
405 | 0 | declKind = "C++ class"; |
406 | 0 | break; |
407 | 11 | case Decl::CXXMethod: |
408 | 11 | declKind = "C++ method"; |
409 | 11 | break; |
410 | 50 | case Decl::ObjCMethod: |
411 | 50 | declKind = "Objective-C method"; |
412 | 50 | break; |
413 | 479 | case Decl::Function: |
414 | 479 | declKind = "function"; |
415 | 479 | break; |
416 | 14 | default: |
417 | 14 | break; |
418 | 554 | } |
419 | 554 | if (554 !declKind.empty()554 ) { |
420 | 540 | const std::string &declName = ND->getDeclName().getAsString(); |
421 | 540 | o << " <key>issue_context_kind</key>"; |
422 | 540 | EmitString(o, declKind) << '\n'; |
423 | 540 | o << " <key>issue_context</key>"; |
424 | 540 | EmitString(o, declName) << '\n'; |
425 | 540 | } |
426 | 554 | |
427 | 554 | // Output the bug hash for issue unique-ing. Currently, it's just an |
428 | 554 | // offset from the beginning of the function. |
429 | 554 | if (const Stmt *Body554 = DeclWithIssue->getBody()) { |
430 | 554 | |
431 | 554 | // If the bug uniqueing location exists, use it for the hash. |
432 | 554 | // For example, this ensures that two leaks reported on the same line |
433 | 554 | // will have different issue_hashes and that the hash will identify |
434 | 554 | // the leak location even after code is added between the allocation |
435 | 554 | // site and the end of scope (leak report location). |
436 | 554 | if (UPDLoc.isValid()554 ) { |
437 | 191 | FullSourceLoc UFunL(SM->getExpansionLoc( |
438 | 191 | D->getUniqueingDecl()->getBody()->getLocStart()), *SM); |
439 | 191 | o << " <key>issue_hash_function_offset</key><string>" |
440 | 191 | << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() |
441 | 191 | << "</string>\n"; |
442 | 191 | |
443 | 191 | // Otherwise, use the location on which the bug is reported. |
444 | 554 | } else { |
445 | 363 | FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM); |
446 | 363 | o << " <key>issue_hash_function_offset</key><string>" |
447 | 363 | << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() |
448 | 363 | << "</string>\n"; |
449 | 363 | } |
450 | 554 | |
451 | 554 | } |
452 | 554 | } |
453 | 563 | } |
454 | 565 | |
455 | 565 | // Output the location of the bug. |
456 | 565 | o << " <key>location</key>\n"; |
457 | 565 | EmitLocation(o, *SM, D->getLocation().asLocation(), FM, 2); |
458 | 565 | |
459 | 565 | // Output the diagnostic to the sub-diagnostic client, if any. |
460 | 565 | if (!filesMade->empty()565 ) { |
461 | 1 | StringRef lastName; |
462 | 1 | PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D); |
463 | 1 | if (files1 ) { |
464 | 1 | for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), |
465 | 2 | CE = files->end(); CI != CE2 ; ++CI1 ) { |
466 | 1 | StringRef newName = CI->first; |
467 | 1 | if (newName != lastName1 ) { |
468 | 1 | if (!lastName.empty()1 ) { |
469 | 0 | o << " </array>\n"; |
470 | 0 | } |
471 | 1 | lastName = newName; |
472 | 1 | o << " <key>" << lastName << "_files</key>\n"; |
473 | 1 | o << " <array>\n"; |
474 | 1 | } |
475 | 1 | o << " <string>" << CI->second << "</string>\n"; |
476 | 1 | } |
477 | 1 | o << " </array>\n"; |
478 | 1 | } |
479 | 1 | } |
480 | 565 | |
481 | 565 | // Close up the entry. |
482 | 565 | o << " </dict>\n"; |
483 | 565 | } |
484 | 41 | |
485 | 41 | o << " </array>\n"; |
486 | 41 | |
487 | 41 | // Finish. |
488 | 41 | o << "</dict>\n</plist>"; |
489 | 41 | } |