PlistDiagnostics.cpp revision 9b663716449b618ba0390b1dbebc54fa8e971124
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/StaticAnalyzer/Core/PathDiagnosticClients.h" 15#include "clang/StaticAnalyzer/Core/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" 23using namespace clang; 24using namespace ento; 25using llvm::cast; 26 27typedef llvm::DenseMap<FileID, unsigned> FIDMap; 28 29namespace clang { 30 class Preprocessor; 31} 32 33namespace { 34struct CompareDiagnostics { 35 // Compare if 'X' is "<" than 'Y'. 36 bool operator()(const PathDiagnostic *X, const PathDiagnostic *Y) const { 37 // First compare by location 38 const FullSourceLoc &XLoc = X->getLocation().asLocation(); 39 const FullSourceLoc &YLoc = Y->getLocation().asLocation(); 40 if (XLoc < YLoc) 41 return true; 42 if (XLoc != YLoc) 43 return false; 44 45 // Next, compare by bug type. 46 llvm::StringRef XBugType = X->getBugType(); 47 llvm::StringRef YBugType = Y->getBugType(); 48 if (XBugType < YBugType) 49 return true; 50 if (XBugType != YBugType) 51 return false; 52 53 // Next, compare by bug description. 54 llvm::StringRef XDesc = X->getDescription(); 55 llvm::StringRef YDesc = Y->getDescription(); 56 if (XDesc < YDesc) 57 return true; 58 if (XDesc != YDesc) 59 return false; 60 61 // FIXME: Further refine by comparing PathDiagnosticPieces? 62 return false; 63 } 64}; 65} 66 67namespace { 68 class PlistDiagnostics : public PathDiagnosticClient { 69 std::vector<const PathDiagnostic*> BatchedDiags; 70 const std::string OutputFile; 71 const LangOptions &LangOpts; 72 llvm::OwningPtr<PathDiagnosticClient> SubPD; 73 bool flushed; 74 public: 75 PlistDiagnostics(const std::string& prefix, const LangOptions &LangOpts, 76 PathDiagnosticClient *subPD); 77 78 ~PlistDiagnostics() { FlushDiagnostics(NULL); } 79 80 void FlushDiagnostics(llvm::SmallVectorImpl<std::string> *FilesMade); 81 82 void HandlePathDiagnostic(const PathDiagnostic* D); 83 84 virtual llvm::StringRef getName() const { 85 return "PlistDiagnostics"; 86 } 87 88 PathGenerationScheme getGenerationScheme() const; 89 bool supportsLogicalOpControlFlow() const { return true; } 90 bool supportsAllBlockEdges() const { return true; } 91 virtual bool useVerboseDescription() const { return false; } 92 }; 93} // end anonymous namespace 94 95PlistDiagnostics::PlistDiagnostics(const std::string& output, 96 const LangOptions &LO, 97 PathDiagnosticClient *subPD) 98 : OutputFile(output), LangOpts(LO), SubPD(subPD), flushed(false) {} 99 100PathDiagnosticClient* 101ento::createPlistDiagnosticClient(const std::string& s, const Preprocessor &PP, 102 PathDiagnosticClient *subPD) { 103 return new PlistDiagnostics(s, PP.getLangOptions(), subPD); 104} 105 106PathDiagnosticClient::PathGenerationScheme 107PlistDiagnostics::getGenerationScheme() const { 108 if (const PathDiagnosticClient *PD = SubPD.get()) 109 return PD->getGenerationScheme(); 110 111 return Extensive; 112} 113 114static void AddFID(FIDMap &FIDs, llvm::SmallVectorImpl<FileID> &V, 115 const SourceManager* SM, SourceLocation L) { 116 117 FileID FID = SM->getFileID(SM->getInstantiationLoc(L)); 118 FIDMap::iterator I = FIDs.find(FID); 119 if (I != FIDs.end()) return; 120 FIDs[FID] = V.size(); 121 V.push_back(FID); 122} 123 124static unsigned GetFID(const FIDMap& FIDs, const SourceManager &SM, 125 SourceLocation L) { 126 FileID FID = SM.getFileID(SM.getInstantiationLoc(L)); 127 FIDMap::const_iterator I = FIDs.find(FID); 128 assert(I != FIDs.end()); 129 return I->second; 130} 131 132static llvm::raw_ostream& Indent(llvm::raw_ostream& o, const unsigned indent) { 133 for (unsigned i = 0; i < indent; ++i) o << ' '; 134 return o; 135} 136 137static void EmitLocation(llvm::raw_ostream& o, const SourceManager &SM, 138 const LangOptions &LangOpts, 139 SourceLocation L, const FIDMap &FM, 140 unsigned indent, bool extend = false) { 141 142 FullSourceLoc Loc(SM.getInstantiationLoc(L), const_cast<SourceManager&>(SM)); 143 144 // Add in the length of the token, so that we cover multi-char tokens. 145 unsigned offset = 146 extend ? Lexer::MeasureTokenLength(Loc, SM, LangOpts) - 1 : 0; 147 148 Indent(o, indent) << "<dict>\n"; 149 Indent(o, indent) << " <key>line</key><integer>" 150 << Loc.getInstantiationLineNumber() << "</integer>\n"; 151 Indent(o, indent) << " <key>col</key><integer>" 152 << Loc.getInstantiationColumnNumber() + offset << "</integer>\n"; 153 Indent(o, indent) << " <key>file</key><integer>" 154 << GetFID(FM, SM, Loc) << "</integer>\n"; 155 Indent(o, indent) << "</dict>\n"; 156} 157 158static void EmitLocation(llvm::raw_ostream& o, const SourceManager &SM, 159 const LangOptions &LangOpts, 160 const PathDiagnosticLocation &L, const FIDMap& FM, 161 unsigned indent, bool extend = false) { 162 EmitLocation(o, SM, LangOpts, L.asLocation(), FM, indent, extend); 163} 164 165static void EmitRange(llvm::raw_ostream& o, const SourceManager &SM, 166 const LangOptions &LangOpts, 167 PathDiagnosticRange R, const FIDMap &FM, 168 unsigned indent) { 169 Indent(o, indent) << "<array>\n"; 170 EmitLocation(o, SM, LangOpts, R.getBegin(), FM, indent+1); 171 EmitLocation(o, SM, LangOpts, R.getEnd(), FM, indent+1, !R.isPoint); 172 Indent(o, indent) << "</array>\n"; 173} 174 175static llvm::raw_ostream& EmitString(llvm::raw_ostream& o, 176 const std::string& s) { 177 o << "<string>"; 178 for (std::string::const_iterator I=s.begin(), E=s.end(); I!=E; ++I) { 179 char c = *I; 180 switch (c) { 181 default: o << c; break; 182 case '&': o << "&"; break; 183 case '<': o << "<"; break; 184 case '>': o << ">"; break; 185 case '\'': o << "'"; break; 186 case '\"': o << """; break; 187 } 188 } 189 o << "</string>"; 190 return o; 191} 192 193static void ReportControlFlow(llvm::raw_ostream& o, 194 const PathDiagnosticControlFlowPiece& P, 195 const FIDMap& FM, 196 const SourceManager &SM, 197 const LangOptions &LangOpts, 198 unsigned indent) { 199 200 Indent(o, indent) << "<dict>\n"; 201 ++indent; 202 203 Indent(o, indent) << "<key>kind</key><string>control</string>\n"; 204 205 // Emit edges. 206 Indent(o, indent) << "<key>edges</key>\n"; 207 ++indent; 208 Indent(o, indent) << "<array>\n"; 209 ++indent; 210 for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); 211 I!=E; ++I) { 212 Indent(o, indent) << "<dict>\n"; 213 ++indent; 214 Indent(o, indent) << "<key>start</key>\n"; 215 EmitRange(o, SM, LangOpts, I->getStart().asRange(), FM, indent+1); 216 Indent(o, indent) << "<key>end</key>\n"; 217 EmitRange(o, SM, LangOpts, I->getEnd().asRange(), FM, indent+1); 218 --indent; 219 Indent(o, indent) << "</dict>\n"; 220 } 221 --indent; 222 Indent(o, indent) << "</array>\n"; 223 --indent; 224 225 // Output any helper text. 226 const std::string& s = P.getString(); 227 if (!s.empty()) { 228 Indent(o, indent) << "<key>alternate</key>"; 229 EmitString(o, s) << '\n'; 230 } 231 232 --indent; 233 Indent(o, indent) << "</dict>\n"; 234} 235 236static void ReportEvent(llvm::raw_ostream& o, const PathDiagnosticPiece& P, 237 const FIDMap& FM, 238 const SourceManager &SM, 239 const LangOptions &LangOpts, 240 unsigned indent) { 241 242 Indent(o, indent) << "<dict>\n"; 243 ++indent; 244 245 Indent(o, indent) << "<key>kind</key><string>event</string>\n"; 246 247 // Output the location. 248 FullSourceLoc L = P.getLocation().asLocation(); 249 250 Indent(o, indent) << "<key>location</key>\n"; 251 EmitLocation(o, SM, LangOpts, L, FM, indent); 252 253 // Output the ranges (if any). 254 PathDiagnosticPiece::range_iterator RI = P.ranges_begin(), 255 RE = P.ranges_end(); 256 257 if (RI != RE) { 258 Indent(o, indent) << "<key>ranges</key>\n"; 259 Indent(o, indent) << "<array>\n"; 260 ++indent; 261 for (; RI != RE; ++RI) 262 EmitRange(o, SM, LangOpts, *RI, FM, indent+1); 263 --indent; 264 Indent(o, indent) << "</array>\n"; 265 } 266 267 // Output the text. 268 assert(!P.getString().empty()); 269 Indent(o, indent) << "<key>extended_message</key>\n"; 270 Indent(o, indent); 271 EmitString(o, P.getString()) << '\n'; 272 273 // Output the short text. 274 // FIXME: Really use a short string. 275 Indent(o, indent) << "<key>message</key>\n"; 276 EmitString(o, P.getString()) << '\n'; 277 278 // Finish up. 279 --indent; 280 Indent(o, indent); o << "</dict>\n"; 281} 282 283static void ReportMacro(llvm::raw_ostream& o, 284 const PathDiagnosticMacroPiece& P, 285 const FIDMap& FM, const SourceManager &SM, 286 const LangOptions &LangOpts, 287 unsigned indent) { 288 289 for (PathDiagnosticMacroPiece::const_iterator I=P.begin(), E=P.end(); 290 I!=E; ++I) { 291 292 switch ((*I)->getKind()) { 293 default: 294 break; 295 case PathDiagnosticPiece::Event: 296 ReportEvent(o, cast<PathDiagnosticEventPiece>(**I), FM, SM, LangOpts, 297 indent); 298 break; 299 case PathDiagnosticPiece::Macro: 300 ReportMacro(o, cast<PathDiagnosticMacroPiece>(**I), FM, SM, LangOpts, 301 indent); 302 break; 303 } 304 } 305} 306 307static void ReportDiag(llvm::raw_ostream& o, const PathDiagnosticPiece& P, 308 const FIDMap& FM, const SourceManager &SM, 309 const LangOptions &LangOpts) { 310 311 unsigned indent = 4; 312 313 switch (P.getKind()) { 314 case PathDiagnosticPiece::ControlFlow: 315 ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM, 316 LangOpts, indent); 317 break; 318 case PathDiagnosticPiece::Event: 319 ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, SM, LangOpts, 320 indent); 321 break; 322 case PathDiagnosticPiece::Macro: 323 ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts, 324 indent); 325 break; 326 } 327} 328 329void PlistDiagnostics::HandlePathDiagnostic(const PathDiagnostic* D) { 330 if (!D) 331 return; 332 333 if (D->empty()) { 334 delete D; 335 return; 336 } 337 338 // We need to flatten the locations (convert Stmt* to locations) because 339 // the referenced statements may be freed by the time the diagnostics 340 // are emitted. 341 const_cast<PathDiagnostic*>(D)->flattenLocations(); 342 BatchedDiags.push_back(D); 343} 344 345void PlistDiagnostics::FlushDiagnostics(llvm::SmallVectorImpl<std::string> 346 *FilesMade) { 347 348 if (flushed) 349 return; 350 351 flushed = true; 352 353 // Sort the diagnostics so that they are always emitted in a deterministic 354 // order. 355 if (!BatchedDiags.empty()) 356 std::sort(BatchedDiags.begin(), BatchedDiags.end(), CompareDiagnostics()); 357 358 // Build up a set of FIDs that we use by scanning the locations and 359 // ranges of the diagnostics. 360 FIDMap FM; 361 llvm::SmallVector<FileID, 10> Fids; 362 const SourceManager* SM = 0; 363 364 if (!BatchedDiags.empty()) 365 SM = &(*BatchedDiags.begin())->begin()->getLocation().getManager(); 366 367 for (std::vector<const PathDiagnostic*>::iterator DI = BatchedDiags.begin(), 368 DE = BatchedDiags.end(); DI != DE; ++DI) { 369 370 const PathDiagnostic *D = *DI; 371 372 for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I!=E; ++I) { 373 AddFID(FM, Fids, SM, I->getLocation().asLocation()); 374 375 for (PathDiagnosticPiece::range_iterator RI=I->ranges_begin(), 376 RE=I->ranges_end(); RI!=RE; ++RI) { 377 AddFID(FM, Fids, SM, RI->getBegin()); 378 AddFID(FM, Fids, SM, RI->getEnd()); 379 } 380 } 381 } 382 383 // Open the file. 384 std::string ErrMsg; 385 llvm::raw_fd_ostream o(OutputFile.c_str(), ErrMsg); 386 if (!ErrMsg.empty()) { 387 llvm::errs() << "warning: could not creat file: " << OutputFile << '\n'; 388 return; 389 } 390 391 // Write the plist header. 392 o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 393 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" " 394 "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" 395 "<plist version=\"1.0\">\n"; 396 397 // Write the root object: a <dict> containing... 398 // - "files", an <array> mapping from FIDs to file names 399 // - "diagnostics", an <array> containing the path diagnostics 400 o << "<dict>\n" 401 " <key>files</key>\n" 402 " <array>\n"; 403 404 for (llvm::SmallVectorImpl<FileID>::iterator I=Fids.begin(), E=Fids.end(); 405 I!=E; ++I) { 406 o << " "; 407 EmitString(o, SM->getFileEntryForID(*I)->getName()) << '\n'; 408 } 409 410 o << " </array>\n" 411 " <key>diagnostics</key>\n" 412 " <array>\n"; 413 414 for (std::vector<const PathDiagnostic*>::iterator DI=BatchedDiags.begin(), 415 DE = BatchedDiags.end(); DI!=DE; ++DI) { 416 417 o << " <dict>\n" 418 " <key>path</key>\n"; 419 420 const PathDiagnostic *D = *DI; 421 // Create an owning smart pointer for 'D' just so that we auto-free it 422 // when we exit this method. 423 llvm::OwningPtr<PathDiagnostic> OwnedD(const_cast<PathDiagnostic*>(D)); 424 425 o << " <array>\n"; 426 427 for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I != E; ++I) 428 ReportDiag(o, *I, FM, *SM, LangOpts); 429 430 o << " </array>\n"; 431 432 // Output the bug type and bug category. 433 o << " <key>description</key>"; 434 EmitString(o, D->getDescription()) << '\n'; 435 o << " <key>category</key>"; 436 EmitString(o, D->getCategory()) << '\n'; 437 o << " <key>type</key>"; 438 EmitString(o, D->getBugType()) << '\n'; 439 440 // Output the location of the bug. 441 o << " <key>location</key>\n"; 442 EmitLocation(o, *SM, LangOpts, D->getLocation(), FM, 2); 443 444 // Output the diagnostic to the sub-diagnostic client, if any. 445 if (SubPD) { 446 SubPD->HandlePathDiagnostic(OwnedD.take()); 447 llvm::SmallVector<std::string, 1> SubFilesMade; 448 SubPD->FlushDiagnostics(SubFilesMade); 449 450 if (!SubFilesMade.empty()) { 451 o << " <key>" << SubPD->getName() << "_files</key>\n"; 452 o << " <array>\n"; 453 for (size_t i = 0, n = SubFilesMade.size(); i < n ; ++i) 454 o << " <string>" << SubFilesMade[i] << "</string>\n"; 455 o << " </array>\n"; 456 } 457 } 458 459 // Close up the entry. 460 o << " </dict>\n"; 461 } 462 463 o << " </array>\n"; 464 465 // Finish. 466 o << "</dict>\n</plist>"; 467 468 if (FilesMade) 469 FilesMade->push_back(OutputFile); 470 471 BatchedDiags.clear(); 472} 473