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