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