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