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/Basic/FileManager.h" 15#include "clang/Basic/PlistSupport.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/IssueHash.h" 21#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" 22#include "llvm/ADT/DenseMap.h" 23#include "llvm/ADT/SmallVector.h" 24#include "llvm/Support/Casting.h" 25using namespace clang; 26using namespace ento; 27using namespace markup; 28 29namespace { 30 class PlistDiagnostics : public PathDiagnosticConsumer { 31 const std::string OutputFile; 32 const LangOptions &LangOpts; 33 const bool SupportsCrossFileDiagnostics; 34 public: 35 PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, 36 const std::string& prefix, 37 const LangOptions &LangOpts, 38 bool supportsMultipleFiles); 39 40 ~PlistDiagnostics() override {} 41 42 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 43 FilesMade *filesMade) override; 44 45 StringRef getName() const override { 46 return "PlistDiagnostics"; 47 } 48 49 PathGenerationScheme getGenerationScheme() const override { 50 return Extensive; 51 } 52 bool supportsLogicalOpControlFlow() const override { return true; } 53 bool supportsCrossFileDiagnostics() const override { 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 ReportControlFlow(raw_ostream &o, 84 const PathDiagnosticControlFlowPiece& P, 85 const FIDMap& FM, 86 const SourceManager &SM, 87 const LangOptions &LangOpts, 88 unsigned indent) { 89 90 Indent(o, indent) << "<dict>\n"; 91 ++indent; 92 93 Indent(o, indent) << "<key>kind</key><string>control</string>\n"; 94 95 // Emit edges. 96 Indent(o, indent) << "<key>edges</key>\n"; 97 ++indent; 98 Indent(o, indent) << "<array>\n"; 99 ++indent; 100 for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); 101 I!=E; ++I) { 102 Indent(o, indent) << "<dict>\n"; 103 ++indent; 104 105 // Make the ranges of the start and end point self-consistent with adjacent edges 106 // by forcing to use only the beginning of the range. This simplifies the layout 107 // logic for clients. 108 Indent(o, indent) << "<key>start</key>\n"; 109 SourceRange StartEdge( 110 SM.getExpansionLoc(I->getStart().asRange().getBegin())); 111 EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM, 112 indent + 1); 113 114 Indent(o, indent) << "<key>end</key>\n"; 115 SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin())); 116 EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM, 117 indent + 1); 118 119 --indent; 120 Indent(o, indent) << "</dict>\n"; 121 } 122 --indent; 123 Indent(o, indent) << "</array>\n"; 124 --indent; 125 126 // Output any helper text. 127 const auto &s = P.getString(); 128 if (!s.empty()) { 129 Indent(o, indent) << "<key>alternate</key>"; 130 EmitString(o, s) << '\n'; 131 } 132 133 --indent; 134 Indent(o, indent) << "</dict>\n"; 135} 136 137static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, 138 const FIDMap& FM, 139 const SourceManager &SM, 140 const LangOptions &LangOpts, 141 unsigned indent, 142 unsigned depth, 143 bool isKeyEvent = false) { 144 145 Indent(o, indent) << "<dict>\n"; 146 ++indent; 147 148 Indent(o, indent) << "<key>kind</key><string>event</string>\n"; 149 150 if (isKeyEvent) { 151 Indent(o, indent) << "<key>key_event</key><true/>\n"; 152 } 153 154 // Output the location. 155 FullSourceLoc L = P.getLocation().asLocation(); 156 157 Indent(o, indent) << "<key>location</key>\n"; 158 EmitLocation(o, SM, L, FM, indent); 159 160 // Output the ranges (if any). 161 ArrayRef<SourceRange> Ranges = P.getRanges(); 162 163 if (!Ranges.empty()) { 164 Indent(o, indent) << "<key>ranges</key>\n"; 165 Indent(o, indent) << "<array>\n"; 166 ++indent; 167 for (auto &R : Ranges) 168 EmitRange(o, SM, 169 Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), 170 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.front()->path.front()->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 o << " <!-- This hash is experimental and going to change! -->\n"; 394 o << " <key>issue_hash_content_of_line_in_context</key>"; 395 PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); 396 FullSourceLoc L(SM->getExpansionLoc(UPDLoc.isValid() 397 ? UPDLoc.asLocation() 398 : D->getLocation().asLocation()), 399 *SM); 400 const Decl *DeclWithIssue = D->getDeclWithIssue(); 401 EmitString(o, GetIssueHash(*SM, L, D->getCheckName(), D->getBugType(), 402 DeclWithIssue, LangOpts)) 403 << '\n'; 404 405 // Output information about the semantic context where 406 // the issue occurred. 407 if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { 408 // FIXME: handle blocks, which have no name. 409 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { 410 StringRef declKind; 411 switch (ND->getKind()) { 412 case Decl::CXXRecord: 413 declKind = "C++ class"; 414 break; 415 case Decl::CXXMethod: 416 declKind = "C++ method"; 417 break; 418 case Decl::ObjCMethod: 419 declKind = "Objective-C method"; 420 break; 421 case Decl::Function: 422 declKind = "function"; 423 break; 424 default: 425 break; 426 } 427 if (!declKind.empty()) { 428 const std::string &declName = ND->getDeclName().getAsString(); 429 o << " <key>issue_context_kind</key>"; 430 EmitString(o, declKind) << '\n'; 431 o << " <key>issue_context</key>"; 432 EmitString(o, declName) << '\n'; 433 } 434 435 // Output the bug hash for issue unique-ing. Currently, it's just an 436 // offset from the beginning of the function. 437 if (const Stmt *Body = DeclWithIssue->getBody()) { 438 439 // If the bug uniqueing location exists, use it for the hash. 440 // For example, this ensures that two leaks reported on the same line 441 // will have different issue_hashes and that the hash will identify 442 // the leak location even after code is added between the allocation 443 // site and the end of scope (leak report location). 444 if (UPDLoc.isValid()) { 445 FullSourceLoc UFunL(SM->getExpansionLoc( 446 D->getUniqueingDecl()->getBody()->getLocStart()), *SM); 447 o << " <key>issue_hash_function_offset</key><string>" 448 << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() 449 << "</string>\n"; 450 451 // Otherwise, use the location on which the bug is reported. 452 } else { 453 FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM); 454 o << " <key>issue_hash_function_offset</key><string>" 455 << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() 456 << "</string>\n"; 457 } 458 459 } 460 } 461 } 462 463 // Output the location of the bug. 464 o << " <key>location</key>\n"; 465 EmitLocation(o, *SM, D->getLocation().asLocation(), FM, 2); 466 467 // Output the diagnostic to the sub-diagnostic client, if any. 468 if (!filesMade->empty()) { 469 StringRef lastName; 470 PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D); 471 if (files) { 472 for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), 473 CE = files->end(); CI != CE; ++CI) { 474 StringRef newName = CI->first; 475 if (newName != lastName) { 476 if (!lastName.empty()) { 477 o << " </array>\n"; 478 } 479 lastName = newName; 480 o << " <key>" << lastName << "_files</key>\n"; 481 o << " <array>\n"; 482 } 483 o << " <string>" << CI->second << "</string>\n"; 484 } 485 o << " </array>\n"; 486 } 487 } 488 489 // Close up the entry. 490 o << " </dict>\n"; 491 } 492 493 o << " </array>\n"; 494 495 // Finish. 496 o << "</dict>\n</plist>"; 497} 498