1 //===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file defines the PlistDiagnostics object.
12 //===----------------------------------------------------------------------===//
14 #include "clang/Checker/PathDiagnosticClients.h"
15 #include "clang/Checker/BugReporter/PathDiagnostic.h"
16 #include "clang/Basic/SourceManager.h"
17 #include "clang/Basic/FileManager.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "llvm/Support/raw_ostream.h"
20 #include "llvm/Support/Casting.h"
21 #include "llvm/ADT/DenseMap.h"
22 #include "llvm/ADT/SmallVector.h"
23 using namespace clang
;
26 typedef llvm::DenseMap
<FileID
, unsigned> FIDMap
;
33 struct CompareDiagnostics
{
34 // Compare if 'X' is "<" than 'Y'.
35 bool operator()(const PathDiagnostic
*X
, const PathDiagnostic
*Y
) const {
36 // First compare by location
37 const FullSourceLoc
&XLoc
= X
->getLocation().asLocation();
38 const FullSourceLoc
&YLoc
= Y
->getLocation().asLocation();
44 // Next, compare by bug type.
45 llvm::StringRef XBugType
= X
->getBugType();
46 llvm::StringRef YBugType
= Y
->getBugType();
47 if (XBugType
< YBugType
)
49 if (XBugType
!= YBugType
)
52 // Next, compare by bug description.
53 llvm::StringRef XDesc
= X
->getDescription();
54 llvm::StringRef YDesc
= Y
->getDescription();
60 // FIXME: Further refine by comparing PathDiagnosticPieces?
67 class PlistDiagnostics
: public PathDiagnosticClient
{
68 std::vector
<const PathDiagnostic
*> BatchedDiags
;
69 const std::string OutputFile
;
70 const LangOptions
&LangOpts
;
71 llvm::OwningPtr
<PathDiagnosticClient
> SubPD
;
74 PlistDiagnostics(const std::string
& prefix
, const LangOptions
&LangOpts
,
75 PathDiagnosticClient
*subPD
);
77 ~PlistDiagnostics() { FlushDiagnostics(NULL
); }
79 void FlushDiagnostics(llvm::SmallVectorImpl
<std::string
> *FilesMade
);
81 void HandlePathDiagnostic(const PathDiagnostic
* D
);
83 virtual llvm::StringRef
getName() const {
84 return "PlistDiagnostics";
87 PathGenerationScheme
getGenerationScheme() const;
88 bool supportsLogicalOpControlFlow() const { return true; }
89 bool supportsAllBlockEdges() const { return true; }
90 virtual bool useVerboseDescription() const { return false; }
92 } // end anonymous namespace
94 PlistDiagnostics::PlistDiagnostics(const std::string
& output
,
95 const LangOptions
&LO
,
96 PathDiagnosticClient
*subPD
)
97 : OutputFile(output
), LangOpts(LO
), SubPD(subPD
), flushed(false) {}
100 clang::CreatePlistDiagnosticClient(const std::string
& s
, const Preprocessor
&PP
,
101 PathDiagnosticClient
*subPD
) {
102 return new PlistDiagnostics(s
, PP
.getLangOptions(), subPD
);
105 PathDiagnosticClient::PathGenerationScheme
106 PlistDiagnostics::getGenerationScheme() const {
107 if (const PathDiagnosticClient
*PD
= SubPD
.get())
108 return PD
->getGenerationScheme();
113 static void AddFID(FIDMap
&FIDs
, llvm::SmallVectorImpl
<FileID
> &V
,
114 const SourceManager
* SM
, SourceLocation L
) {
116 FileID FID
= SM
->getFileID(SM
->getInstantiationLoc(L
));
117 FIDMap::iterator I
= FIDs
.find(FID
);
118 if (I
!= FIDs
.end()) return;
119 FIDs
[FID
] = V
.size();
123 static unsigned GetFID(const FIDMap
& FIDs
, const SourceManager
&SM
,
125 FileID FID
= SM
.getFileID(SM
.getInstantiationLoc(L
));
126 FIDMap::const_iterator I
= FIDs
.find(FID
);
127 assert(I
!= FIDs
.end());
131 static llvm::raw_ostream
& Indent(llvm::raw_ostream
& o
, const unsigned indent
) {
132 for (unsigned i
= 0; i
< indent
; ++i
) o
<< ' ';
136 static void EmitLocation(llvm::raw_ostream
& o
, const SourceManager
&SM
,
137 const LangOptions
&LangOpts
,
138 SourceLocation L
, const FIDMap
&FM
,
139 unsigned indent
, bool extend
= false) {
141 FullSourceLoc
Loc(SM
.getInstantiationLoc(L
), const_cast<SourceManager
&>(SM
));
143 // Add in the length of the token, so that we cover multi-char tokens.
145 extend
? Lexer::MeasureTokenLength(Loc
, SM
, LangOpts
) - 1 : 0;
147 Indent(o
, indent
) << "<dict>\n";
148 Indent(o
, indent
) << " <key>line</key><integer>"
149 << Loc
.getInstantiationLineNumber() << "</integer>\n";
150 Indent(o
, indent
) << " <key>col</key><integer>"
151 << Loc
.getInstantiationColumnNumber() + offset
<< "</integer>\n";
152 Indent(o
, indent
) << " <key>file</key><integer>"
153 << GetFID(FM
, SM
, Loc
) << "</integer>\n";
154 Indent(o
, indent
) << "</dict>\n";
157 static void EmitLocation(llvm::raw_ostream
& o
, const SourceManager
&SM
,
158 const LangOptions
&LangOpts
,
159 const PathDiagnosticLocation
&L
, const FIDMap
& FM
,
160 unsigned indent
, bool extend
= false) {
161 EmitLocation(o
, SM
, LangOpts
, L
.asLocation(), FM
, indent
, extend
);
164 static void EmitRange(llvm::raw_ostream
& o
, const SourceManager
&SM
,
165 const LangOptions
&LangOpts
,
166 PathDiagnosticRange R
, const FIDMap
&FM
,
168 Indent(o
, indent
) << "<array>\n";
169 EmitLocation(o
, SM
, LangOpts
, R
.getBegin(), FM
, indent
+1);
170 EmitLocation(o
, SM
, LangOpts
, R
.getEnd(), FM
, indent
+1, !R
.isPoint
);
171 Indent(o
, indent
) << "</array>\n";
174 static llvm::raw_ostream
& EmitString(llvm::raw_ostream
& o
,
175 const std::string
& s
) {
177 for (std::string::const_iterator I
=s
.begin(), E
=s
.end(); I
!=E
; ++I
) {
180 default: o
<< c
; break;
181 case '&': o
<< "&"; break;
182 case '<': o
<< "<"; break;
183 case '>': o
<< ">"; break;
184 case '\'': o
<< "'"; break;
185 case '\"': o
<< """; break;
192 static void ReportControlFlow(llvm::raw_ostream
& o
,
193 const PathDiagnosticControlFlowPiece
& P
,
195 const SourceManager
&SM
,
196 const LangOptions
&LangOpts
,
199 Indent(o
, indent
) << "<dict>\n";
202 Indent(o
, indent
) << "<key>kind</key><string>control</string>\n";
205 Indent(o
, indent
) << "<key>edges</key>\n";
207 Indent(o
, indent
) << "<array>\n";
209 for (PathDiagnosticControlFlowPiece::const_iterator I
=P
.begin(), E
=P
.end();
211 Indent(o
, indent
) << "<dict>\n";
213 Indent(o
, indent
) << "<key>start</key>\n";
214 EmitRange(o
, SM
, LangOpts
, I
->getStart().asRange(), FM
, indent
+1);
215 Indent(o
, indent
) << "<key>end</key>\n";
216 EmitRange(o
, SM
, LangOpts
, I
->getEnd().asRange(), FM
, indent
+1);
218 Indent(o
, indent
) << "</dict>\n";
221 Indent(o
, indent
) << "</array>\n";
224 // Output any helper text.
225 const std::string
& s
= P
.getString();
227 Indent(o
, indent
) << "<key>alternate</key>";
228 EmitString(o
, s
) << '\n';
232 Indent(o
, indent
) << "</dict>\n";
235 static void ReportEvent(llvm::raw_ostream
& o
, const PathDiagnosticPiece
& P
,
237 const SourceManager
&SM
,
238 const LangOptions
&LangOpts
,
241 Indent(o
, indent
) << "<dict>\n";
244 Indent(o
, indent
) << "<key>kind</key><string>event</string>\n";
246 // Output the location.
247 FullSourceLoc L
= P
.getLocation().asLocation();
249 Indent(o
, indent
) << "<key>location</key>\n";
250 EmitLocation(o
, SM
, LangOpts
, L
, FM
, indent
);
252 // Output the ranges (if any).
253 PathDiagnosticPiece::range_iterator RI
= P
.ranges_begin(),
257 Indent(o
, indent
) << "<key>ranges</key>\n";
258 Indent(o
, indent
) << "<array>\n";
260 for (; RI
!= RE
; ++RI
)
261 EmitRange(o
, SM
, LangOpts
, *RI
, FM
, indent
+1);
263 Indent(o
, indent
) << "</array>\n";
267 assert(!P
.getString().empty());
268 Indent(o
, indent
) << "<key>extended_message</key>\n";
270 EmitString(o
, P
.getString()) << '\n';
272 // Output the short text.
273 // FIXME: Really use a short string.
274 Indent(o
, indent
) << "<key>message</key>\n";
275 EmitString(o
, P
.getString()) << '\n';
279 Indent(o
, indent
); o
<< "</dict>\n";
282 static void ReportMacro(llvm::raw_ostream
& o
,
283 const PathDiagnosticMacroPiece
& P
,
284 const FIDMap
& FM
, const SourceManager
&SM
,
285 const LangOptions
&LangOpts
,
288 for (PathDiagnosticMacroPiece::const_iterator I
=P
.begin(), E
=P
.end();
291 switch ((*I
)->getKind()) {
294 case PathDiagnosticPiece::Event
:
295 ReportEvent(o
, cast
<PathDiagnosticEventPiece
>(**I
), FM
, SM
, LangOpts
,
298 case PathDiagnosticPiece::Macro
:
299 ReportMacro(o
, cast
<PathDiagnosticMacroPiece
>(**I
), FM
, SM
, LangOpts
,
306 static void ReportDiag(llvm::raw_ostream
& o
, const PathDiagnosticPiece
& P
,
307 const FIDMap
& FM
, const SourceManager
&SM
,
308 const LangOptions
&LangOpts
) {
312 switch (P
.getKind()) {
313 case PathDiagnosticPiece::ControlFlow
:
314 ReportControlFlow(o
, cast
<PathDiagnosticControlFlowPiece
>(P
), FM
, SM
,
317 case PathDiagnosticPiece::Event
:
318 ReportEvent(o
, cast
<PathDiagnosticEventPiece
>(P
), FM
, SM
, LangOpts
,
321 case PathDiagnosticPiece::Macro
:
322 ReportMacro(o
, cast
<PathDiagnosticMacroPiece
>(P
), FM
, SM
, LangOpts
,
328 void PlistDiagnostics::HandlePathDiagnostic(const PathDiagnostic
* D
) {
337 // We need to flatten the locations (convert Stmt* to locations) because
338 // the referenced statements may be freed by the time the diagnostics
340 const_cast<PathDiagnostic
*>(D
)->flattenLocations();
341 BatchedDiags
.push_back(D
);
344 void PlistDiagnostics::FlushDiagnostics(llvm::SmallVectorImpl
<std::string
>
352 // Sort the diagnostics so that they are always emitted in a deterministic
354 if (!BatchedDiags
.empty())
355 std::sort(BatchedDiags
.begin(), BatchedDiags
.end(), CompareDiagnostics());
357 // Build up a set of FIDs that we use by scanning the locations and
358 // ranges of the diagnostics.
360 llvm::SmallVector
<FileID
, 10> Fids
;
361 const SourceManager
* SM
= 0;
363 if (!BatchedDiags
.empty())
364 SM
= &(*BatchedDiags
.begin())->begin()->getLocation().getManager();
366 for (std::vector
<const PathDiagnostic
*>::iterator DI
= BatchedDiags
.begin(),
367 DE
= BatchedDiags
.end(); DI
!= DE
; ++DI
) {
369 const PathDiagnostic
*D
= *DI
;
371 for (PathDiagnostic::const_iterator I
=D
->begin(), E
=D
->end(); I
!=E
; ++I
) {
372 AddFID(FM
, Fids
, SM
, I
->getLocation().asLocation());
374 for (PathDiagnosticPiece::range_iterator RI
=I
->ranges_begin(),
375 RE
=I
->ranges_end(); RI
!=RE
; ++RI
) {
376 AddFID(FM
, Fids
, SM
, RI
->getBegin());
377 AddFID(FM
, Fids
, SM
, RI
->getEnd());
384 llvm::raw_fd_ostream
o(OutputFile
.c_str(), ErrMsg
);
385 if (!ErrMsg
.empty()) {
386 llvm::errs() << "warning: could not creat file: " << OutputFile
<< '\n';
390 // Write the plist header.
391 o
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
392 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
393 "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
394 "<plist version=\"1.0\">\n";
396 // Write the root object: a <dict> containing...
397 // - "files", an <array> mapping from FIDs to file names
398 // - "diagnostics", an <array> containing the path diagnostics
400 " <key>files</key>\n"
403 for (llvm::SmallVectorImpl
<FileID
>::iterator I
=Fids
.begin(), E
=Fids
.end();
406 EmitString(o
, SM
->getFileEntryForID(*I
)->getName()) << '\n';
410 " <key>diagnostics</key>\n"
413 for (std::vector
<const PathDiagnostic
*>::iterator DI
=BatchedDiags
.begin(),
414 DE
= BatchedDiags
.end(); DI
!=DE
; ++DI
) {
417 " <key>path</key>\n";
419 const PathDiagnostic
*D
= *DI
;
420 // Create an owning smart pointer for 'D' just so that we auto-free it
421 // when we exit this method.
422 llvm::OwningPtr
<PathDiagnostic
> OwnedD(const_cast<PathDiagnostic
*>(D
));
426 for (PathDiagnostic::const_iterator I
=D
->begin(), E
=D
->end(); I
!= E
; ++I
)
427 ReportDiag(o
, *I
, FM
, *SM
, LangOpts
);
431 // Output the bug type and bug category.
432 o
<< " <key>description</key>";
433 EmitString(o
, D
->getDescription()) << '\n';
434 o
<< " <key>category</key>";
435 EmitString(o
, D
->getCategory()) << '\n';
436 o
<< " <key>type</key>";
437 EmitString(o
, D
->getBugType()) << '\n';
439 // Output the location of the bug.
440 o
<< " <key>location</key>\n";
441 EmitLocation(o
, *SM
, LangOpts
, D
->getLocation(), FM
, 2);
443 // Output the diagnostic to the sub-diagnostic client, if any.
445 SubPD
->HandlePathDiagnostic(OwnedD
.take());
446 llvm::SmallVector
<std::string
, 1> SubFilesMade
;
447 SubPD
->FlushDiagnostics(SubFilesMade
);
449 if (!SubFilesMade
.empty()) {
450 o
<< " <key>" << SubPD
->getName() << "_files</key>\n";
452 for (size_t i
= 0, n
= SubFilesMade
.size(); i
< n
; ++i
)
453 o
<< " <string>" << SubFilesMade
[i
] << "</string>\n";
458 // Close up the entry.
465 o
<< "</dict>\n</plist>";
468 FilesMade
->push_back(OutputFile
);
470 BatchedDiags
.clear();