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