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/PlistSupport.h" 17 #include "clang/Basic/SourceManager.h" 18 #include "clang/Basic/Version.h" 19 #include "clang/Lex/Preprocessor.h" 20 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.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" 25 using namespace clang; 26 using namespace ento; 27 using namespace markup; 28 29 namespace { 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 virtual ~PlistDiagnostics() {} 41 42 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 43 FilesMade *filesMade); 44 45 virtual StringRef getName() const { 46 return "PlistDiagnostics"; 47 } 48 49 PathGenerationScheme getGenerationScheme() const { return Extensive; } 50 bool supportsLogicalOpControlFlow() const { return true; } 51 virtual bool supportsCrossFileDiagnostics() const { 52 return SupportsCrossFileDiagnostics; 53 } 54 }; 55 } // end anonymous namespace 56 57 PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, 58 const std::string& output, 59 const LangOptions &LO, 60 bool supportsMultipleFiles) 61 : OutputFile(output), 62 LangOpts(LO), 63 SupportsCrossFileDiagnostics(supportsMultipleFiles) {} 64 65 void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 66 PathDiagnosticConsumers &C, 67 const std::string& s, 68 const Preprocessor &PP) { 69 C.push_back(new PlistDiagnostics(AnalyzerOpts, s, 70 PP.getLangOpts(), false)); 71 } 72 73 void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 74 PathDiagnosticConsumers &C, 75 const std::string &s, 76 const Preprocessor &PP) { 77 C.push_back(new PlistDiagnostics(AnalyzerOpts, s, 78 PP.getLangOpts(), true)); 79 } 80 81 static void ReportControlFlow(raw_ostream &o, 82 const PathDiagnosticControlFlowPiece& P, 83 const FIDMap& FM, 84 const SourceManager &SM, 85 const LangOptions &LangOpts, 86 unsigned indent) { 87 88 Indent(o, indent) << "<dict>\n"; 89 ++indent; 90 91 Indent(o, indent) << "<key>kind</key><string>control</string>\n"; 92 93 // Emit edges. 94 Indent(o, indent) << "<key>edges</key>\n"; 95 ++indent; 96 Indent(o, indent) << "<array>\n"; 97 ++indent; 98 for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); 99 I!=E; ++I) { 100 Indent(o, indent) << "<dict>\n"; 101 ++indent; 102 103 // Make the ranges of the start and end point self-consistent with adjacent edges 104 // by forcing to use only the beginning of the range. This simplifies the layout 105 // logic for clients. 106 Indent(o, indent) << "<key>start</key>\n"; 107 SourceLocation StartEdge = I->getStart().asRange().getBegin(); 108 EmitRange(o, SM, LangOpts, CharSourceRange::getTokenRange(StartEdge), FM, 109 indent + 1); 110 111 Indent(o, indent) << "<key>end</key>\n"; 112 SourceLocation EndEdge = I->getEnd().asRange().getBegin(); 113 EmitRange(o, SM, LangOpts, CharSourceRange::getTokenRange(EndEdge), FM, 114 indent + 1); 115 116 --indent; 117 Indent(o, indent) << "</dict>\n"; 118 } 119 --indent; 120 Indent(o, indent) << "</array>\n"; 121 --indent; 122 123 // Output any helper text. 124 const std::string& s = P.getString(); 125 if (!s.empty()) { 126 Indent(o, indent) << "<key>alternate</key>"; 127 EmitString(o, s) << '\n'; 128 } 129 130 --indent; 131 Indent(o, indent) << "</dict>\n"; 132 } 133 134 static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, 135 const FIDMap& FM, 136 const SourceManager &SM, 137 const LangOptions &LangOpts, 138 unsigned indent, 139 unsigned depth, 140 bool isKeyEvent = false) { 141 142 Indent(o, indent) << "<dict>\n"; 143 ++indent; 144 145 Indent(o, indent) << "<key>kind</key><string>event</string>\n"; 146 147 if (isKeyEvent) { 148 Indent(o, indent) << "<key>key_event</key><true/>\n"; 149 } 150 151 // Output the location. 152 FullSourceLoc L = P.getLocation().asLocation(); 153 154 Indent(o, indent) << "<key>location</key>\n"; 155 EmitLocation(o, SM, LangOpts, L, FM, indent); 156 157 // Output the ranges (if any). 158 ArrayRef<SourceRange> Ranges = P.getRanges(); 159 160 if (!Ranges.empty()) { 161 Indent(o, indent) << "<key>ranges</key>\n"; 162 Indent(o, indent) << "<array>\n"; 163 ++indent; 164 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); 165 I != E; ++I) { 166 EmitRange(o, SM, LangOpts, CharSourceRange::getTokenRange(*I), FM, 167 indent + 1); 168 } 169 --indent; 170 Indent(o, indent) << "</array>\n"; 171 } 172 173 // Output the call depth. 174 Indent(o, indent) << "<key>depth</key>" 175 << "<integer>" << depth << "</integer>\n"; 176 177 // Output the text. 178 assert(!P.getString().empty()); 179 Indent(o, indent) << "<key>extended_message</key>\n"; 180 Indent(o, indent); 181 EmitString(o, P.getString()) << '\n'; 182 183 // Output the short text. 184 // FIXME: Really use a short string. 185 Indent(o, indent) << "<key>message</key>\n"; 186 Indent(o, indent); 187 EmitString(o, P.getString()) << '\n'; 188 189 // Finish up. 190 --indent; 191 Indent(o, indent); o << "</dict>\n"; 192 } 193 194 static void ReportPiece(raw_ostream &o, 195 const PathDiagnosticPiece &P, 196 const FIDMap& FM, const SourceManager &SM, 197 const LangOptions &LangOpts, 198 unsigned indent, 199 unsigned depth, 200 bool includeControlFlow, 201 bool isKeyEvent = false); 202 203 static void ReportCall(raw_ostream &o, 204 const PathDiagnosticCallPiece &P, 205 const FIDMap& FM, const SourceManager &SM, 206 const LangOptions &LangOpts, 207 unsigned indent, 208 unsigned depth) { 209 210 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnter = 211 P.getCallEnterEvent(); 212 213 if (callEnter) 214 ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true, 215 P.isLastInMainSourceFile()); 216 217 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnterWithinCaller = 218 P.getCallEnterWithinCallerEvent(); 219 220 ++depth; 221 222 if (callEnterWithinCaller) 223 ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts, 224 indent, depth, true); 225 226 for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) 227 ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true); 228 229 --depth; 230 231 IntrusiveRefCntPtr<PathDiagnosticEventPiece> callExit = 232 P.getCallExitEvent(); 233 234 if (callExit) 235 ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true); 236 } 237 238 static void ReportMacro(raw_ostream &o, 239 const PathDiagnosticMacroPiece& P, 240 const FIDMap& FM, const SourceManager &SM, 241 const LangOptions &LangOpts, 242 unsigned indent, 243 unsigned depth) { 244 245 for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); 246 I!=E; ++I) { 247 ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false); 248 } 249 } 250 251 static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P, 252 const FIDMap& FM, const SourceManager &SM, 253 const LangOptions &LangOpts) { 254 ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true); 255 } 256 257 static void ReportPiece(raw_ostream &o, 258 const PathDiagnosticPiece &P, 259 const FIDMap& FM, const SourceManager &SM, 260 const LangOptions &LangOpts, 261 unsigned indent, 262 unsigned depth, 263 bool includeControlFlow, 264 bool isKeyEvent) { 265 switch (P.getKind()) { 266 case PathDiagnosticPiece::ControlFlow: 267 if (includeControlFlow) 268 ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM, 269 LangOpts, indent); 270 break; 271 case PathDiagnosticPiece::Call: 272 ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts, 273 indent, depth); 274 break; 275 case PathDiagnosticPiece::Event: 276 ReportEvent(o, cast<PathDiagnosticSpotPiece>(P), FM, SM, LangOpts, 277 indent, depth, isKeyEvent); 278 break; 279 case PathDiagnosticPiece::Macro: 280 ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts, 281 indent, depth); 282 break; 283 } 284 } 285 286 void PlistDiagnostics::FlushDiagnosticsImpl( 287 std::vector<const PathDiagnostic *> &Diags, 288 FilesMade *filesMade) { 289 // Build up a set of FIDs that we use by scanning the locations and 290 // ranges of the diagnostics. 291 FIDMap FM; 292 SmallVector<FileID, 10> Fids; 293 const SourceManager* SM = 0; 294 295 if (!Diags.empty()) 296 SM = &(*(*Diags.begin())->path.begin())->getLocation().getManager(); 297 298 299 for (std::vector<const PathDiagnostic*>::iterator DI = Diags.begin(), 300 DE = Diags.end(); DI != DE; ++DI) { 301 302 const PathDiagnostic *D = *DI; 303 304 SmallVector<const PathPieces *, 5> WorkList; 305 WorkList.push_back(&D->path); 306 307 while (!WorkList.empty()) { 308 const PathPieces &path = *WorkList.pop_back_val(); 309 310 for (PathPieces::const_iterator I = path.begin(), E = path.end(); I != E; 311 ++I) { 312 const PathDiagnosticPiece *piece = I->getPtr(); 313 AddFID(FM, Fids, *SM, piece->getLocation().asLocation()); 314 ArrayRef<SourceRange> Ranges = piece->getRanges(); 315 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), 316 E = Ranges.end(); I != E; ++I) { 317 AddFID(FM, Fids, *SM, I->getBegin()); 318 AddFID(FM, Fids, *SM, I->getEnd()); 319 } 320 321 if (const PathDiagnosticCallPiece *call = 322 dyn_cast<PathDiagnosticCallPiece>(piece)) { 323 IntrusiveRefCntPtr<PathDiagnosticEventPiece> 324 callEnterWithin = call->getCallEnterWithinCallerEvent(); 325 if (callEnterWithin) 326 AddFID(FM, Fids, *SM, callEnterWithin->getLocation().asLocation()); 327 328 WorkList.push_back(&call->path); 329 } 330 else if (const PathDiagnosticMacroPiece *macro = 331 dyn_cast<PathDiagnosticMacroPiece>(piece)) { 332 WorkList.push_back(¯o->subPieces); 333 } 334 } 335 } 336 } 337 338 // Open the file. 339 std::string ErrMsg; 340 llvm::raw_fd_ostream o(OutputFile.c_str(), ErrMsg); 341 if (!ErrMsg.empty()) { 342 llvm::errs() << "warning: could not create file: " << OutputFile << '\n'; 343 return; 344 } 345 346 // Write the plist header. 347 o << PlistHeader; 348 349 // Write the root object: a <dict> containing... 350 // - "clang_version", the string representation of clang version 351 // - "files", an <array> mapping from FIDs to file names 352 // - "diagnostics", an <array> containing the path diagnostics 353 o << "<dict>\n" << 354 " <key>clang_version</key>\n"; 355 EmitString(o, getClangFullVersion()) << '\n'; 356 o << " <key>files</key>\n" 357 " <array>\n"; 358 359 for (SmallVectorImpl<FileID>::iterator I=Fids.begin(), E=Fids.end(); 360 I!=E; ++I) { 361 o << " "; 362 EmitString(o, SM->getFileEntryForID(*I)->getName()) << '\n'; 363 } 364 365 o << " </array>\n" 366 " <key>diagnostics</key>\n" 367 " <array>\n"; 368 369 for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), 370 DE = Diags.end(); DI!=DE; ++DI) { 371 372 o << " <dict>\n" 373 " <key>path</key>\n"; 374 375 const PathDiagnostic *D = *DI; 376 377 o << " <array>\n"; 378 379 for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); 380 I != E; ++I) 381 ReportDiag(o, **I, FM, *SM, LangOpts); 382 383 o << " </array>\n"; 384 385 // Output the bug type and bug category. 386 o << " <key>description</key>"; 387 EmitString(o, D->getShortDescription()) << '\n'; 388 o << " <key>category</key>"; 389 EmitString(o, D->getCategory()) << '\n'; 390 o << " <key>type</key>"; 391 EmitString(o, D->getBugType()) << '\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, LangOpts, 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