| 1 | //===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file defines the PlistDiagnostics object. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "PlistDiagnostics.h" |
| 14 | #include "clang/Analysis/IssueHash.h" |
| 15 | #include "clang/Analysis/MacroExpansionContext.h" |
| 16 | #include "clang/Analysis/PathDiagnostic.h" |
| 17 | #include "clang/Basic/PlistSupport.h" |
| 18 | #include "clang/Basic/SourceManager.h" |
| 19 | #include "clang/Basic/Version.h" |
| 20 | #include "clang/CrossTU/CrossTranslationUnit.h" |
| 21 | #include "clang/Frontend/ASTUnit.h" |
| 22 | #include "clang/Lex/Preprocessor.h" |
| 23 | #include "clang/Lex/TokenConcatenation.h" |
| 24 | #include "clang/Rewrite/Core/HTMLRewrite.h" |
| 25 | #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
| 26 | #include "llvm/ADT/SmallVector.h" |
| 27 | #include "llvm/ADT/Statistic.h" |
| 28 | #include <memory> |
| 29 | #include <optional> |
| 30 | |
| 31 | using namespace clang; |
| 32 | using namespace ento; |
| 33 | using namespace markup; |
| 34 | |
| 35 | //===----------------------------------------------------------------------===// |
| 36 | // Declarations of helper classes and functions for emitting bug reports in |
| 37 | // plist format. |
| 38 | //===----------------------------------------------------------------------===// |
| 39 | |
| 40 | namespace { |
| 41 | class PlistDiagnostics : public PathDiagnosticConsumer { |
| 42 | PathDiagnosticConsumerOptions DiagOpts; |
| 43 | const std::string OutputFile; |
| 44 | const Preprocessor &PP; |
| 45 | const cross_tu::CrossTranslationUnitContext &CTU; |
| 46 | const MacroExpansionContext &MacroExpansions; |
| 47 | const bool SupportsCrossFileDiagnostics; |
| 48 | |
| 49 | void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
| 50 | const PathPieces &Path); |
| 51 | |
| 52 | public: |
| 53 | PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, |
| 54 | const std::string &OutputFile, const Preprocessor &PP, |
| 55 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 56 | const MacroExpansionContext &MacroExpansions, |
| 57 | bool supportsMultipleFiles); |
| 58 | |
| 59 | ~PlistDiagnostics() override {} |
| 60 | |
| 61 | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
| 62 | FilesMade *filesMade) override; |
| 63 | |
| 64 | StringRef getName() const override { |
| 65 | return "PlistDiagnostics" ; |
| 66 | } |
| 67 | |
| 68 | PathGenerationScheme getGenerationScheme() const override { |
| 69 | return Extensive; |
| 70 | } |
| 71 | bool supportsLogicalOpControlFlow() const override { return true; } |
| 72 | bool supportsCrossFileDiagnostics() const override { |
| 73 | return SupportsCrossFileDiagnostics; |
| 74 | } |
| 75 | }; |
| 76 | } // end anonymous namespace |
| 77 | |
| 78 | namespace { |
| 79 | |
| 80 | /// A helper class for emitting a single report. |
| 81 | class PlistPrinter { |
| 82 | const FIDMap& FM; |
| 83 | const Preprocessor &PP; |
| 84 | const cross_tu::CrossTranslationUnitContext &CTU; |
| 85 | const MacroExpansionContext &MacroExpansions; |
| 86 | llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; |
| 87 | |
| 88 | public: |
| 89 | PlistPrinter(const FIDMap &FM, const Preprocessor &PP, |
| 90 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 91 | const MacroExpansionContext &MacroExpansions) |
| 92 | : FM(FM), PP(PP), CTU(CTU), MacroExpansions(MacroExpansions) {} |
| 93 | |
| 94 | void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { |
| 95 | ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); |
| 96 | } |
| 97 | |
| 98 | /// Print the expansions of the collected macro pieces. |
| 99 | /// |
| 100 | /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one |
| 101 | /// is found through a call piece, etc), it's subpieces are reported, and the |
| 102 | /// piece itself is collected. Call this function after the entire bugpath |
| 103 | /// was reported. |
| 104 | void ReportMacroExpansions(raw_ostream &o, unsigned indent); |
| 105 | |
| 106 | private: |
| 107 | void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, |
| 108 | unsigned indent, unsigned depth, bool includeControlFlow, |
| 109 | bool isKeyEvent = false) { |
| 110 | switch (P.getKind()) { |
| 111 | case PathDiagnosticPiece::ControlFlow: |
| 112 | if (includeControlFlow) |
| 113 | ReportControlFlow(o, P: cast<PathDiagnosticControlFlowPiece>(Val: P), indent); |
| 114 | break; |
| 115 | case PathDiagnosticPiece::Call: |
| 116 | ReportCall(o, P: cast<PathDiagnosticCallPiece>(Val: P), indent, |
| 117 | depth); |
| 118 | break; |
| 119 | case PathDiagnosticPiece::Event: |
| 120 | ReportEvent(o, P: cast<PathDiagnosticEventPiece>(Val: P), indent, depth, |
| 121 | isKeyEvent); |
| 122 | break; |
| 123 | case PathDiagnosticPiece::Macro: |
| 124 | ReportMacroSubPieces(o, P: cast<PathDiagnosticMacroPiece>(Val: P), indent, |
| 125 | depth); |
| 126 | break; |
| 127 | case PathDiagnosticPiece::Note: |
| 128 | ReportNote(o, P: cast<PathDiagnosticNotePiece>(Val: P), indent); |
| 129 | break; |
| 130 | case PathDiagnosticPiece::PopUp: |
| 131 | ReportPopUp(o, P: cast<PathDiagnosticPopUpPiece>(Val: P), indent); |
| 132 | break; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges, |
| 137 | unsigned indent); |
| 138 | void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); |
| 139 | void EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, unsigned indent); |
| 140 | |
| 141 | void ReportControlFlow(raw_ostream &o, |
| 142 | const PathDiagnosticControlFlowPiece& P, |
| 143 | unsigned indent); |
| 144 | void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
| 145 | unsigned indent, unsigned depth, bool isKeyEvent = false); |
| 146 | void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
| 147 | unsigned indent, unsigned depth); |
| 148 | void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P, |
| 149 | unsigned indent, unsigned depth); |
| 150 | void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
| 151 | unsigned indent); |
| 152 | |
| 153 | void ReportPopUp(raw_ostream &o, const PathDiagnosticPopUpPiece &P, |
| 154 | unsigned indent); |
| 155 | }; |
| 156 | |
| 157 | } // end of anonymous namespace |
| 158 | |
| 159 | /// Print coverage information to output stream @c o. |
| 160 | /// May modify the used list of files @c Fids by inserting new ones. |
| 161 | static void printCoverage(const PathDiagnostic *D, |
| 162 | unsigned InputIndentLevel, |
| 163 | SmallVectorImpl<FileID> &Fids, |
| 164 | FIDMap &FM, |
| 165 | llvm::raw_fd_ostream &o); |
| 166 | |
| 167 | static std::optional<StringRef> getExpandedMacro( |
| 168 | SourceLocation MacroLoc, const cross_tu::CrossTranslationUnitContext &CTU, |
| 169 | const MacroExpansionContext &MacroExpansions, const SourceManager &SM); |
| 170 | |
| 171 | //===----------------------------------------------------------------------===// |
| 172 | // Methods of PlistPrinter. |
| 173 | //===----------------------------------------------------------------------===// |
| 174 | |
| 175 | void PlistPrinter::EmitRanges(raw_ostream &o, |
| 176 | const ArrayRef<SourceRange> Ranges, |
| 177 | unsigned indent) { |
| 178 | |
| 179 | if (Ranges.empty()) |
| 180 | return; |
| 181 | |
| 182 | Indent(o, indent) << "<key>ranges</key>\n" ; |
| 183 | Indent(o, indent) << "<array>\n" ; |
| 184 | ++indent; |
| 185 | |
| 186 | const SourceManager &SM = PP.getSourceManager(); |
| 187 | const LangOptions &LangOpts = PP.getLangOpts(); |
| 188 | |
| 189 | for (auto &R : Ranges) |
| 190 | EmitRange(o, SM, |
| 191 | R: Lexer::getAsCharRange(Range: SM.getExpansionRange(Range: R), SM, LangOpts), |
| 192 | FM, indent: indent + 1); |
| 193 | --indent; |
| 194 | Indent(o, indent) << "</array>\n" ; |
| 195 | } |
| 196 | |
| 197 | void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, |
| 198 | unsigned indent) { |
| 199 | // Output the text. |
| 200 | assert(!Message.empty()); |
| 201 | Indent(o, indent) << "<key>extended_message</key>\n" ; |
| 202 | Indent(o, indent); |
| 203 | EmitString(o, s: Message) << '\n'; |
| 204 | |
| 205 | // Output the short text. |
| 206 | // FIXME: Really use a short string. |
| 207 | Indent(o, indent) << "<key>message</key>\n" ; |
| 208 | Indent(o, indent); |
| 209 | EmitString(o, s: Message) << '\n'; |
| 210 | } |
| 211 | |
| 212 | void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, |
| 213 | unsigned indent) { |
| 214 | if (fixits.size() == 0) |
| 215 | return; |
| 216 | |
| 217 | const SourceManager &SM = PP.getSourceManager(); |
| 218 | const LangOptions &LangOpts = PP.getLangOpts(); |
| 219 | |
| 220 | Indent(o, indent) << "<key>fixits</key>\n" ; |
| 221 | Indent(o, indent) << "<array>\n" ; |
| 222 | for (const auto &fixit : fixits) { |
| 223 | assert(!fixit.isNull()); |
| 224 | // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. |
| 225 | assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!" ); |
| 226 | assert(!fixit.BeforePreviousInsertions && "Not implemented yet!" ); |
| 227 | Indent(o, indent) << " <dict>\n" ; |
| 228 | Indent(o, indent) << " <key>remove_range</key>\n" ; |
| 229 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: fixit.RemoveRange, SM, LangOpts), |
| 230 | FM, indent: indent + 2); |
| 231 | Indent(o, indent) << " <key>insert_string</key>" ; |
| 232 | EmitString(o, s: fixit.CodeToInsert); |
| 233 | o << "\n" ; |
| 234 | Indent(o, indent) << " </dict>\n" ; |
| 235 | } |
| 236 | Indent(o, indent) << "</array>\n" ; |
| 237 | } |
| 238 | |
| 239 | void PlistPrinter::ReportControlFlow(raw_ostream &o, |
| 240 | const PathDiagnosticControlFlowPiece& P, |
| 241 | unsigned indent) { |
| 242 | |
| 243 | const SourceManager &SM = PP.getSourceManager(); |
| 244 | const LangOptions &LangOpts = PP.getLangOpts(); |
| 245 | |
| 246 | Indent(o, indent) << "<dict>\n" ; |
| 247 | ++indent; |
| 248 | |
| 249 | Indent(o, indent) << "<key>kind</key><string>control</string>\n" ; |
| 250 | |
| 251 | // Emit edges. |
| 252 | Indent(o, indent) << "<key>edges</key>\n" ; |
| 253 | ++indent; |
| 254 | Indent(o, indent) << "<array>\n" ; |
| 255 | ++indent; |
| 256 | for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); |
| 257 | I!=E; ++I) { |
| 258 | Indent(o, indent) << "<dict>\n" ; |
| 259 | ++indent; |
| 260 | |
| 261 | // Make the ranges of the start and end point self-consistent with adjacent edges |
| 262 | // by forcing to use only the beginning of the range. This simplifies the layout |
| 263 | // logic for clients. |
| 264 | Indent(o, indent) << "<key>start</key>\n" ; |
| 265 | SourceRange StartEdge( |
| 266 | SM.getExpansionLoc(Loc: I->getStart().asRange().getBegin())); |
| 267 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: StartEdge, SM, LangOpts), FM, |
| 268 | indent: indent + 1); |
| 269 | |
| 270 | Indent(o, indent) << "<key>end</key>\n" ; |
| 271 | SourceRange EndEdge(SM.getExpansionLoc(Loc: I->getEnd().asRange().getBegin())); |
| 272 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: EndEdge, SM, LangOpts), FM, |
| 273 | indent: indent + 1); |
| 274 | |
| 275 | --indent; |
| 276 | Indent(o, indent) << "</dict>\n" ; |
| 277 | } |
| 278 | --indent; |
| 279 | Indent(o, indent) << "</array>\n" ; |
| 280 | --indent; |
| 281 | |
| 282 | // Output any helper text. |
| 283 | const auto &s = P.getString(); |
| 284 | if (!s.empty()) { |
| 285 | Indent(o, indent) << "<key>alternate</key>" ; |
| 286 | EmitString(o, s) << '\n'; |
| 287 | } |
| 288 | |
| 289 | assert(P.getFixits().size() == 0 && |
| 290 | "Fixits on constrol flow pieces are not implemented yet!" ); |
| 291 | |
| 292 | --indent; |
| 293 | Indent(o, indent) << "</dict>\n" ; |
| 294 | } |
| 295 | |
| 296 | void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
| 297 | unsigned indent, unsigned depth, |
| 298 | bool isKeyEvent) { |
| 299 | |
| 300 | const SourceManager &SM = PP.getSourceManager(); |
| 301 | |
| 302 | Indent(o, indent) << "<dict>\n" ; |
| 303 | ++indent; |
| 304 | |
| 305 | Indent(o, indent) << "<key>kind</key><string>event</string>\n" ; |
| 306 | |
| 307 | if (isKeyEvent) { |
| 308 | Indent(o, indent) << "<key>key_event</key><true/>\n" ; |
| 309 | } |
| 310 | |
| 311 | // Output the location. |
| 312 | FullSourceLoc L = P.getLocation().asLocation(); |
| 313 | |
| 314 | Indent(o, indent) << "<key>location</key>\n" ; |
| 315 | EmitLocation(o, SM, L, FM, indent); |
| 316 | |
| 317 | // Output the ranges (if any). |
| 318 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
| 319 | EmitRanges(o, Ranges, indent); |
| 320 | |
| 321 | // Output the call depth. |
| 322 | Indent(o, indent) << "<key>depth</key>" ; |
| 323 | EmitInteger(o, value: depth) << '\n'; |
| 324 | |
| 325 | // Output the text. |
| 326 | EmitMessage(o, Message: P.getString(), indent); |
| 327 | |
| 328 | // Output the fixits. |
| 329 | EmitFixits(o, fixits: P.getFixits(), indent); |
| 330 | |
| 331 | // Finish up. |
| 332 | --indent; |
| 333 | Indent(o, indent); o << "</dict>\n" ; |
| 334 | } |
| 335 | |
| 336 | void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
| 337 | unsigned indent, |
| 338 | unsigned depth) { |
| 339 | |
| 340 | if (auto callEnter = P.getCallEnterEvent()) |
| 341 | ReportPiece(o, P: *callEnter, indent, depth, /*includeControlFlow*/ true, |
| 342 | isKeyEvent: P.isLastInMainSourceFile()); |
| 343 | |
| 344 | |
| 345 | ++depth; |
| 346 | |
| 347 | if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) |
| 348 | ReportPiece(o, P: *callEnterWithinCaller, indent, depth, |
| 349 | /*includeControlFlow*/ true); |
| 350 | |
| 351 | for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) |
| 352 | ReportPiece(o, P: **I, indent, depth, /*includeControlFlow*/ true); |
| 353 | |
| 354 | --depth; |
| 355 | |
| 356 | if (auto callExit = P.getCallExitEvent()) |
| 357 | ReportPiece(o, P: *callExit, indent, depth, /*includeControlFlow*/ true); |
| 358 | |
| 359 | assert(P.getFixits().size() == 0 && |
| 360 | "Fixits on call pieces are not implemented yet!" ); |
| 361 | } |
| 362 | |
| 363 | void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, |
| 364 | const PathDiagnosticMacroPiece& P, |
| 365 | unsigned indent, unsigned depth) { |
| 366 | MacroPieces.push_back(Elt: &P); |
| 367 | |
| 368 | for (const auto &SubPiece : P.subPieces) { |
| 369 | ReportPiece(o, P: *SubPiece, indent, depth, /*includeControlFlow*/ false); |
| 370 | } |
| 371 | |
| 372 | assert(P.getFixits().size() == 0 && |
| 373 | "Fixits on constrol flow pieces are not implemented yet!" ); |
| 374 | } |
| 375 | |
| 376 | void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { |
| 377 | |
| 378 | for (const PathDiagnosticMacroPiece *P : MacroPieces) { |
| 379 | const SourceManager &SM = PP.getSourceManager(); |
| 380 | |
| 381 | SourceLocation MacroExpansionLoc = |
| 382 | P->getLocation().asLocation().getExpansionLoc(); |
| 383 | |
| 384 | const std::optional<StringRef> MacroName = |
| 385 | MacroExpansions.getOriginalText(MacroExpansionLoc); |
| 386 | const std::optional<StringRef> ExpansionText = |
| 387 | getExpandedMacro(MacroLoc: MacroExpansionLoc, CTU, MacroExpansions, SM); |
| 388 | |
| 389 | if (!MacroName || !ExpansionText) |
| 390 | continue; |
| 391 | |
| 392 | Indent(o, indent) << "<dict>\n" ; |
| 393 | ++indent; |
| 394 | |
| 395 | // Output the location. |
| 396 | FullSourceLoc L = P->getLocation().asLocation(); |
| 397 | |
| 398 | Indent(o, indent) << "<key>location</key>\n" ; |
| 399 | EmitLocation(o, SM, L, FM, indent); |
| 400 | |
| 401 | // Output the ranges (if any). |
| 402 | ArrayRef<SourceRange> Ranges = P->getRanges(); |
| 403 | EmitRanges(o, Ranges, indent); |
| 404 | |
| 405 | // Output the macro name. |
| 406 | Indent(o, indent) << "<key>name</key>" ; |
| 407 | EmitString(o, s: *MacroName) << '\n'; |
| 408 | |
| 409 | // Output what it expands into. |
| 410 | Indent(o, indent) << "<key>expansion</key>" ; |
| 411 | EmitString(o, s: *ExpansionText) << '\n'; |
| 412 | |
| 413 | // Finish up. |
| 414 | --indent; |
| 415 | Indent(o, indent); |
| 416 | o << "</dict>\n" ; |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
| 421 | unsigned indent) { |
| 422 | |
| 423 | const SourceManager &SM = PP.getSourceManager(); |
| 424 | |
| 425 | Indent(o, indent) << "<dict>\n" ; |
| 426 | ++indent; |
| 427 | |
| 428 | // Output the location. |
| 429 | FullSourceLoc L = P.getLocation().asLocation(); |
| 430 | |
| 431 | Indent(o, indent) << "<key>location</key>\n" ; |
| 432 | EmitLocation(o, SM, L, FM, indent); |
| 433 | |
| 434 | // Output the ranges (if any). |
| 435 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
| 436 | EmitRanges(o, Ranges, indent); |
| 437 | |
| 438 | // Output the text. |
| 439 | EmitMessage(o, Message: P.getString(), indent); |
| 440 | |
| 441 | // Output the fixits. |
| 442 | EmitFixits(o, fixits: P.getFixits(), indent); |
| 443 | |
| 444 | // Finish up. |
| 445 | --indent; |
| 446 | Indent(o, indent); o << "</dict>\n" ; |
| 447 | } |
| 448 | |
| 449 | void PlistPrinter::(raw_ostream &o, |
| 450 | const PathDiagnosticPopUpPiece &P, |
| 451 | unsigned indent) { |
| 452 | const SourceManager &SM = PP.getSourceManager(); |
| 453 | |
| 454 | Indent(o, indent) << "<dict>\n" ; |
| 455 | ++indent; |
| 456 | |
| 457 | Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n" ; |
| 458 | |
| 459 | // Output the location. |
| 460 | FullSourceLoc L = P.getLocation().asLocation(); |
| 461 | |
| 462 | Indent(o, indent) << "<key>location</key>\n" ; |
| 463 | EmitLocation(o, SM, L, FM, indent); |
| 464 | |
| 465 | // Output the ranges (if any). |
| 466 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
| 467 | EmitRanges(o, Ranges, indent); |
| 468 | |
| 469 | // Output the text. |
| 470 | EmitMessage(o, Message: P.getString(), indent); |
| 471 | |
| 472 | assert(P.getFixits().size() == 0 && |
| 473 | "Fixits on pop-up pieces are not implemented yet!" ); |
| 474 | |
| 475 | // Finish up. |
| 476 | --indent; |
| 477 | Indent(o, indent) << "</dict>\n" ; |
| 478 | } |
| 479 | |
| 480 | //===----------------------------------------------------------------------===// |
| 481 | // Static function definitions. |
| 482 | //===----------------------------------------------------------------------===// |
| 483 | |
| 484 | /// Print coverage information to output stream @c o. |
| 485 | /// May modify the used list of files @c Fids by inserting new ones. |
| 486 | static void printCoverage(const PathDiagnostic *D, |
| 487 | unsigned InputIndentLevel, |
| 488 | SmallVectorImpl<FileID> &Fids, |
| 489 | FIDMap &FM, |
| 490 | llvm::raw_fd_ostream &o) { |
| 491 | unsigned IndentLevel = InputIndentLevel; |
| 492 | |
| 493 | Indent(o, indent: IndentLevel) << "<key>ExecutedLines</key>\n" ; |
| 494 | Indent(o, indent: IndentLevel) << "<dict>\n" ; |
| 495 | IndentLevel++; |
| 496 | |
| 497 | // Mapping from file IDs to executed lines. |
| 498 | const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); |
| 499 | for (const auto &[FID, Lines] : ExecutedLines) { |
| 500 | unsigned FileKey = AddFID(FIDs&: FM, V&: Fids, FID); |
| 501 | Indent(o, indent: IndentLevel) << "<key>" << FileKey << "</key>\n" ; |
| 502 | Indent(o, indent: IndentLevel) << "<array>\n" ; |
| 503 | IndentLevel++; |
| 504 | for (unsigned LineNo : Lines) { |
| 505 | Indent(o, indent: IndentLevel); |
| 506 | EmitInteger(o, value: LineNo) << "\n" ; |
| 507 | } |
| 508 | IndentLevel--; |
| 509 | Indent(o, indent: IndentLevel) << "</array>\n" ; |
| 510 | } |
| 511 | IndentLevel--; |
| 512 | Indent(o, indent: IndentLevel) << "</dict>\n" ; |
| 513 | |
| 514 | assert(IndentLevel == InputIndentLevel); |
| 515 | } |
| 516 | |
| 517 | //===----------------------------------------------------------------------===// |
| 518 | // Methods of PlistDiagnostics. |
| 519 | //===----------------------------------------------------------------------===// |
| 520 | |
| 521 | PlistDiagnostics::PlistDiagnostics( |
| 522 | PathDiagnosticConsumerOptions DiagOpts, const std::string &output, |
| 523 | const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, |
| 524 | const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles) |
| 525 | : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), |
| 526 | MacroExpansions(MacroExpansions), |
| 527 | SupportsCrossFileDiagnostics(supportsMultipleFiles) { |
| 528 | // FIXME: Will be used by a later planned change. |
| 529 | (void)this->CTU; |
| 530 | } |
| 531 | |
| 532 | /// Creates and registers a Plist diagnostic consumer, without any additional |
| 533 | /// text consumer. |
| 534 | void ento::createPlistDiagnosticConsumerImpl( |
| 535 | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
| 536 | const std::string &OutputFile, const Preprocessor &PP, |
| 537 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 538 | const MacroExpansionContext &MacroExpansions, bool SupportsMultipleFiles) { |
| 539 | |
| 540 | // TODO: Emit an error here. |
| 541 | if (OutputFile.empty()) |
| 542 | return; |
| 543 | |
| 544 | C.push_back(x: std::make_unique<PlistDiagnostics>( |
| 545 | args&: DiagOpts, args: OutputFile, args: PP, args: CTU, args: MacroExpansions, args&: SupportsMultipleFiles)); |
| 546 | } |
| 547 | |
| 548 | void ento::createPlistDiagnosticConsumer( |
| 549 | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
| 550 | const std::string &OutputFile, const Preprocessor &PP, |
| 551 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 552 | const MacroExpansionContext &MacroExpansions) { |
| 553 | |
| 554 | createPlistDiagnosticConsumerImpl(DiagOpts, C, OutputFile, PP, CTU, |
| 555 | MacroExpansions, |
| 556 | /*SupportsMultipleFiles=*/false); |
| 557 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
| 558 | PP, CTU, MacroExpansions); |
| 559 | } |
| 560 | |
| 561 | void ento::createPlistMultiFileDiagnosticConsumer( |
| 562 | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
| 563 | const std::string &OutputFile, const Preprocessor &PP, |
| 564 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 565 | const MacroExpansionContext &MacroExpansions) { |
| 566 | |
| 567 | createPlistDiagnosticConsumerImpl(DiagOpts, C, OutputFile, PP, CTU, |
| 568 | MacroExpansions, |
| 569 | /*SupportsMultipleFiles=*/true); |
| 570 | |
| 571 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
| 572 | PP, CTU, MacroExpansions); |
| 573 | } |
| 574 | |
| 575 | void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
| 576 | const PathPieces &Path) { |
| 577 | PlistPrinter Printer(FM, PP, CTU, MacroExpansions); |
| 578 | assert(std::is_partitioned(Path.begin(), Path.end(), |
| 579 | [](const PathDiagnosticPieceRef &E) { |
| 580 | return E->getKind() == PathDiagnosticPiece::Note; |
| 581 | }) && |
| 582 | "PathDiagnostic is not partitioned so that notes precede the rest" ); |
| 583 | |
| 584 | PathPieces::const_iterator FirstNonNote = |
| 585 | llvm::partition_point(Range: Path, P: [](const PathDiagnosticPieceRef &E) { |
| 586 | return E->getKind() == PathDiagnosticPiece::Note; |
| 587 | }); |
| 588 | |
| 589 | PathPieces::const_iterator I = Path.begin(); |
| 590 | |
| 591 | if (FirstNonNote != Path.begin()) { |
| 592 | o << " <key>notes</key>\n" |
| 593 | " <array>\n" ; |
| 594 | |
| 595 | for (; I != FirstNonNote; ++I) |
| 596 | Printer.ReportDiag(o, P: **I); |
| 597 | |
| 598 | o << " </array>\n" ; |
| 599 | } |
| 600 | |
| 601 | o << " <key>path</key>\n" ; |
| 602 | |
| 603 | o << " <array>\n" ; |
| 604 | |
| 605 | for (const auto &Piece : llvm::make_range(x: I, y: Path.end())) |
| 606 | Printer.ReportDiag(o, P: *Piece); |
| 607 | |
| 608 | o << " </array>\n" ; |
| 609 | |
| 610 | if (!DiagOpts.ShouldDisplayMacroExpansions) |
| 611 | return; |
| 612 | |
| 613 | o << " <key>macro_expansions</key>\n" |
| 614 | " <array>\n" ; |
| 615 | Printer.ReportMacroExpansions(o, /* indent */ 4); |
| 616 | o << " </array>\n" ; |
| 617 | } |
| 618 | |
| 619 | void PlistDiagnostics::FlushDiagnosticsImpl( |
| 620 | std::vector<const PathDiagnostic *> &Diags, |
| 621 | FilesMade *filesMade) { |
| 622 | // Build up a set of FIDs that we use by scanning the locations and |
| 623 | // ranges of the diagnostics. |
| 624 | FIDMap FM; |
| 625 | SmallVector<FileID, 10> Fids; |
| 626 | const SourceManager& SM = PP.getSourceManager(); |
| 627 | const LangOptions &LangOpts = PP.getLangOpts(); |
| 628 | |
| 629 | auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { |
| 630 | AddFID(FIDs&: FM, V&: Fids, SM, L: Piece.getLocation().asLocation()); |
| 631 | ArrayRef<SourceRange> Ranges = Piece.getRanges(); |
| 632 | for (const SourceRange &Range : Ranges) { |
| 633 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getBegin()); |
| 634 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getEnd()); |
| 635 | } |
| 636 | }; |
| 637 | |
| 638 | for (const PathDiagnostic *D : Diags) { |
| 639 | |
| 640 | SmallVector<const PathPieces *, 5> WorkList; |
| 641 | WorkList.push_back(Elt: &D->path); |
| 642 | |
| 643 | while (!WorkList.empty()) { |
| 644 | const PathPieces &Path = *WorkList.pop_back_val(); |
| 645 | |
| 646 | for (const auto &Iter : Path) { |
| 647 | const PathDiagnosticPiece &Piece = *Iter; |
| 648 | AddPieceFID(Piece); |
| 649 | |
| 650 | if (const PathDiagnosticCallPiece *Call = |
| 651 | dyn_cast<PathDiagnosticCallPiece>(Val: &Piece)) { |
| 652 | if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) |
| 653 | AddPieceFID(*CallEnterWithin); |
| 654 | |
| 655 | if (auto CallEnterEvent = Call->getCallEnterEvent()) |
| 656 | AddPieceFID(*CallEnterEvent); |
| 657 | |
| 658 | WorkList.push_back(Elt: &Call->path); |
| 659 | } else if (const PathDiagnosticMacroPiece *Macro = |
| 660 | dyn_cast<PathDiagnosticMacroPiece>(Val: &Piece)) { |
| 661 | WorkList.push_back(Elt: &Macro->subPieces); |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | } |
| 666 | |
| 667 | // Open the file. |
| 668 | std::error_code EC; |
| 669 | llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
| 670 | if (EC) { |
| 671 | llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; |
| 672 | return; |
| 673 | } |
| 674 | |
| 675 | EmitPlistHeader(o); |
| 676 | |
| 677 | // Write the root object: a <dict> containing... |
| 678 | // - "clang_version", the string representation of clang version |
| 679 | // - "files", an <array> mapping from FIDs to file names |
| 680 | // - "diagnostics", an <array> containing the path diagnostics |
| 681 | o << "<dict>\n" << |
| 682 | " <key>clang_version</key>\n" ; |
| 683 | EmitString(o, s: getClangFullVersion()) << '\n'; |
| 684 | o << " <key>diagnostics</key>\n" |
| 685 | " <array>\n" ; |
| 686 | |
| 687 | for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), |
| 688 | DE = Diags.end(); DI!=DE; ++DI) { |
| 689 | |
| 690 | o << " <dict>\n" ; |
| 691 | |
| 692 | const PathDiagnostic *D = *DI; |
| 693 | printBugPath(o, FM, Path: D->path); |
| 694 | |
| 695 | // Output the bug type and bug category. |
| 696 | o << " <key>description</key>" ; |
| 697 | EmitString(o, s: D->getShortDescription()) << '\n'; |
| 698 | o << " <key>category</key>" ; |
| 699 | EmitString(o, s: D->getCategory()) << '\n'; |
| 700 | o << " <key>type</key>" ; |
| 701 | EmitString(o, s: D->getBugType()) << '\n'; |
| 702 | o << " <key>check_name</key>" ; |
| 703 | EmitString(o, s: D->getCheckerName()) << '\n'; |
| 704 | |
| 705 | o << " <!-- This hash is experimental and going to change! -->\n" ; |
| 706 | o << " <key>issue_hash_content_of_line_in_context</key>" ; |
| 707 | PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); |
| 708 | FullSourceLoc L(SM.getExpansionLoc(Loc: UPDLoc.isValid() |
| 709 | ? UPDLoc.asLocation() |
| 710 | : D->getLocation().asLocation()), |
| 711 | SM); |
| 712 | |
| 713 | EmitString(o, s: D->getIssueHash(SrcMgr: SM, LangOpts)) << '\n'; |
| 714 | |
| 715 | // Output information about the semantic context where |
| 716 | // the issue occurred. |
| 717 | if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { |
| 718 | // FIXME: handle blocks, which have no name. |
| 719 | if (const NamedDecl *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) { |
| 720 | StringRef declKind; |
| 721 | switch (ND->getKind()) { |
| 722 | case Decl::CXXRecord: |
| 723 | declKind = "C++ class" ; |
| 724 | break; |
| 725 | case Decl::CXXMethod: |
| 726 | declKind = "C++ method" ; |
| 727 | break; |
| 728 | case Decl::ObjCMethod: |
| 729 | declKind = "Objective-C method" ; |
| 730 | break; |
| 731 | case Decl::Function: |
| 732 | declKind = "function" ; |
| 733 | break; |
| 734 | default: |
| 735 | break; |
| 736 | } |
| 737 | if (!declKind.empty()) { |
| 738 | const std::string &declName = ND->getDeclName().getAsString(); |
| 739 | o << " <key>issue_context_kind</key>" ; |
| 740 | EmitString(o, s: declKind) << '\n'; |
| 741 | o << " <key>issue_context</key>" ; |
| 742 | EmitString(o, s: declName) << '\n'; |
| 743 | } |
| 744 | |
| 745 | // Output the bug hash for issue unique-ing. Currently, it's just an |
| 746 | // offset from the beginning of the function. |
| 747 | if (const Stmt *Body = DeclWithIssue->getBody()) { |
| 748 | |
| 749 | // If the bug uniqueing location exists, use it for the hash. |
| 750 | // For example, this ensures that two leaks reported on the same line |
| 751 | // will have different issue_hashes and that the hash will identify |
| 752 | // the leak location even after code is added between the allocation |
| 753 | // site and the end of scope (leak report location). |
| 754 | if (UPDLoc.isValid()) { |
| 755 | FullSourceLoc UFunL( |
| 756 | SM.getExpansionLoc( |
| 757 | Loc: D->getUniqueingDecl()->getBody()->getBeginLoc()), |
| 758 | SM); |
| 759 | o << " <key>issue_hash_function_offset</key><string>" |
| 760 | << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() |
| 761 | << "</string>\n" ; |
| 762 | |
| 763 | // Otherwise, use the location on which the bug is reported. |
| 764 | } else { |
| 765 | FullSourceLoc FunL(SM.getExpansionLoc(Loc: Body->getBeginLoc()), SM); |
| 766 | o << " <key>issue_hash_function_offset</key><string>" |
| 767 | << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() |
| 768 | << "</string>\n" ; |
| 769 | } |
| 770 | |
| 771 | } |
| 772 | } |
| 773 | } |
| 774 | |
| 775 | // Output the location of the bug. |
| 776 | o << " <key>location</key>\n" ; |
| 777 | EmitLocation(o, SM, L: D->getLocation().asLocation(), FM, indent: 2); |
| 778 | |
| 779 | // Output the diagnostic to the sub-diagnostic client, if any. |
| 780 | if (!filesMade->empty()) { |
| 781 | StringRef lastName; |
| 782 | PDFileEntry::ConsumerFiles *files = filesMade->getFiles(PD: *D); |
| 783 | if (files) { |
| 784 | for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), |
| 785 | CE = files->end(); CI != CE; ++CI) { |
| 786 | StringRef newName = CI->first; |
| 787 | if (newName != lastName) { |
| 788 | if (!lastName.empty()) { |
| 789 | o << " </array>\n" ; |
| 790 | } |
| 791 | lastName = newName; |
| 792 | o << " <key>" << lastName << "_files</key>\n" ; |
| 793 | o << " <array>\n" ; |
| 794 | } |
| 795 | o << " <string>" << CI->second << "</string>\n" ; |
| 796 | } |
| 797 | o << " </array>\n" ; |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | printCoverage(D, /*IndentLevel=*/InputIndentLevel: 2, Fids, FM, o); |
| 802 | |
| 803 | // Close up the entry. |
| 804 | o << " </dict>\n" ; |
| 805 | } |
| 806 | |
| 807 | o << " </array>\n" ; |
| 808 | |
| 809 | o << " <key>files</key>\n" |
| 810 | " <array>\n" ; |
| 811 | for (FileID FID : Fids) |
| 812 | EmitString(o&: o << " " , s: SM.getFileEntryRefForID(FID)->getName()) << '\n'; |
| 813 | o << " </array>\n" ; |
| 814 | |
| 815 | if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { |
| 816 | o << " <key>statistics</key>\n" ; |
| 817 | std::string stats; |
| 818 | llvm::raw_string_ostream os(stats); |
| 819 | llvm::PrintStatisticsJSON(OS&: os); |
| 820 | EmitString(o, s: html::EscapeText(s: stats)) << '\n'; |
| 821 | } |
| 822 | |
| 823 | // Finish. |
| 824 | o << "</dict>\n</plist>\n" ; |
| 825 | } |
| 826 | |
| 827 | //===----------------------------------------------------------------------===// |
| 828 | // Definitions of helper functions and methods for expanding macros. |
| 829 | //===----------------------------------------------------------------------===// |
| 830 | |
| 831 | static std::optional<StringRef> |
| 832 | getExpandedMacro(SourceLocation MacroExpansionLoc, |
| 833 | const cross_tu::CrossTranslationUnitContext &CTU, |
| 834 | const MacroExpansionContext &MacroExpansions, |
| 835 | const SourceManager &SM) { |
| 836 | if (auto CTUMacroExpCtx = |
| 837 | CTU.getMacroExpansionContextForSourceLocation(ToLoc: MacroExpansionLoc)) { |
| 838 | return CTUMacroExpCtx->getFormattedExpandedText(MacroExpansionLoc); |
| 839 | } |
| 840 | return MacroExpansions.getFormattedExpandedText(MacroExpansionLoc); |
| 841 | } |
| 842 | |