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