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