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