PlistDiagnostics.cpp revision 288943
1227972Stheraven//===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===// 2227972Stheraven// 3227972Stheraven// The LLVM Compiler Infrastructure 4227972Stheraven// 5227972Stheraven// This file is distributed under the University of Illinois Open Source 6227972Stheraven// License. See LICENSE.TXT for details. 7227972Stheraven// 8227972Stheraven//===----------------------------------------------------------------------===// 9227972Stheraven// 10227972Stheraven// This file defines the PlistDiagnostics object. 11227972Stheraven// 12227972Stheraven//===----------------------------------------------------------------------===// 13227972Stheraven 14227972Stheraven#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" 15227972Stheraven#include "clang/Basic/FileManager.h" 16227972Stheraven#include "clang/Basic/PlistSupport.h" 17227972Stheraven#include "clang/Basic/SourceManager.h" 18227972Stheraven#include "clang/Basic/Version.h" 19227972Stheraven#include "clang/Lex/Preprocessor.h" 20227972Stheraven#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" 21227972Stheraven#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" 22227972Stheraven#include "llvm/ADT/DenseMap.h" 23227972Stheraven#include "llvm/ADT/SmallVector.h" 24227972Stheraven#include "llvm/Support/Casting.h" 25227972Stheravenusing namespace clang; 26227972Stheravenusing namespace ento; 27227972Stheravenusing namespace markup; 28227972Stheraven 29227972Stheravennamespace { 30227972Stheraven class PlistDiagnostics : public PathDiagnosticConsumer { 31227972Stheraven const std::string OutputFile; 32227972Stheraven const LangOptions &LangOpts; 33227972Stheraven const bool SupportsCrossFileDiagnostics; 34227972Stheraven public: 35227972Stheraven PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, 36227972Stheraven const std::string& prefix, 37227972Stheraven const LangOptions &LangOpts, 38227972Stheraven bool supportsMultipleFiles); 39227972Stheraven 40227972Stheraven ~PlistDiagnostics() override {} 41227972Stheraven 42227972Stheraven void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 43227972Stheraven FilesMade *filesMade) override; 44227972Stheraven 45227972Stheraven StringRef getName() const override { 46227972Stheraven return "PlistDiagnostics"; 47227972Stheraven } 48227972Stheraven 49227972Stheraven PathGenerationScheme getGenerationScheme() const override { 50227972Stheraven return Extensive; 51227972Stheraven } 52227972Stheraven bool supportsLogicalOpControlFlow() const override { return true; } 53227972Stheraven bool supportsCrossFileDiagnostics() const override { 54227972Stheraven return SupportsCrossFileDiagnostics; 55227972Stheraven } 56227972Stheraven }; 57227972Stheraven} // end anonymous namespace 58227972Stheraven 59227972StheravenPlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, 60227972Stheraven const std::string& output, 61227972Stheraven const LangOptions &LO, 62227972Stheraven bool supportsMultipleFiles) 63227972Stheraven : OutputFile(output), 64227972Stheraven LangOpts(LO), 65227972Stheraven SupportsCrossFileDiagnostics(supportsMultipleFiles) {} 66227972Stheraven 67227972Stheravenvoid ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 68227972Stheraven PathDiagnosticConsumers &C, 69227972Stheraven const std::string& s, 70227972Stheraven const Preprocessor &PP) { 71227972Stheraven C.push_back(new PlistDiagnostics(AnalyzerOpts, s, 72227972Stheraven PP.getLangOpts(), false)); 73227972Stheraven} 74227972Stheraven 75227972Stheravenvoid ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 76227972Stheraven PathDiagnosticConsumers &C, 77227972Stheraven const std::string &s, 78227972Stheraven const Preprocessor &PP) { 79227972Stheraven C.push_back(new PlistDiagnostics(AnalyzerOpts, s, 80227972Stheraven PP.getLangOpts(), true)); 81227972Stheraven} 82227972Stheraven 83255815Stheravenstatic void ReportControlFlow(raw_ostream &o, 84227972Stheraven const PathDiagnosticControlFlowPiece& P, 85227972Stheraven const FIDMap& FM, 86227972Stheraven const SourceManager &SM, 87227972Stheraven const LangOptions &LangOpts, 88227972Stheraven unsigned indent) { 89227972Stheraven 90227972Stheraven Indent(o, indent) << "<dict>\n"; 91227972Stheraven ++indent; 92227972Stheraven 93227972Stheraven Indent(o, indent) << "<key>kind</key><string>control</string>\n"; 94227972Stheraven 95227972Stheraven // Emit edges. 96227972Stheraven Indent(o, indent) << "<key>edges</key>\n"; 97227972Stheraven ++indent; 98227972Stheraven Indent(o, indent) << "<array>\n"; 99227972Stheraven ++indent; 100227972Stheraven for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); 101227972Stheraven I!=E; ++I) { 102227972Stheraven Indent(o, indent) << "<dict>\n"; 103227972Stheraven ++indent; 104227972Stheraven 105227972Stheraven // Make the ranges of the start and end point self-consistent with adjacent edges 106227972Stheraven // by forcing to use only the beginning of the range. This simplifies the layout 107227972Stheraven // logic for clients. 108227972Stheraven Indent(o, indent) << "<key>start</key>\n"; 109227972Stheraven SourceRange StartEdge( 110227972Stheraven SM.getExpansionLoc(I->getStart().asRange().getBegin())); 111227972Stheraven EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM, 112227972Stheraven indent + 1); 113227972Stheraven 114227972Stheraven Indent(o, indent) << "<key>end</key>\n"; 115227972Stheraven SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin())); 116227972Stheraven EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM, 117227972Stheraven indent + 1); 118227972Stheraven 119227972Stheraven --indent; 120227972Stheraven Indent(o, indent) << "</dict>\n"; 121227972Stheraven } 122227972Stheraven --indent; 123227972Stheraven Indent(o, indent) << "</array>\n"; 124227972Stheraven --indent; 125227972Stheraven 126227972Stheraven // Output any helper text. 127227972Stheraven const std::string& s = P.getString(); 128227972Stheraven if (!s.empty()) { 129227972Stheraven Indent(o, indent) << "<key>alternate</key>"; 130227972Stheraven EmitString(o, s) << '\n'; 131227972Stheraven } 132227972Stheraven 133227972Stheraven --indent; 134227972Stheraven Indent(o, indent) << "</dict>\n"; 135227972Stheraven} 136227972Stheraven 137227972Stheravenstatic void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, 138227972Stheraven const FIDMap& FM, 139227972Stheraven const SourceManager &SM, 140227972Stheraven const LangOptions &LangOpts, 141227972Stheraven unsigned indent, 142227972Stheraven unsigned depth, 143227972Stheraven bool isKeyEvent = false) { 144227972Stheraven 145227972Stheraven Indent(o, indent) << "<dict>\n"; 146227972Stheraven ++indent; 147227972Stheraven 148227972Stheraven Indent(o, indent) << "<key>kind</key><string>event</string>\n"; 149227972Stheraven 150227972Stheraven if (isKeyEvent) { 151227972Stheraven Indent(o, indent) << "<key>key_event</key><true/>\n"; 152227972Stheraven } 153227972Stheraven 154227972Stheraven // Output the location. 155227972Stheraven FullSourceLoc L = P.getLocation().asLocation(); 156227972Stheraven 157227972Stheraven Indent(o, indent) << "<key>location</key>\n"; 158227972Stheraven EmitLocation(o, SM, L, FM, indent); 159227972Stheraven 160227972Stheraven // Output the ranges (if any). 161227972Stheraven ArrayRef<SourceRange> Ranges = P.getRanges(); 162227972Stheraven 163227972Stheraven if (!Ranges.empty()) { 164227972Stheraven Indent(o, indent) << "<key>ranges</key>\n"; 165227972Stheraven Indent(o, indent) << "<array>\n"; 166227972Stheraven ++indent; 167227972Stheraven for (auto &R : Ranges) 168227972Stheraven EmitRange(o, SM, 169227972Stheraven Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), 170227972Stheraven FM, indent + 1); 171 --indent; 172 Indent(o, indent) << "</array>\n"; 173 } 174 175 // Output the call depth. 176 Indent(o, indent) << "<key>depth</key>"; 177 EmitInteger(o, depth) << '\n'; 178 179 // Output the text. 180 assert(!P.getString().empty()); 181 Indent(o, indent) << "<key>extended_message</key>\n"; 182 Indent(o, indent); 183 EmitString(o, P.getString()) << '\n'; 184 185 // Output the short text. 186 // FIXME: Really use a short string. 187 Indent(o, indent) << "<key>message</key>\n"; 188 Indent(o, indent); 189 EmitString(o, P.getString()) << '\n'; 190 191 // Finish up. 192 --indent; 193 Indent(o, indent); o << "</dict>\n"; 194} 195 196static void ReportPiece(raw_ostream &o, 197 const PathDiagnosticPiece &P, 198 const FIDMap& FM, const SourceManager &SM, 199 const LangOptions &LangOpts, 200 unsigned indent, 201 unsigned depth, 202 bool includeControlFlow, 203 bool isKeyEvent = false); 204 205static void ReportCall(raw_ostream &o, 206 const PathDiagnosticCallPiece &P, 207 const FIDMap& FM, const SourceManager &SM, 208 const LangOptions &LangOpts, 209 unsigned indent, 210 unsigned depth) { 211 212 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnter = 213 P.getCallEnterEvent(); 214 215 if (callEnter) 216 ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true, 217 P.isLastInMainSourceFile()); 218 219 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnterWithinCaller = 220 P.getCallEnterWithinCallerEvent(); 221 222 ++depth; 223 224 if (callEnterWithinCaller) 225 ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts, 226 indent, depth, true); 227 228 for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) 229 ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true); 230 231 --depth; 232 233 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callExit = 234 P.getCallExitEvent(); 235 236 if (callExit) 237 ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true); 238} 239 240static void ReportMacro(raw_ostream &o, 241 const PathDiagnosticMacroPiece& P, 242 const FIDMap& FM, const SourceManager &SM, 243 const LangOptions &LangOpts, 244 unsigned indent, 245 unsigned depth) { 246 247 for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); 248 I!=E; ++I) { 249 ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false); 250 } 251} 252 253static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P, 254 const FIDMap& FM, const SourceManager &SM, 255 const LangOptions &LangOpts) { 256 ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true); 257} 258 259static void ReportPiece(raw_ostream &o, 260 const PathDiagnosticPiece &P, 261 const FIDMap& FM, const SourceManager &SM, 262 const LangOptions &LangOpts, 263 unsigned indent, 264 unsigned depth, 265 bool includeControlFlow, 266 bool isKeyEvent) { 267 switch (P.getKind()) { 268 case PathDiagnosticPiece::ControlFlow: 269 if (includeControlFlow) 270 ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM, 271 LangOpts, indent); 272 break; 273 case PathDiagnosticPiece::Call: 274 ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts, 275 indent, depth); 276 break; 277 case PathDiagnosticPiece::Event: 278 ReportEvent(o, cast<PathDiagnosticSpotPiece>(P), FM, SM, LangOpts, 279 indent, depth, isKeyEvent); 280 break; 281 case PathDiagnosticPiece::Macro: 282 ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts, 283 indent, depth); 284 break; 285 } 286} 287 288void PlistDiagnostics::FlushDiagnosticsImpl( 289 std::vector<const PathDiagnostic *> &Diags, 290 FilesMade *filesMade) { 291 // Build up a set of FIDs that we use by scanning the locations and 292 // ranges of the diagnostics. 293 FIDMap FM; 294 SmallVector<FileID, 10> Fids; 295 const SourceManager* SM = nullptr; 296 297 if (!Diags.empty()) 298 SM = &(*(*Diags.begin())->path.begin())->getLocation().getManager(); 299 300 301 for (std::vector<const PathDiagnostic*>::iterator DI = Diags.begin(), 302 DE = Diags.end(); DI != DE; ++DI) { 303 304 const PathDiagnostic *D = *DI; 305 306 SmallVector<const PathPieces *, 5> WorkList; 307 WorkList.push_back(&D->path); 308 309 while (!WorkList.empty()) { 310 const PathPieces &path = *WorkList.pop_back_val(); 311 312 for (PathPieces::const_iterator I = path.begin(), E = path.end(); I != E; 313 ++I) { 314 const PathDiagnosticPiece *piece = I->get(); 315 AddFID(FM, Fids, *SM, piece->getLocation().asLocation()); 316 ArrayRef<SourceRange> Ranges = piece->getRanges(); 317 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), 318 E = Ranges.end(); I != E; ++I) { 319 AddFID(FM, Fids, *SM, I->getBegin()); 320 AddFID(FM, Fids, *SM, I->getEnd()); 321 } 322 323 if (const PathDiagnosticCallPiece *call = 324 dyn_cast<PathDiagnosticCallPiece>(piece)) { 325 IntrusiveRefCntPtr<PathDiagnosticEventPiece> 326 callEnterWithin = call->getCallEnterWithinCallerEvent(); 327 if (callEnterWithin) 328 AddFID(FM, Fids, *SM, callEnterWithin->getLocation().asLocation()); 329 330 WorkList.push_back(&call->path); 331 } 332 else if (const PathDiagnosticMacroPiece *macro = 333 dyn_cast<PathDiagnosticMacroPiece>(piece)) { 334 WorkList.push_back(¯o->subPieces); 335 } 336 } 337 } 338 } 339 340 // Open the file. 341 std::error_code EC; 342 llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::F_Text); 343 if (EC) { 344 llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; 345 return; 346 } 347 348 EmitPlistHeader(o); 349 350 // Write the root object: a <dict> containing... 351 // - "clang_version", the string representation of clang version 352 // - "files", an <array> mapping from FIDs to file names 353 // - "diagnostics", an <array> containing the path diagnostics 354 o << "<dict>\n" << 355 " <key>clang_version</key>\n"; 356 EmitString(o, getClangFullVersion()) << '\n'; 357 o << " <key>files</key>\n" 358 " <array>\n"; 359 360 for (FileID FID : Fids) 361 EmitString(o << " ", SM->getFileEntryForID(FID)->getName()) << '\n'; 362 363 o << " </array>\n" 364 " <key>diagnostics</key>\n" 365 " <array>\n"; 366 367 for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), 368 DE = Diags.end(); DI!=DE; ++DI) { 369 370 o << " <dict>\n" 371 " <key>path</key>\n"; 372 373 const PathDiagnostic *D = *DI; 374 375 o << " <array>\n"; 376 377 for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); 378 I != E; ++I) 379 ReportDiag(o, **I, FM, *SM, LangOpts); 380 381 o << " </array>\n"; 382 383 // Output the bug type and bug category. 384 o << " <key>description</key>"; 385 EmitString(o, D->getShortDescription()) << '\n'; 386 o << " <key>category</key>"; 387 EmitString(o, D->getCategory()) << '\n'; 388 o << " <key>type</key>"; 389 EmitString(o, D->getBugType()) << '\n'; 390 o << " <key>check_name</key>"; 391 EmitString(o, D->getCheckName()) << '\n'; 392 393 // Output information about the semantic context where 394 // the issue occurred. 395 if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { 396 // FIXME: handle blocks, which have no name. 397 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { 398 StringRef declKind; 399 switch (ND->getKind()) { 400 case Decl::CXXRecord: 401 declKind = "C++ class"; 402 break; 403 case Decl::CXXMethod: 404 declKind = "C++ method"; 405 break; 406 case Decl::ObjCMethod: 407 declKind = "Objective-C method"; 408 break; 409 case Decl::Function: 410 declKind = "function"; 411 break; 412 default: 413 break; 414 } 415 if (!declKind.empty()) { 416 const std::string &declName = ND->getDeclName().getAsString(); 417 o << " <key>issue_context_kind</key>"; 418 EmitString(o, declKind) << '\n'; 419 o << " <key>issue_context</key>"; 420 EmitString(o, declName) << '\n'; 421 } 422 423 // Output the bug hash for issue unique-ing. Currently, it's just an 424 // offset from the beginning of the function. 425 if (const Stmt *Body = DeclWithIssue->getBody()) { 426 427 // If the bug uniqueing location exists, use it for the hash. 428 // For example, this ensures that two leaks reported on the same line 429 // will have different issue_hashes and that the hash will identify 430 // the leak location even after code is added between the allocation 431 // site and the end of scope (leak report location). 432 PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); 433 if (UPDLoc.isValid()) { 434 FullSourceLoc UL(SM->getExpansionLoc(UPDLoc.asLocation()), 435 *SM); 436 FullSourceLoc UFunL(SM->getExpansionLoc( 437 D->getUniqueingDecl()->getBody()->getLocStart()), *SM); 438 o << " <key>issue_hash</key><string>" 439 << UL.getExpansionLineNumber() - UFunL.getExpansionLineNumber() 440 << "</string>\n"; 441 442 // Otherwise, use the location on which the bug is reported. 443 } else { 444 FullSourceLoc L(SM->getExpansionLoc(D->getLocation().asLocation()), 445 *SM); 446 FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM); 447 o << " <key>issue_hash</key><string>" 448 << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() 449 << "</string>\n"; 450 } 451 452 } 453 } 454 } 455 456 // Output the location of the bug. 457 o << " <key>location</key>\n"; 458 EmitLocation(o, *SM, D->getLocation().asLocation(), FM, 2); 459 460 // Output the diagnostic to the sub-diagnostic client, if any. 461 if (!filesMade->empty()) { 462 StringRef lastName; 463 PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D); 464 if (files) { 465 for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), 466 CE = files->end(); CI != CE; ++CI) { 467 StringRef newName = CI->first; 468 if (newName != lastName) { 469 if (!lastName.empty()) { 470 o << " </array>\n"; 471 } 472 lastName = newName; 473 o << " <key>" << lastName << "_files</key>\n"; 474 o << " <array>\n"; 475 } 476 o << " <string>" << CI->second << "</string>\n"; 477 } 478 o << " </array>\n"; 479 } 480 } 481 482 // Close up the entry. 483 o << " </dict>\n"; 484 } 485 486 o << " </array>\n"; 487 488 // Finish. 489 o << "</dict>\n</plist>"; 490} 491