diff options
author | Artem Dergachev <artem.dergachev@gmail.com> | 2020-07-30 08:52:22 -0700 |
---|---|---|
committer | Artem Dergachev <artem.dergachev@gmail.com> | 2020-10-13 10:53:10 -0700 |
commit | 44b7cf2983b6a8373c99a9b254f8c3f944e03f35 (patch) | |
tree | da853a66b0ab3f3e2158a89ca30b3aa22ef49116 /clang/lib/StaticAnalyzer | |
parent | b76dc111dd02672488df794570d82e3edbbfa5d8 (diff) | |
download | llvm-44b7cf2983b6a8373c99a9b254f8c3f944e03f35.zip llvm-44b7cf2983b6a8373c99a9b254f8c3f944e03f35.tar.gz llvm-44b7cf2983b6a8373c99a9b254f8c3f944e03f35.tar.bz2 |
[analyzer] NFC: Move path diagnostic consumer implementations to libAnalysis.
With this change, we're more or less ready to allow users outside
of the Static Analyzer to take advantage of path diagnostic consumers
for emitting their warnings in different formats.
Differential Revision: https://reviews.llvm.org/D67422
Diffstat (limited to 'clang/lib/StaticAnalyzer')
-rw-r--r-- | clang/lib/StaticAnalyzer/Core/CMakeLists.txt | 4 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | 1152 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp | 1384 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp | 400 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp | 155 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 4 |
6 files changed, 2 insertions, 3097 deletions
diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt index d947d41..b1f527e 100644 --- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -30,16 +30,13 @@ add_clang_library(clangStaticAnalyzerCore ExprEngineCallAndReturn.cpp ExprEngineObjC.cpp FunctionSummary.cpp - HTMLDiagnostics.cpp LoopUnrolling.cpp LoopWidening.cpp MemRegion.cpp - PlistDiagnostics.cpp ProgramState.cpp RangeConstraintManager.cpp RangedConstraintManager.cpp RegionStore.cpp - SarifDiagnostics.cpp SimpleConstraintManager.cpp SimpleSValBuilder.cpp SMTConstraintManager.cpp @@ -47,7 +44,6 @@ add_clang_library(clangStaticAnalyzerCore SValBuilder.cpp SVals.cpp SymbolManager.cpp - TextDiagnostics.cpp WorkList.cpp LINK_LIBS diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp deleted file mode 100644 index 35c590f..0000000 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ /dev/null @@ -1,1152 +0,0 @@ -//===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the HTMLDiagnostics object. -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/IssueHash.h" -#include "clang/Analysis/PathDiagnostic.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclBase.h" -#include "clang/AST/Stmt.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/LLVM.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Lex/Lexer.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Lex/Token.h" -#include "clang/Rewrite/Core/HTMLRewrite.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/ADT/iterator_range.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/raw_ostream.h" -#include <algorithm> -#include <cassert> -#include <map> -#include <memory> -#include <set> -#include <sstream> -#include <string> -#include <system_error> -#include <utility> -#include <vector> - -using namespace clang; -using namespace ento; - -//===----------------------------------------------------------------------===// -// Boilerplate. -//===----------------------------------------------------------------------===// - -namespace { - -class HTMLDiagnostics : public PathDiagnosticConsumer { - PathDiagnosticConsumerOptions DiagOpts; - std::string Directory; - bool createdDir = false; - bool noDir = false; - const Preprocessor &PP; - const bool SupportsCrossFileDiagnostics; - -public: - HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts, - const std::string &OutputDir, const Preprocessor &pp, - bool supportsMultipleFiles) - : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp), - SupportsCrossFileDiagnostics(supportsMultipleFiles) {} - - ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } - - void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) override; - - StringRef getName() const override { - return "HTMLDiagnostics"; - } - - bool supportsCrossFileDiagnostics() const override { - return SupportsCrossFileDiagnostics; - } - - unsigned ProcessMacroPiece(raw_ostream &os, - const PathDiagnosticMacroPiece& P, - unsigned num); - - void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P, - const std::vector<SourceRange> &PopUpRanges, unsigned num, - unsigned max); - - void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, - const char *HighlightStart = "<span class=\"mrange\">", - const char *HighlightEnd = "</span>"); - - void ReportDiag(const PathDiagnostic& D, - FilesMade *filesMade); - - // Generate the full HTML report - std::string GenerateHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, - const char *declName); - - // Add HTML header/footers to file specified by FID - void FinalizeHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, - FileID FID, const FileEntry *Entry, const char *declName); - - // Rewrite the file specified by FID with HTML formatting. - void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID); - - -private: - /// \return Javascript for displaying shortcuts help; - StringRef showHelpJavascript(); - - /// \return Javascript for navigating the HTML report using j/k keys. - StringRef generateKeyboardNavigationJavascript(); - - /// \return JavaScript for an option to only show relevant lines. - std::string showRelevantLinesJavascript( - const PathDiagnostic &D, const PathPieces &path); - - /// Write executed lines from \p D in JSON format into \p os. - void dumpCoverageData(const PathDiagnostic &D, - const PathPieces &path, - llvm::raw_string_ostream &os); -}; - -} // namespace - -void ento::createHTMLDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &OutputDir, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - - // FIXME: HTML is currently our default output type, but if the output - // directory isn't specified, it acts like if it was in the minimal text - // output mode. This doesn't make much sense, we should have the minimal text - // as our default. In the case of backward compatibility concerns, this could - // be preserved with -analyzer-config-compatibility-mode=true. - createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU); - - // TODO: Emit an error here. - if (OutputDir.empty()) - return; - - C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, true)); -} - -void ento::createHTMLSingleFileDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &OutputDir, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU); - - // TODO: Emit an error here. - if (OutputDir.empty()) - return; - - C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, false)); -} - -void ento::createPlistHTMLDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - createHTMLDiagnosticConsumer( - DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, - CTU); - createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU); - createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP, - CTU); -} - -//===----------------------------------------------------------------------===// -// Report processing. -//===----------------------------------------------------------------------===// - -void HTMLDiagnostics::FlushDiagnosticsImpl( - std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) { - for (const auto Diag : Diags) - ReportDiag(*Diag, filesMade); -} - -void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, - FilesMade *filesMade) { - // Create the HTML directory if it is missing. - if (!createdDir) { - createdDir = true; - if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { - llvm::errs() << "warning: could not create directory '" - << Directory << "': " << ec.message() << '\n'; - noDir = true; - return; - } - } - - if (noDir) - return; - - // First flatten out the entire path to make it easier to use. - PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false); - - // The path as already been prechecked that the path is non-empty. - assert(!path.empty()); - const SourceManager &SMgr = path.front()->getLocation().getManager(); - - // Create a new rewriter to generate HTML. - Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); - - // The file for the first path element is considered the main report file, it - // will usually be equivalent to SMgr.getMainFileID(); however, it might be a - // header when -analyzer-opt-analyze-headers is used. - FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); - - // Get the function/method name - SmallString<128> declName("unknown"); - int offsetDecl = 0; - if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { - if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue)) - declName = ND->getDeclName().getAsString(); - - if (const Stmt *Body = DeclWithIssue->getBody()) { - // Retrieve the relative position of the declaration which will be used - // for the file name - FullSourceLoc L( - SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), - SMgr); - FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr); - offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); - } - } - - std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str()); - if (report.empty()) { - llvm::errs() << "warning: no diagnostics generated for main file.\n"; - return; - } - - // Create a path for the target HTML file. - int FD; - SmallString<128> Model, ResultPath; - - if (!DiagOpts.ShouldWriteStableReportFilename) { - llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); - if (std::error_code EC = - llvm::sys::fs::make_absolute(Model)) { - llvm::errs() << "warning: could not make '" << Model - << "' absolute: " << EC.message() << '\n'; - return; - } - if (std::error_code EC = - llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) { - llvm::errs() << "warning: could not create file in '" << Directory - << "': " << EC.message() << '\n'; - return; - } - } else { - int i = 1; - std::error_code EC; - do { - // Find a filename which is not already used - const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile); - std::stringstream filename; - Model = ""; - filename << "report-" - << llvm::sys::path::filename(Entry->getName()).str() - << "-" << declName.c_str() - << "-" << offsetDecl - << "-" << i << ".html"; - llvm::sys::path::append(Model, Directory, - filename.str()); - EC = llvm::sys::fs::openFileForReadWrite( - Model, FD, llvm::sys::fs::CD_CreateNew, llvm::sys::fs::OF_None); - if (EC && EC != llvm::errc::file_exists) { - llvm::errs() << "warning: could not create file '" << Model - << "': " << EC.message() << '\n'; - return; - } - i++; - } while (EC); - } - - llvm::raw_fd_ostream os(FD, true); - - if (filesMade) - filesMade->addDiagnostic(D, getName(), - llvm::sys::path::filename(ResultPath)); - - // Emit the HTML to disk. - os << report; -} - -std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, const char *declName) { - // Rewrite source files as HTML for every new file the path crosses - std::vector<FileID> FileIDs; - for (auto I : path) { - FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID(); - if (llvm::is_contained(FileIDs, FID)) - continue; - - FileIDs.push_back(FID); - RewriteFile(R, path, FID); - } - - if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) { - // Prefix file names, anchor tags, and nav cursors to every file - for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) { - std::string s; - llvm::raw_string_ostream os(s); - - if (I != FileIDs.begin()) - os << "<hr class=divider>\n"; - - os << "<div id=File" << I->getHashValue() << ">\n"; - - // Left nav arrow - if (I != FileIDs.begin()) - os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue() - << "\">←</a></div>"; - - os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName() - << "</h4>\n"; - - // Right nav arrow - if (I + 1 != E) - os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue() - << "\">→</a></div>"; - - os << "</div>\n"; - - R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str()); - } - - // Append files to the main report file in the order they appear in the path - for (auto I : llvm::make_range(FileIDs.begin() + 1, FileIDs.end())) { - std::string s; - llvm::raw_string_ostream os(s); - - const RewriteBuffer *Buf = R.getRewriteBufferFor(I); - for (auto BI : *Buf) - os << BI; - - R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str()); - } - } - - const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]); - if (!Buf) - return {}; - - // Add CSS, header, and footer. - FileID FID = - path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); - const FileEntry* Entry = SMgr.getFileEntryForID(FID); - FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName); - - std::string file; - llvm::raw_string_ostream os(file); - for (auto BI : *Buf) - os << BI; - - return os.str(); -} - -void HTMLDiagnostics::dumpCoverageData( - const PathDiagnostic &D, - const PathPieces &path, - llvm::raw_string_ostream &os) { - - const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines(); - - os << "var relevant_lines = {"; - for (auto I = ExecutedLines.begin(), - E = ExecutedLines.end(); I != E; ++I) { - if (I != ExecutedLines.begin()) - os << ", "; - - os << "\"" << I->first.getHashValue() << "\": {"; - for (unsigned LineNo : I->second) { - if (LineNo != *(I->second.begin())) - os << ", "; - - os << "\"" << LineNo << "\": 1"; - } - os << "}"; - } - - os << "};"; -} - -std::string HTMLDiagnostics::showRelevantLinesJavascript( - const PathDiagnostic &D, const PathPieces &path) { - std::string s; - llvm::raw_string_ostream os(s); - os << "<script type='text/javascript'>\n"; - dumpCoverageData(D, path, os); - os << R"<<<( - -var filterCounterexample = function (hide) { - var tables = document.getElementsByClassName("code"); - for (var t=0; t<tables.length; t++) { - var table = tables[t]; - var file_id = table.getAttribute("data-fileid"); - var lines_in_fid = relevant_lines[file_id]; - if (!lines_in_fid) { - lines_in_fid = {}; - } - var lines = table.getElementsByClassName("codeline"); - for (var i=0; i<lines.length; i++) { - var el = lines[i]; - var lineNo = el.getAttribute("data-linenumber"); - if (!lines_in_fid[lineNo]) { - if (hide) { - el.setAttribute("hidden", ""); - } else { - el.removeAttribute("hidden"); - } - } - } - } -} - -window.addEventListener("keydown", function (event) { - if (event.defaultPrevented) { - return; - } - if (event.key == "S") { - var checked = document.getElementsByName("showCounterexample")[0].checked; - filterCounterexample(!checked); - document.getElementsByName("showCounterexample")[0].checked = !checked; - } else { - return; - } - event.preventDefault(); -}, true); - -document.addEventListener("DOMContentLoaded", function() { - document.querySelector('input[name="showCounterexample"]').onchange= - function (event) { - filterCounterexample(this.checked); - }; -}); -</script> - -<form> - <input type="checkbox" name="showCounterexample" id="showCounterexample" /> - <label for="showCounterexample"> - Show only relevant lines - </label> -</form> -)<<<"; - - return os.str(); -} - -void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, FileID FID, - const FileEntry *Entry, const char *declName) { - // This is a cludge; basically we want to append either the full - // working directory if we have no directory information. This is - // a work in progress. - - llvm::SmallString<0> DirName; - - if (llvm::sys::path::is_relative(Entry->getName())) { - llvm::sys::fs::current_path(DirName); - DirName += '/'; - } - - int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); - int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); - - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript()); - - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), - generateKeyboardNavigationJavascript()); - - // Checkbox and javascript for filtering the output to the counterexample. - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), - showRelevantLinesJavascript(D, path)); - - // Add the name of the file as an <h1> tag. - { - std::string s; - llvm::raw_string_ostream os(s); - - os << "<!-- REPORTHEADER -->\n" - << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" - "<tr><td class=\"rowname\">File:</td><td>" - << html::EscapeText(DirName) - << html::EscapeText(Entry->getName()) - << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>" - "<a href=\"#EndPath\">line " - << LineNumber - << ", column " - << ColumnNumber - << "</a><br />" - << D.getVerboseDescription() << "</td></tr>\n"; - - // The navigation across the extra notes pieces. - unsigned NumExtraPieces = 0; - for (const auto &Piece : path) { - if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) { - int LineNumber = - P->getLocation().asLocation().getExpansionLineNumber(); - int ColumnNumber = - P->getLocation().asLocation().getExpansionColumnNumber(); - os << "<tr><td class=\"rowname\">Note:</td><td>" - << "<a href=\"#Note" << NumExtraPieces << "\">line " - << LineNumber << ", column " << ColumnNumber << "</a><br />" - << P->getString() << "</td></tr>"; - ++NumExtraPieces; - } - } - - // Output any other meta data. - - for (PathDiagnostic::meta_iterator I = D.meta_begin(), E = D.meta_end(); - I != E; ++I) { - os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; - } - - os << R"<<<( -</table> -<!-- REPORTSUMMARYEXTRA --> -<h3>Annotated Source Code</h3> -<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a> - to see keyboard shortcuts</p> -<input type="checkbox" class="spoilerhider" id="showinvocation" /> -<label for="showinvocation" >Show analyzer invocation</label> -<div class="spoiler">clang -cc1 )<<<"; - os << html::EscapeText(DiagOpts.ToolInvocation); - os << R"<<<( -</div> -<div id='tooltiphint' hidden="true"> - <p>Keyboard shortcuts: </p> - <ul> - <li>Use 'j/k' keys for keyboard navigation</li> - <li>Use 'Shift+S' to show/hide relevant lines</li> - <li>Use '?' to toggle this window</li> - </ul> - <a href="#" onclick="toggleHelp(); return false;">Close</a> -</div> -)<<<"; - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); - } - - // Embed meta-data tags. - { - std::string s; - llvm::raw_string_ostream os(s); - - StringRef BugDesc = D.getVerboseDescription(); - if (!BugDesc.empty()) - os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; - - StringRef BugType = D.getBugType(); - if (!BugType.empty()) - os << "\n<!-- BUGTYPE " << BugType << " -->\n"; - - PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); - FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() - ? UPDLoc.asLocation() - : D.getLocation().asLocation()), - SMgr); - const Decl *DeclWithIssue = D.getDeclWithIssue(); - - StringRef BugCategory = D.getCategory(); - if (!BugCategory.empty()) - os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; - - os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; - - os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n"; - - os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; - - os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " - << getIssueHash(L, D.getCheckerName(), D.getBugType(), DeclWithIssue, - PP.getLangOpts()) - << " -->\n"; - - os << "\n<!-- BUGLINE " - << LineNumber - << " -->\n"; - - os << "\n<!-- BUGCOLUMN " - << ColumnNumber - << " -->\n"; - - os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n"; - - // Mark the end of the tags. - os << "\n<!-- BUGMETAEND -->\n"; - - // Insert the text. - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); - } - - html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); -} - -StringRef HTMLDiagnostics::showHelpJavascript() { - return R"<<<( -<script type='text/javascript'> - -var toggleHelp = function() { - var hint = document.querySelector("#tooltiphint"); - var attributeName = "hidden"; - if (hint.hasAttribute(attributeName)) { - hint.removeAttribute(attributeName); - } else { - hint.setAttribute("hidden", "true"); - } -}; -window.addEventListener("keydown", function (event) { - if (event.defaultPrevented) { - return; - } - if (event.key == "?") { - toggleHelp(); - } else { - return; - } - event.preventDefault(); -}); -</script> -)<<<"; -} - -static bool shouldDisplayPopUpRange(const SourceRange &Range) { - return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID()); -} - -static void -HandlePopUpPieceStartTag(Rewriter &R, - const std::vector<SourceRange> &PopUpRanges) { - for (const auto &Range : PopUpRanges) { - if (!shouldDisplayPopUpRange(Range)) - continue; - - html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", - "<table class='variable_popup'><tbody>", - /*IsTokenRange=*/true); - } -} - -static void HandlePopUpPieceEndTag(Rewriter &R, - const PathDiagnosticPopUpPiece &Piece, - std::vector<SourceRange> &PopUpRanges, - unsigned int LastReportedPieceIndex, - unsigned int PopUpPieceIndex) { - SmallString<256> Buf; - llvm::raw_svector_ostream Out(Buf); - - SourceRange Range(Piece.getLocation().asRange()); - if (!shouldDisplayPopUpRange(Range)) - return; - - // Write out the path indices with a right arrow and the message as a row. - Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>" - << LastReportedPieceIndex; - - // Also annotate the state transition with extra indices. - Out << '.' << PopUpPieceIndex; - - Out << "</div></td><td>" << Piece.getString() << "</td></tr>"; - - // If no report made at this range mark the variable and add the end tags. - if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) == - PopUpRanges.end()) { - // Store that we create a report at this range. - PopUpRanges.push_back(Range); - - Out << "</tbody></table></span>"; - html::HighlightRange(R, Range.getBegin(), Range.getEnd(), - "<span class='variable'>", Buf.c_str(), - /*IsTokenRange=*/true); - } else { - // Otherwise inject just the new row at the end of the range. - html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(), - /*IsTokenRange=*/true); - } -} - -void HTMLDiagnostics::RewriteFile(Rewriter &R, - const PathPieces& path, FileID FID) { - // Process the path. - // Maintain the counts of extra note pieces separately. - unsigned TotalPieces = path.size(); - unsigned TotalNotePieces = std::count_if( - path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { - return isa<PathDiagnosticNotePiece>(*p); - }); - unsigned PopUpPieceCount = std::count_if( - path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { - return isa<PathDiagnosticPopUpPiece>(*p); - }); - - unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount; - unsigned NumRegularPieces = TotalRegularPieces; - unsigned NumNotePieces = TotalNotePieces; - // Stores the count of the regular piece indices. - std::map<int, int> IndexMap; - - // Stores the different ranges where we have reported something. - std::vector<SourceRange> PopUpRanges; - for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) { - const auto &Piece = *I->get(); - - if (isa<PathDiagnosticPopUpPiece>(Piece)) { - ++IndexMap[NumRegularPieces]; - } else if (isa<PathDiagnosticNotePiece>(Piece)) { - // This adds diagnostic bubbles, but not navigation. - // Navigation through note pieces would be added later, - // as a separate pass through the piece list. - HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces); - --NumNotePieces; - } else { - HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces, - TotalRegularPieces); - --NumRegularPieces; - } - } - - // Secondary indexing if we are having multiple pop-ups between two notes. - // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...) - NumRegularPieces = TotalRegularPieces; - for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) { - const auto &Piece = *I->get(); - - if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) { - int PopUpPieceIndex = IndexMap[NumRegularPieces]; - - // Pop-up pieces needs the index of the last reported piece and its count - // how many times we report to handle multiple reports on the same range. - // This marks the variable, adds the </table> end tag and the message - // (list element) as a row. The <table> start tag will be added after the - // rows has been written out. Note: It stores every different range. - HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces, - PopUpPieceIndex); - - if (PopUpPieceIndex > 0) - --IndexMap[NumRegularPieces]; - - } else if (!isa<PathDiagnosticNotePiece>(Piece)) { - --NumRegularPieces; - } - } - - // Add the <table> start tag of pop-up pieces based on the stored ranges. - HandlePopUpPieceStartTag(R, PopUpRanges); - - // Add line numbers, header, footer, etc. - html::EscapeText(R, FID); - html::AddLineNumbers(R, FID); - - // If we have a preprocessor, relex the file and syntax highlight. - // We might not have a preprocessor if we come from a deserialized AST file, - // for example. - html::SyntaxHighlight(R, FID, PP); - html::HighlightMacros(R, FID, PP); -} - -void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, - const PathDiagnosticPiece &P, - const std::vector<SourceRange> &PopUpRanges, - unsigned num, unsigned max) { - // For now, just draw a box above the line in question, and emit the - // warning. - FullSourceLoc Pos = P.getLocation().asLocation(); - - if (!Pos.isValid()) - return; - - SourceManager &SM = R.getSourceMgr(); - assert(&Pos.getManager() == &SM && "SourceManagers are different!"); - std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); - - if (LPosInfo.first != BugFileID) - return; - - const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); - const char* FileStart = Buf->getBufferStart(); - - // Compute the column number. Rewind from the current position to the start - // of the line. - unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); - const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); - const char *LineStart = TokInstantiationPtr-ColNo; - - // Compute LineEnd. - const char *LineEnd = TokInstantiationPtr; - const char* FileEnd = Buf->getBufferEnd(); - while (*LineEnd != '\n' && LineEnd != FileEnd) - ++LineEnd; - - // Compute the margin offset by counting tabs and non-tabs. - unsigned PosNo = 0; - for (const char* c = LineStart; c != TokInstantiationPtr; ++c) - PosNo += *c == '\t' ? 8 : 1; - - // Create the html for the message. - - const char *Kind = nullptr; - bool IsNote = false; - bool SuppressIndex = (max == 1); - switch (P.getKind()) { - case PathDiagnosticPiece::Event: Kind = "Event"; break; - case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break; - // Setting Kind to "Control" is intentional. - case PathDiagnosticPiece::Macro: Kind = "Control"; break; - case PathDiagnosticPiece::Note: - Kind = "Note"; - IsNote = true; - SuppressIndex = true; - break; - case PathDiagnosticPiece::Call: - case PathDiagnosticPiece::PopUp: - llvm_unreachable("Calls and extra notes should already be handled"); - } - - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\""; - - if (IsNote) - os << "Note" << num; - else if (num == max) - os << "EndPath"; - else - os << "Path" << num; - - os << "\" class=\"msg"; - if (Kind) - os << " msg" << Kind; - os << "\" style=\"margin-left:" << PosNo << "ex"; - - // Output a maximum size. - if (!isa<PathDiagnosticMacroPiece>(P)) { - // Get the string and determining its maximum substring. - const auto &Msg = P.getString(); - unsigned max_token = 0; - unsigned cnt = 0; - unsigned len = Msg.size(); - - for (char C : Msg) - switch (C) { - default: - ++cnt; - continue; - case ' ': - case '\t': - case '\n': - if (cnt > max_token) max_token = cnt; - cnt = 0; - } - - if (cnt > max_token) - max_token = cnt; - - // Determine the approximate size of the message bubble in em. - unsigned em; - const unsigned max_line = 120; - - if (max_token >= max_line) - em = max_token / 2; - else { - unsigned characters = max_line; - unsigned lines = len / max_line; - - if (lines > 0) { - for (; characters > max_token; --characters) - if (len / characters > lines) { - ++characters; - break; - } - } - - em = characters / 2; - } - - if (em < max_line/2) - os << "; max-width:" << em << "em"; - } - else - os << "; max-width:100em"; - - os << "\">"; - - if (!SuppressIndex) { - os << "<table class=\"msgT\"><tr><td valign=\"top\">"; - os << "<div class=\"PathIndex"; - if (Kind) os << " PathIndex" << Kind; - os << "\">" << num << "</div>"; - - if (num > 1) { - os << "</td><td><div class=\"PathNav\"><a href=\"#Path" - << (num - 1) - << "\" title=\"Previous event (" - << (num - 1) - << ")\">←</a></div>"; - } - - os << "</td><td>"; - } - - if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) { - os << "Within the expansion of the macro '"; - - // Get the name of the macro by relexing it. - { - FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); - assert(L.isFileID()); - StringRef BufferInfo = L.getBufferData(); - std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); - const char* MacroName = LocInfo.second + BufferInfo.data(); - Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(), - BufferInfo.begin(), MacroName, BufferInfo.end()); - - Token TheTok; - rawLexer.LexFromRawLexer(TheTok); - for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i) - os << MacroName[i]; - } - - os << "':\n"; - - if (!SuppressIndex) { - os << "</td>"; - if (num < max) { - os << "<td><div class=\"PathNav\"><a href=\"#"; - if (num == max - 1) - os << "EndPath"; - else - os << "Path" << (num + 1); - os << "\" title=\"Next event (" - << (num + 1) - << ")\">→</a></div></td>"; - } - - os << "</tr></table>"; - } - - // Within a macro piece. Write out each event. - ProcessMacroPiece(os, *MP, 0); - } - else { - os << html::EscapeText(P.getString()); - - if (!SuppressIndex) { - os << "</td>"; - if (num < max) { - os << "<td><div class=\"PathNav\"><a href=\"#"; - if (num == max - 1) - os << "EndPath"; - else - os << "Path" << (num + 1); - os << "\" title=\"Next event (" - << (num + 1) - << ")\">→</a></div></td>"; - } - - os << "</tr></table>"; - } - } - - os << "</div></td></tr>"; - - // Insert the new html. - unsigned DisplayPos = LineEnd - FileStart; - SourceLocation Loc = - SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); - - R.InsertTextBefore(Loc, os.str()); - - // Now highlight the ranges. - ArrayRef<SourceRange> Ranges = P.getRanges(); - for (const auto &Range : Ranges) { - // If we have already highlighted the range as a pop-up there is no work. - if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) != - PopUpRanges.end()) - continue; - - HighlightRange(R, LPosInfo.first, Range); - } -} - -static void EmitAlphaCounter(raw_ostream &os, unsigned n) { - unsigned x = n % ('z' - 'a'); - n /= 'z' - 'a'; - - if (n > 0) - EmitAlphaCounter(os, n); - - os << char('a' + x); -} - -unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, - const PathDiagnosticMacroPiece& P, - unsigned num) { - for (const auto &subPiece : P.subPieces) { - if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) { - num = ProcessMacroPiece(os, *MP, num); - continue; - } - - if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) { - os << "<div class=\"msg msgEvent\" style=\"width:94%; " - "margin-left:5px\">" - "<table class=\"msgT\"><tr>" - "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">"; - EmitAlphaCounter(os, num++); - os << "</div></td><td valign=\"top\">" - << html::EscapeText(EP->getString()) - << "</td></tr></table></div>\n"; - } - } - - return num; -} - -void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, - SourceRange Range, - const char *HighlightStart, - const char *HighlightEnd) { - SourceManager &SM = R.getSourceMgr(); - const LangOptions &LangOpts = R.getLangOpts(); - - SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin()); - unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart); - - SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd()); - unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd); - - if (EndLineNo < StartLineNo) - return; - - if (SM.getFileID(InstantiationStart) != BugFileID || - SM.getFileID(InstantiationEnd) != BugFileID) - return; - - // Compute the column number of the end. - unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); - unsigned OldEndColNo = EndColNo; - - if (EndColNo) { - // Add in the length of the token, so that we cover multi-char tokens. - EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; - } - - // Highlight the range. Make the span tag the outermost tag for the - // selected range. - - SourceLocation E = - InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); - - html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); -} - -StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { - return R"<<<( -<script type='text/javascript'> -var digitMatcher = new RegExp("[0-9]+"); - -var querySelectorAllArray = function(selector) { - return Array.prototype.slice.call( - document.querySelectorAll(selector)); -} - -document.addEventListener("DOMContentLoaded", function() { - querySelectorAllArray(".PathNav > a").forEach( - function(currentValue, currentIndex) { - var hrefValue = currentValue.getAttribute("href"); - currentValue.onclick = function() { - scrollTo(document.querySelector(hrefValue)); - return false; - }; - }); -}); - -var findNum = function() { - var s = document.querySelector(".selected"); - if (!s || s.id == "EndPath") { - return 0; - } - var out = parseInt(digitMatcher.exec(s.id)[0]); - return out; -}; - -var scrollTo = function(el) { - querySelectorAllArray(".selected").forEach(function(s) { - s.classList.remove("selected"); - }); - el.classList.add("selected"); - window.scrollBy(0, el.getBoundingClientRect().top - - (window.innerHeight / 2)); -} - -var move = function(num, up, numItems) { - if (num == 1 && up || num == numItems - 1 && !up) { - return 0; - } else if (num == 0 && up) { - return numItems - 1; - } else if (num == 0 && !up) { - return 1 % numItems; - } - return up ? num - 1 : num + 1; -} - -var numToId = function(num) { - if (num == 0) { - return document.getElementById("EndPath") - } - return document.getElementById("Path" + num); -}; - -var navigateTo = function(up) { - var numItems = document.querySelectorAll( - ".line > .msgEvent, .line > .msgControl").length; - var currentSelected = findNum(); - var newSelected = move(currentSelected, up, numItems); - var newEl = numToId(newSelected, numItems); - - // Scroll element into center. - scrollTo(newEl); -}; - -window.addEventListener("keydown", function (event) { - if (event.defaultPrevented) { - return; - } - if (event.key == "j") { - navigateTo(/*up=*/false); - } else if (event.key == "k") { - navigateTo(/*up=*/true); - } else { - return; - } - event.preventDefault(); -}, true); -</script> - )<<<"; -} diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp deleted file mode 100644 index 18d8346..0000000 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ /dev/null @@ -1,1384 +0,0 @@ -//===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the PlistDiagnostics object. -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/IssueHash.h" -#include "clang/Analysis/PathDiagnostic.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/PlistSupport.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Basic/Version.h" -#include "clang/CrossTU/CrossTranslationUnit.h" -#include "clang/Frontend/ASTUnit.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Lex/TokenConcatenation.h" -#include "clang/Rewrite/Core/HTMLRewrite.h" -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/Statistic.h" -#include "llvm/Support/Casting.h" -#include <memory> - -using namespace clang; -using namespace ento; -using namespace markup; - -//===----------------------------------------------------------------------===// -// Declarations of helper classes and functions for emitting bug reports in -// plist format. -//===----------------------------------------------------------------------===// - -namespace { - class PlistDiagnostics : public PathDiagnosticConsumer { - PathDiagnosticConsumerOptions DiagOpts; - const std::string OutputFile; - const Preprocessor &PP; - const cross_tu::CrossTranslationUnitContext &CTU; - const bool SupportsCrossFileDiagnostics; - - void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, - const PathPieces &Path); - - public: - PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, - const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU, - bool supportsMultipleFiles); - - ~PlistDiagnostics() override {} - - void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) override; - - StringRef getName() const override { - return "PlistDiagnostics"; - } - - PathGenerationScheme getGenerationScheme() const override { - return Extensive; - } - bool supportsLogicalOpControlFlow() const override { return true; } - bool supportsCrossFileDiagnostics() const override { - return SupportsCrossFileDiagnostics; - } - }; -} // end anonymous namespace - -namespace { - -/// A helper class for emitting a single report. -class PlistPrinter { - const FIDMap& FM; - const Preprocessor &PP; - const cross_tu::CrossTranslationUnitContext &CTU; - llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; - -public: - PlistPrinter(const FIDMap& FM, - const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) - : FM(FM), PP(PP), CTU(CTU) { - } - - void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { - ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); - } - - /// Print the expansions of the collected macro pieces. - /// - /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one - /// is found through a call piece, etc), it's subpieces are reported, and the - /// piece itself is collected. Call this function after the entire bugpath - /// was reported. - void ReportMacroExpansions(raw_ostream &o, unsigned indent); - -private: - void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, - unsigned indent, unsigned depth, bool includeControlFlow, - bool isKeyEvent = false) { - switch (P.getKind()) { - case PathDiagnosticPiece::ControlFlow: - if (includeControlFlow) - ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), indent); - break; - case PathDiagnosticPiece::Call: - ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent, - depth); - break; - case PathDiagnosticPiece::Event: - ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth, - isKeyEvent); - break; - case PathDiagnosticPiece::Macro: - ReportMacroSubPieces(o, cast<PathDiagnosticMacroPiece>(P), indent, - depth); - break; - case PathDiagnosticPiece::Note: - ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent); - break; - case PathDiagnosticPiece::PopUp: - ReportPopUp(o, cast<PathDiagnosticPopUpPiece>(P), indent); - break; - } - } - - void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges, - unsigned indent); - void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); - void EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, unsigned indent); - - void ReportControlFlow(raw_ostream &o, - const PathDiagnosticControlFlowPiece& P, - unsigned indent); - void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, - unsigned indent, unsigned depth, bool isKeyEvent = false); - void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, - unsigned indent, unsigned depth); - void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P, - unsigned indent, unsigned depth); - void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, - unsigned indent); - - void ReportPopUp(raw_ostream &o, const PathDiagnosticPopUpPiece &P, - unsigned indent); -}; - -} // end of anonymous namespace - -namespace { - -struct ExpansionInfo { - std::string MacroName; - std::string Expansion; - ExpansionInfo(std::string N, std::string E) - : MacroName(std::move(N)), Expansion(std::move(E)) {} -}; - -} // end of anonymous namespace - -/// Print coverage information to output stream {@code o}. -/// May modify the used list of files {@code Fids} by inserting new ones. -static void printCoverage(const PathDiagnostic *D, - unsigned InputIndentLevel, - SmallVectorImpl<FileID> &Fids, - FIDMap &FM, - llvm::raw_fd_ostream &o); - -static ExpansionInfo -getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU); - -//===----------------------------------------------------------------------===// -// Methods of PlistPrinter. -//===----------------------------------------------------------------------===// - -void PlistPrinter::EmitRanges(raw_ostream &o, - const ArrayRef<SourceRange> Ranges, - unsigned indent) { - - if (Ranges.empty()) - return; - - Indent(o, indent) << "<key>ranges</key>\n"; - Indent(o, indent) << "<array>\n"; - ++indent; - - const SourceManager &SM = PP.getSourceManager(); - const LangOptions &LangOpts = PP.getLangOpts(); - - for (auto &R : Ranges) - EmitRange(o, SM, - Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), - FM, indent + 1); - --indent; - Indent(o, indent) << "</array>\n"; -} - -void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, - unsigned indent) { - // Output the text. - assert(!Message.empty()); - Indent(o, indent) << "<key>extended_message</key>\n"; - Indent(o, indent); - EmitString(o, Message) << '\n'; - - // Output the short text. - // FIXME: Really use a short string. - Indent(o, indent) << "<key>message</key>\n"; - Indent(o, indent); - EmitString(o, Message) << '\n'; -} - -void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, - unsigned indent) { - if (fixits.size() == 0) - return; - - const SourceManager &SM = PP.getSourceManager(); - const LangOptions &LangOpts = PP.getLangOpts(); - - Indent(o, indent) << "<key>fixits</key>\n"; - Indent(o, indent) << "<array>\n"; - for (const auto &fixit : fixits) { - assert(!fixit.isNull()); - // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. - assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!"); - assert(!fixit.BeforePreviousInsertions && "Not implemented yet!"); - Indent(o, indent) << " <dict>\n"; - Indent(o, indent) << " <key>remove_range</key>\n"; - EmitRange(o, SM, Lexer::getAsCharRange(fixit.RemoveRange, SM, LangOpts), - FM, indent + 2); - Indent(o, indent) << " <key>insert_string</key>"; - EmitString(o, fixit.CodeToInsert); - o << "\n"; - Indent(o, indent) << " </dict>\n"; - } - Indent(o, indent) << "</array>\n"; -} - -void PlistPrinter::ReportControlFlow(raw_ostream &o, - const PathDiagnosticControlFlowPiece& P, - unsigned indent) { - - const SourceManager &SM = PP.getSourceManager(); - const LangOptions &LangOpts = PP.getLangOpts(); - - Indent(o, indent) << "<dict>\n"; - ++indent; - - Indent(o, indent) << "<key>kind</key><string>control</string>\n"; - - // Emit edges. - Indent(o, indent) << "<key>edges</key>\n"; - ++indent; - Indent(o, indent) << "<array>\n"; - ++indent; - for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); - I!=E; ++I) { - Indent(o, indent) << "<dict>\n"; - ++indent; - - // Make the ranges of the start and end point self-consistent with adjacent edges - // by forcing to use only the beginning of the range. This simplifies the layout - // logic for clients. - Indent(o, indent) << "<key>start</key>\n"; - SourceRange StartEdge( - SM.getExpansionLoc(I->getStart().asRange().getBegin())); - EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM, - indent + 1); - - Indent(o, indent) << "<key>end</key>\n"; - SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin())); - EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM, - indent + 1); - - --indent; - Indent(o, indent) << "</dict>\n"; - } - --indent; - Indent(o, indent) << "</array>\n"; - --indent; - - // Output any helper text. - const auto &s = P.getString(); - if (!s.empty()) { - Indent(o, indent) << "<key>alternate</key>"; - EmitString(o, s) << '\n'; - } - - assert(P.getFixits().size() == 0 && - "Fixits on constrol flow pieces are not implemented yet!"); - - --indent; - Indent(o, indent) << "</dict>\n"; -} - -void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, - unsigned indent, unsigned depth, - bool isKeyEvent) { - - const SourceManager &SM = PP.getSourceManager(); - - Indent(o, indent) << "<dict>\n"; - ++indent; - - Indent(o, indent) << "<key>kind</key><string>event</string>\n"; - - if (isKeyEvent) { - Indent(o, indent) << "<key>key_event</key><true/>\n"; - } - - // Output the location. - FullSourceLoc L = P.getLocation().asLocation(); - - Indent(o, indent) << "<key>location</key>\n"; - EmitLocation(o, SM, L, FM, indent); - - // Output the ranges (if any). - ArrayRef<SourceRange> Ranges = P.getRanges(); - EmitRanges(o, Ranges, indent); - - // Output the call depth. - Indent(o, indent) << "<key>depth</key>"; - EmitInteger(o, depth) << '\n'; - - // Output the text. - EmitMessage(o, P.getString(), indent); - - // Output the fixits. - EmitFixits(o, P.getFixits(), indent); - - // Finish up. - --indent; - Indent(o, indent); o << "</dict>\n"; -} - -void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, - unsigned indent, - unsigned depth) { - - if (auto callEnter = P.getCallEnterEvent()) - ReportPiece(o, *callEnter, indent, depth, /*includeControlFlow*/ true, - P.isLastInMainSourceFile()); - - - ++depth; - - if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) - ReportPiece(o, *callEnterWithinCaller, indent, depth, - /*includeControlFlow*/ true); - - for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) - ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ true); - - --depth; - - if (auto callExit = P.getCallExitEvent()) - ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true); - - assert(P.getFixits().size() == 0 && - "Fixits on call pieces are not implemented yet!"); -} - -void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, - const PathDiagnosticMacroPiece& P, - unsigned indent, unsigned depth) { - MacroPieces.push_back(&P); - - for (PathPieces::const_iterator I = P.subPieces.begin(), - E = P.subPieces.end(); - I != E; ++I) { - ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false); - } - - assert(P.getFixits().size() == 0 && - "Fixits on constrol flow pieces are not implemented yet!"); -} - -void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { - - for (const PathDiagnosticMacroPiece *P : MacroPieces) { - const SourceManager &SM = PP.getSourceManager(); - ExpansionInfo EI = getExpandedMacro(P->getLocation().asLocation(), PP, CTU); - - Indent(o, indent) << "<dict>\n"; - ++indent; - - // Output the location. - FullSourceLoc L = P->getLocation().asLocation(); - - Indent(o, indent) << "<key>location</key>\n"; - EmitLocation(o, SM, L, FM, indent); - - // Output the ranges (if any). - ArrayRef<SourceRange> Ranges = P->getRanges(); - EmitRanges(o, Ranges, indent); - - // Output the macro name. - Indent(o, indent) << "<key>name</key>"; - EmitString(o, EI.MacroName) << '\n'; - - // Output what it expands into. - Indent(o, indent) << "<key>expansion</key>"; - EmitString(o, EI.Expansion) << '\n'; - - // Finish up. - --indent; - Indent(o, indent); - o << "</dict>\n"; - } -} - -void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, - unsigned indent) { - - const SourceManager &SM = PP.getSourceManager(); - - Indent(o, indent) << "<dict>\n"; - ++indent; - - // Output the location. - FullSourceLoc L = P.getLocation().asLocation(); - - Indent(o, indent) << "<key>location</key>\n"; - EmitLocation(o, SM, L, FM, indent); - - // Output the ranges (if any). - ArrayRef<SourceRange> Ranges = P.getRanges(); - EmitRanges(o, Ranges, indent); - - // Output the text. - EmitMessage(o, P.getString(), indent); - - // Output the fixits. - EmitFixits(o, P.getFixits(), indent); - - // Finish up. - --indent; - Indent(o, indent); o << "</dict>\n"; -} - -void PlistPrinter::ReportPopUp(raw_ostream &o, - const PathDiagnosticPopUpPiece &P, - unsigned indent) { - const SourceManager &SM = PP.getSourceManager(); - - Indent(o, indent) << "<dict>\n"; - ++indent; - - Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n"; - - // Output the location. - FullSourceLoc L = P.getLocation().asLocation(); - - Indent(o, indent) << "<key>location</key>\n"; - EmitLocation(o, SM, L, FM, indent); - - // Output the ranges (if any). - ArrayRef<SourceRange> Ranges = P.getRanges(); - EmitRanges(o, Ranges, indent); - - // Output the text. - EmitMessage(o, P.getString(), indent); - - assert(P.getFixits().size() == 0 && - "Fixits on pop-up pieces are not implemented yet!"); - - // Finish up. - --indent; - Indent(o, indent) << "</dict>\n"; -} - -//===----------------------------------------------------------------------===// -// Static function definitions. -//===----------------------------------------------------------------------===// - -/// Print coverage information to output stream {@code o}. -/// May modify the used list of files {@code Fids} by inserting new ones. -static void printCoverage(const PathDiagnostic *D, - unsigned InputIndentLevel, - SmallVectorImpl<FileID> &Fids, - FIDMap &FM, - llvm::raw_fd_ostream &o) { - unsigned IndentLevel = InputIndentLevel; - - Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n"; - Indent(o, IndentLevel) << "<dict>\n"; - IndentLevel++; - - // Mapping from file IDs to executed lines. - const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); - for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) { - unsigned FileKey = AddFID(FM, Fids, I->first); - Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n"; - Indent(o, IndentLevel) << "<array>\n"; - IndentLevel++; - for (unsigned LineNo : I->second) { - Indent(o, IndentLevel); - EmitInteger(o, LineNo) << "\n"; - } - IndentLevel--; - Indent(o, IndentLevel) << "</array>\n"; - } - IndentLevel--; - Indent(o, IndentLevel) << "</dict>\n"; - - assert(IndentLevel == InputIndentLevel); -} - -//===----------------------------------------------------------------------===// -// Methods of PlistDiagnostics. -//===----------------------------------------------------------------------===// - -PlistDiagnostics::PlistDiagnostics( - PathDiagnosticConsumerOptions DiagOpts, const std::string &output, - const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, - bool supportsMultipleFiles) - : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), - SupportsCrossFileDiagnostics(supportsMultipleFiles) { - // FIXME: Will be used by a later planned change. - (void)this->CTU; -} - -void ento::createPlistDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - - // TODO: Emit an error here. - if (OutputFile.empty()) - return; - - C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, - /*supportsMultipleFiles=*/false)); - createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, - PP, CTU); -} - -void ento::createPlistMultiFileDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - - // TODO: Emit an error here. - if (OutputFile.empty()) - return; - - C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, - /*supportsMultipleFiles=*/true)); - createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, - PP, CTU); -} - -void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, - const PathPieces &Path) { - PlistPrinter Printer(FM, PP, CTU); - assert(std::is_partitioned(Path.begin(), Path.end(), - [](const PathDiagnosticPieceRef &E) { - return E->getKind() == PathDiagnosticPiece::Note; - }) && - "PathDiagnostic is not partitioned so that notes precede the rest"); - - PathPieces::const_iterator FirstNonNote = std::partition_point( - Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) { - return E->getKind() == PathDiagnosticPiece::Note; - }); - - PathPieces::const_iterator I = Path.begin(); - - if (FirstNonNote != Path.begin()) { - o << " <key>notes</key>\n" - " <array>\n"; - - for (; I != FirstNonNote; ++I) - Printer.ReportDiag(o, **I); - - o << " </array>\n"; - } - - o << " <key>path</key>\n"; - - o << " <array>\n"; - - for (PathPieces::const_iterator E = Path.end(); I != E; ++I) - Printer.ReportDiag(o, **I); - - o << " </array>\n"; - - if (!DiagOpts.ShouldDisplayMacroExpansions) - return; - - o << " <key>macro_expansions</key>\n" - " <array>\n"; - Printer.ReportMacroExpansions(o, /* indent */ 4); - o << " </array>\n"; -} - -void PlistDiagnostics::FlushDiagnosticsImpl( - std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) { - // Build up a set of FIDs that we use by scanning the locations and - // ranges of the diagnostics. - FIDMap FM; - SmallVector<FileID, 10> Fids; - const SourceManager& SM = PP.getSourceManager(); - const LangOptions &LangOpts = PP.getLangOpts(); - - auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { - AddFID(FM, Fids, SM, Piece.getLocation().asLocation()); - ArrayRef<SourceRange> Ranges = Piece.getRanges(); - for (const SourceRange &Range : Ranges) { - AddFID(FM, Fids, SM, Range.getBegin()); - AddFID(FM, Fids, SM, Range.getEnd()); - } - }; - - for (const PathDiagnostic *D : Diags) { - - SmallVector<const PathPieces *, 5> WorkList; - WorkList.push_back(&D->path); - - while (!WorkList.empty()) { - const PathPieces &Path = *WorkList.pop_back_val(); - - for (const auto &Iter : Path) { - const PathDiagnosticPiece &Piece = *Iter; - AddPieceFID(Piece); - - if (const PathDiagnosticCallPiece *Call = - dyn_cast<PathDiagnosticCallPiece>(&Piece)) { - if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) - AddPieceFID(*CallEnterWithin); - - if (auto CallEnterEvent = Call->getCallEnterEvent()) - AddPieceFID(*CallEnterEvent); - - WorkList.push_back(&Call->path); - } else if (const PathDiagnosticMacroPiece *Macro = - dyn_cast<PathDiagnosticMacroPiece>(&Piece)) { - WorkList.push_back(&Macro->subPieces); - } - } - } - } - - // Open the file. - std::error_code EC; - llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_Text); - if (EC) { - llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; - return; - } - - EmitPlistHeader(o); - - // Write the root object: a <dict> containing... - // - "clang_version", the string representation of clang version - // - "files", an <array> mapping from FIDs to file names - // - "diagnostics", an <array> containing the path diagnostics - o << "<dict>\n" << - " <key>clang_version</key>\n"; - EmitString(o, getClangFullVersion()) << '\n'; - o << " <key>diagnostics</key>\n" - " <array>\n"; - - for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), - DE = Diags.end(); DI!=DE; ++DI) { - - o << " <dict>\n"; - - const PathDiagnostic *D = *DI; - printBugPath(o, FM, D->path); - - // Output the bug type and bug category. - o << " <key>description</key>"; - EmitString(o, D->getShortDescription()) << '\n'; - o << " <key>category</key>"; - EmitString(o, D->getCategory()) << '\n'; - o << " <key>type</key>"; - EmitString(o, D->getBugType()) << '\n'; - o << " <key>check_name</key>"; - EmitString(o, D->getCheckerName()) << '\n'; - - o << " <!-- This hash is experimental and going to change! -->\n"; - o << " <key>issue_hash_content_of_line_in_context</key>"; - PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); - FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid() - ? UPDLoc.asLocation() - : D->getLocation().asLocation()), - SM); - const Decl *DeclWithIssue = D->getDeclWithIssue(); - EmitString(o, getIssueHash(L, D->getCheckerName(), D->getBugType(), - DeclWithIssue, LangOpts)) - << '\n'; - - // Output information about the semantic context where - // the issue occurred. - if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { - // FIXME: handle blocks, which have no name. - if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { - StringRef declKind; - switch (ND->getKind()) { - case Decl::CXXRecord: - declKind = "C++ class"; - break; - case Decl::CXXMethod: - declKind = "C++ method"; - break; - case Decl::ObjCMethod: - declKind = "Objective-C method"; - break; - case Decl::Function: - declKind = "function"; - break; - default: - break; - } - if (!declKind.empty()) { - const std::string &declName = ND->getDeclName().getAsString(); - o << " <key>issue_context_kind</key>"; - EmitString(o, declKind) << '\n'; - o << " <key>issue_context</key>"; - EmitString(o, declName) << '\n'; - } - - // Output the bug hash for issue unique-ing. Currently, it's just an - // offset from the beginning of the function. - if (const Stmt *Body = DeclWithIssue->getBody()) { - - // If the bug uniqueing location exists, use it for the hash. - // For example, this ensures that two leaks reported on the same line - // will have different issue_hashes and that the hash will identify - // the leak location even after code is added between the allocation - // site and the end of scope (leak report location). - if (UPDLoc.isValid()) { - FullSourceLoc UFunL( - SM.getExpansionLoc( - D->getUniqueingDecl()->getBody()->getBeginLoc()), - SM); - o << " <key>issue_hash_function_offset</key><string>" - << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() - << "</string>\n"; - - // Otherwise, use the location on which the bug is reported. - } else { - FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM); - o << " <key>issue_hash_function_offset</key><string>" - << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() - << "</string>\n"; - } - - } - } - } - - // Output the location of the bug. - o << " <key>location</key>\n"; - EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2); - - // Output the diagnostic to the sub-diagnostic client, if any. - if (!filesMade->empty()) { - StringRef lastName; - PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D); - if (files) { - for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), - CE = files->end(); CI != CE; ++CI) { - StringRef newName = CI->first; - if (newName != lastName) { - if (!lastName.empty()) { - o << " </array>\n"; - } - lastName = newName; - o << " <key>" << lastName << "_files</key>\n"; - o << " <array>\n"; - } - o << " <string>" << CI->second << "</string>\n"; - } - o << " </array>\n"; - } - } - - printCoverage(D, /*IndentLevel=*/2, Fids, FM, o); - - // Close up the entry. - o << " </dict>\n"; - } - - o << " </array>\n"; - - o << " <key>files</key>\n" - " <array>\n"; - for (FileID FID : Fids) - EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n'; - o << " </array>\n"; - - if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { - o << " <key>statistics</key>\n"; - std::string stats; - llvm::raw_string_ostream os(stats); - llvm::PrintStatisticsJSON(os); - os.flush(); - EmitString(o, html::EscapeText(stats)) << '\n'; - } - - // Finish. - o << "</dict>\n</plist>\n"; -} - -//===----------------------------------------------------------------------===// -// Declarations of helper functions and data structures for expanding macros. -//===----------------------------------------------------------------------===// - -namespace { - -using ArgTokensTy = llvm::SmallVector<Token, 2>; - -} // end of anonymous namespace - -LLVM_DUMP_METHOD static void dumpArgTokensToStream(llvm::raw_ostream &Out, - const Preprocessor &PP, - const ArgTokensTy &Toks); - -namespace { -/// Maps unexpanded macro parameters to expanded arguments. A macro argument may -/// need to expanded further when it is nested inside another macro. -class MacroParamMap : public std::map<const IdentifierInfo *, ArgTokensTy> { -public: - void expandFromPrevMacro(const MacroParamMap &Super); - - LLVM_DUMP_METHOD void dump(const Preprocessor &PP) const { - dumpToStream(llvm::errs(), PP); - } - - LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &Out, - const Preprocessor &PP) const; -}; - -struct MacroExpansionInfo { - std::string Name; - const MacroInfo *MI = nullptr; - MacroParamMap ParamMap; - - MacroExpansionInfo(std::string N, const MacroInfo *MI, MacroParamMap M) - : Name(std::move(N)), MI(MI), ParamMap(std::move(M)) {} -}; - -class TokenPrinter { - llvm::raw_ostream &OS; - const Preprocessor &PP; - - Token PrevTok, PrevPrevTok; - TokenConcatenation ConcatInfo; - -public: - TokenPrinter(llvm::raw_ostream &OS, const Preprocessor &PP) - : OS(OS), PP(PP), ConcatInfo(PP) { - PrevTok.setKind(tok::unknown); - PrevPrevTok.setKind(tok::unknown); - } - - void printToken(const Token &Tok); -}; - -/// Wrapper around a Lexer object that can lex tokens one-by-one. Its possible -/// to "inject" a range of tokens into the stream, in which case the next token -/// is retrieved from the next element of the range, until the end of the range -/// is reached. -class TokenStream { -public: - TokenStream(SourceLocation ExpanLoc, const SourceManager &SM, - const LangOptions &LangOpts) - : ExpanLoc(ExpanLoc) { - FileID File; - unsigned Offset; - std::tie(File, Offset) = SM.getDecomposedLoc(ExpanLoc); - const llvm::MemoryBuffer *MB = SM.getBuffer(File); - const char *MacroNameTokenPos = MB->getBufferStart() + Offset; - - RawLexer = std::make_unique<Lexer>(SM.getLocForStartOfFile(File), LangOpts, - MB->getBufferStart(), MacroNameTokenPos, - MB->getBufferEnd()); - } - - void next(Token &Result) { - if (CurrTokenIt == TokenRange.end()) { - RawLexer->LexFromRawLexer(Result); - return; - } - Result = *CurrTokenIt; - CurrTokenIt++; - } - - void injectRange(const ArgTokensTy &Range) { - TokenRange = Range; - CurrTokenIt = TokenRange.begin(); - } - - std::unique_ptr<Lexer> RawLexer; - ArgTokensTy TokenRange; - ArgTokensTy::iterator CurrTokenIt = TokenRange.begin(); - SourceLocation ExpanLoc; -}; - -} // end of anonymous namespace - -/// The implementation method of getMacroExpansion: It prints the expansion of -/// a macro to \p Printer, and returns with the name of the macro. -/// -/// Since macros can be nested in one another, this function may call itself -/// recursively. -/// -/// Unfortunately, macro arguments have to expanded manually. To understand why, -/// observe the following example: -/// -/// #define PRINT(x) print(x) -/// #define DO_SOMETHING(str) PRINT(str) -/// -/// DO_SOMETHING("Cute panda cubs."); -/// -/// As we expand the last line, we'll immediately replace PRINT(str) with -/// print(x). The information that both 'str' and 'x' refers to the same string -/// is an information we have to forward, hence the argument \p PrevParamMap. -/// -/// To avoid infinite recursion we maintain the already processed tokens in -/// a set. This is carried as a parameter through the recursive calls. The set -/// is extended with the currently processed token and after processing it, the -/// token is removed. If the token is already in the set, then recursion stops: -/// -/// #define f(y) x -/// #define x f(x) -static std::string getMacroNameAndPrintExpansion( - TokenPrinter &Printer, SourceLocation MacroLoc, const Preprocessor &PP, - const MacroParamMap &PrevParamMap, - llvm::SmallPtrSet<IdentifierInfo *, 8> &AlreadyProcessedTokens); - -/// Retrieves the name of the macro and what it's parameters expand into -/// at \p ExpanLoc. -/// -/// For example, for the following macro expansion: -/// -/// #define SET_TO_NULL(x) x = 0 -/// #define NOT_SUSPICIOUS(a) \ -/// { \ -/// int b = 0; \ -/// } \ -/// SET_TO_NULL(a) -/// -/// int *ptr = new int(4); -/// NOT_SUSPICIOUS(&ptr); -/// *ptr = 5; -/// -/// When \p ExpanLoc references the last line, the macro name "NOT_SUSPICIOUS" -/// and the MacroArgMap map { (a, &ptr) } will be returned. -/// -/// When \p ExpanLoc references "SET_TO_NULL(a)" within the definition of -/// "NOT_SUSPICOUS", the macro name "SET_TO_NULL" and the MacroArgMap map -/// { (x, a) } will be returned. -static MacroExpansionInfo -getMacroExpansionInfo(const MacroParamMap &PrevParamMap, - SourceLocation ExpanLoc, const Preprocessor &PP); - -/// Retrieves the ')' token that matches '(' \p It points to. -static MacroInfo::tokens_iterator getMatchingRParen( - MacroInfo::tokens_iterator It, - MacroInfo::tokens_iterator End); - -/// Retrieves the macro info for \p II refers to at \p Loc. This is important -/// because macros can be redefined or undefined. -static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, - const SourceManager &SM, - const IdentifierInfo *II, - SourceLocation Loc); - -//===----------------------------------------------------------------------===// -// Definitions of helper functions and methods for expanding macros. -//===----------------------------------------------------------------------===// - -static ExpansionInfo -getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - - const Preprocessor *PPToUse = &PP; - if (auto LocAndUnit = CTU.getImportedFromSourceLocation(MacroLoc)) { - MacroLoc = LocAndUnit->first; - PPToUse = &LocAndUnit->second->getPreprocessor(); - } - - llvm::SmallString<200> ExpansionBuf; - llvm::raw_svector_ostream OS(ExpansionBuf); - TokenPrinter Printer(OS, *PPToUse); - llvm::SmallPtrSet<IdentifierInfo*, 8> AlreadyProcessedTokens; - - std::string MacroName = getMacroNameAndPrintExpansion( - Printer, MacroLoc, *PPToUse, MacroParamMap{}, AlreadyProcessedTokens); - return {MacroName, std::string(OS.str())}; -} - -static std::string getMacroNameAndPrintExpansion( - TokenPrinter &Printer, SourceLocation MacroLoc, const Preprocessor &PP, - const MacroParamMap &PrevParamMap, - llvm::SmallPtrSet<IdentifierInfo *, 8> &AlreadyProcessedTokens) { - - const SourceManager &SM = PP.getSourceManager(); - - MacroExpansionInfo MExpInfo = - getMacroExpansionInfo(PrevParamMap, SM.getExpansionLoc(MacroLoc), PP); - IdentifierInfo *MacroNameII = PP.getIdentifierInfo(MExpInfo.Name); - - // TODO: If the macro definition contains another symbol then this function is - // called recursively. In case this symbol is the one being defined, it will - // be an infinite recursion which is stopped by this "if" statement. However, - // in this case we don't get the full expansion text in the Plist file. See - // the test file where "value" is expanded to "garbage_" instead of - // "garbage_value". - if (!AlreadyProcessedTokens.insert(MacroNameII).second) - return MExpInfo.Name; - - if (!MExpInfo.MI) - return MExpInfo.Name; - - // Manually expand its arguments from the previous macro. - MExpInfo.ParamMap.expandFromPrevMacro(PrevParamMap); - - // Iterate over the macro's tokens and stringify them. - for (auto It = MExpInfo.MI->tokens_begin(), E = MExpInfo.MI->tokens_end(); - It != E; ++It) { - Token T = *It; - - // If this token is not an identifier, we only need to print it. - if (T.isNot(tok::identifier)) { - Printer.printToken(T); - continue; - } - - const auto *II = T.getIdentifierInfo(); - assert(II && - "This token is an identifier but has no IdentifierInfo!"); - - // If this token is a macro that should be expanded inside the current - // macro. - if (getMacroInfoForLocation(PP, SM, II, T.getLocation())) { - getMacroNameAndPrintExpansion(Printer, T.getLocation(), PP, - MExpInfo.ParamMap, AlreadyProcessedTokens); - - // If this is a function-like macro, skip its arguments, as - // getExpandedMacro() already printed them. If this is the case, let's - // first jump to the '(' token. - auto N = std::next(It); - if (N != E && N->is(tok::l_paren)) - It = getMatchingRParen(++It, E); - continue; - } - - // If this token is the current macro's argument, we should expand it. - auto ParamToArgIt = MExpInfo.ParamMap.find(II); - if (ParamToArgIt != MExpInfo.ParamMap.end()) { - for (MacroInfo::tokens_iterator ArgIt = ParamToArgIt->second.begin(), - ArgEnd = ParamToArgIt->second.end(); - ArgIt != ArgEnd; ++ArgIt) { - - // These tokens may still be macros, if that is the case, handle it the - // same way we did above. - const auto *ArgII = ArgIt->getIdentifierInfo(); - if (!ArgII) { - Printer.printToken(*ArgIt); - continue; - } - - const auto *MI = PP.getMacroInfo(ArgII); - if (!MI) { - Printer.printToken(*ArgIt); - continue; - } - - getMacroNameAndPrintExpansion(Printer, ArgIt->getLocation(), PP, - MExpInfo.ParamMap, - AlreadyProcessedTokens); - // Peek the next token if it is a tok::l_paren. This way we can decide - // if this is the application or just a reference to a function maxro - // symbol: - // - // #define apply(f) ... - // #define func(x) ... - // apply(func) - // apply(func(42)) - auto N = std::next(ArgIt); - if (N != ArgEnd && N->is(tok::l_paren)) - ArgIt = getMatchingRParen(++ArgIt, ArgEnd); - } - continue; - } - - // If control reached here, then this token isn't a macro identifier, nor an - // unexpanded macro argument that we need to handle, print it. - Printer.printToken(T); - } - - AlreadyProcessedTokens.erase(MacroNameII); - - return MExpInfo.Name; -} - -static MacroExpansionInfo -getMacroExpansionInfo(const MacroParamMap &PrevParamMap, - SourceLocation ExpanLoc, const Preprocessor &PP) { - - const SourceManager &SM = PP.getSourceManager(); - const LangOptions &LangOpts = PP.getLangOpts(); - - // First, we create a Lexer to lex *at the expansion location* the tokens - // referring to the macro's name and its arguments. - TokenStream TStream(ExpanLoc, SM, LangOpts); - - // Acquire the macro's name. - Token TheTok; - TStream.next(TheTok); - - std::string MacroName = PP.getSpelling(TheTok); - - const auto *II = PP.getIdentifierInfo(MacroName); - assert(II && "Failed to acquire the IdentifierInfo for the macro!"); - - const MacroInfo *MI = getMacroInfoForLocation(PP, SM, II, ExpanLoc); - // assert(MI && "The macro must've been defined at it's expansion location!"); - // - // We should always be able to obtain the MacroInfo in a given TU, but if - // we're running the analyzer with CTU, the Preprocessor won't contain the - // directive history (or anything for that matter) from another TU. - // TODO: assert when we're not running with CTU. - if (!MI) - return { MacroName, MI, {} }; - - // Acquire the macro's arguments at the expansion point. - // - // The rough idea here is to lex from the first left parentheses to the last - // right parentheses, and map the macro's parameter to what they will be - // expanded to. A macro argument may contain several token (like '3 + 4'), so - // we'll lex until we find a tok::comma or tok::r_paren, at which point we - // start lexing the next argument or finish. - ArrayRef<const IdentifierInfo *> MacroParams = MI->params(); - if (MacroParams.empty()) - return { MacroName, MI, {} }; - - TStream.next(TheTok); - // When this is a token which expands to another macro function then its - // parentheses are not at its expansion locaiton. For example: - // - // #define foo(x) int bar() { return x; } - // #define apply_zero(f) f(0) - // apply_zero(foo) - // ^ - // This is not a tok::l_paren, but foo is a function. - if (TheTok.isNot(tok::l_paren)) - return { MacroName, MI, {} }; - - MacroParamMap ParamMap; - - // When the argument is a function call, like - // CALL_FN(someFunctionName(param1, param2)) - // we will find tok::l_paren, tok::r_paren, and tok::comma that do not divide - // actual macro arguments, or do not represent the macro argument's closing - // parentheses, so we'll count how many parentheses aren't closed yet. - // If ParanthesesDepth - // * = 0, then there are no more arguments to lex. - // * = 1, then if we find a tok::comma, we can start lexing the next arg. - // * > 1, then tok::comma is a part of the current arg. - int ParenthesesDepth = 1; - - // If we encounter the variadic arg, we will lex until the closing - // tok::r_paren, even if we lex a tok::comma and ParanthesesDepth == 1. - const IdentifierInfo *VariadicParamII = PP.getIdentifierInfo("__VA_ARGS__"); - if (MI->isGNUVarargs()) { - // If macro uses GNU-style variadic args, the param name is user-supplied, - // an not "__VA_ARGS__". E.g.: - // #define FOO(a, b, myvargs...) - // In this case, just use the last parameter: - VariadicParamII = *(MacroParams.rbegin()); - } - - for (const IdentifierInfo *CurrParamII : MacroParams) { - MacroParamMap::mapped_type ArgTokens; - - // One could also simply not supply a single argument to __VA_ARGS__ -- this - // results in a preprocessor warning, but is not an error: - // #define VARIADIC(ptr, ...) \ - // someVariadicTemplateFunction(__VA_ARGS__) - // - // int *ptr; - // VARIADIC(ptr); // Note that there are no commas, this isn't just an - // // empty parameter -- there are no parameters for '...'. - // In any other case, ParenthesesDepth mustn't be 0 here. - if (ParenthesesDepth != 0) { - - // Lex the first token of the next macro parameter. - TStream.next(TheTok); - - while (CurrParamII == VariadicParamII || ParenthesesDepth != 1 || - !TheTok.is(tok::comma)) { - assert(TheTok.isNot(tok::eof) && - "EOF encountered while looking for expanded macro args!"); - - if (TheTok.is(tok::l_paren)) - ++ParenthesesDepth; - - if (TheTok.is(tok::r_paren)) - --ParenthesesDepth; - - if (ParenthesesDepth == 0) - break; - - if (TheTok.is(tok::raw_identifier)) { - PP.LookUpIdentifierInfo(TheTok); - // This token is a variadic parameter: - // - // #define PARAMS_RESOLVE_TO_VA_ARGS(i, fmt) foo(i, fmt); \ - // i = 0; - // #define DISPATCH(...) \ - // PARAMS_RESOLVE_TO_VA_ARGS(__VA_ARGS__); - // // ^~~~~~~~~~~ Variadic parameter here - // - // void multipleParamsResolveToVA_ARGS(void) { - // int x = 1; - // DISPATCH(x, "LF1M healer"); // Multiple arguments are mapped to - // // a single __VA_ARGS__ parameter. - // (void)(10 / x); - // } - // - // We will stumble across this while trying to expand - // PARAMS_RESOLVE_TO_VA_ARGS. By this point, we already noted during - // the processing of DISPATCH what __VA_ARGS__ maps to, so we'll - // retrieve the next series of tokens from that. - if (TheTok.getIdentifierInfo() == VariadicParamII) { - TStream.injectRange(PrevParamMap.at(VariadicParamII)); - TStream.next(TheTok); - continue; - } - } - - ArgTokens.push_back(TheTok); - TStream.next(TheTok); - } - } else { - assert(CurrParamII == VariadicParamII && - "No more macro arguments are found, but the current parameter " - "isn't the variadic arg!"); - } - - ParamMap.emplace(CurrParamII, std::move(ArgTokens)); - } - - assert(TheTok.is(tok::r_paren) && - "Expanded macro argument acquisition failed! After the end of the loop" - " this token should be ')'!"); - - return {MacroName, MI, ParamMap}; -} - -static MacroInfo::tokens_iterator getMatchingRParen( - MacroInfo::tokens_iterator It, - MacroInfo::tokens_iterator End) { - - assert(It->is(tok::l_paren) && "This token should be '('!"); - - // Skip until we find the closing ')'. - int ParenthesesDepth = 1; - while (ParenthesesDepth != 0) { - ++It; - - assert(It->isNot(tok::eof) && - "Encountered EOF while attempting to skip macro arguments!"); - assert(It != End && - "End of the macro definition reached before finding ')'!"); - - if (It->is(tok::l_paren)) - ++ParenthesesDepth; - - if (It->is(tok::r_paren)) - --ParenthesesDepth; - } - return It; -} - -static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, - const SourceManager &SM, - const IdentifierInfo *II, - SourceLocation Loc) { - - const MacroDirective *MD = PP.getLocalMacroDirectiveHistory(II); - if (!MD) - return nullptr; - - return MD->findDirectiveAtLoc(Loc, SM).getMacroInfo(); -} - -void MacroParamMap::expandFromPrevMacro(const MacroParamMap &Super) { - - for (value_type &Pair : *this) { - ArgTokensTy &CurrArgTokens = Pair.second; - - // For each token in the expanded macro argument. - auto It = CurrArgTokens.begin(); - while (It != CurrArgTokens.end()) { - if (It->isNot(tok::identifier)) { - ++It; - continue; - } - - const auto *II = It->getIdentifierInfo(); - assert(II); - - // Is this an argument that "Super" expands further? - if (!Super.count(II)) { - ++It; - continue; - } - - const ArgTokensTy &SuperArgTokens = Super.at(II); - - It = CurrArgTokens.insert(It, SuperArgTokens.begin(), - SuperArgTokens.end()); - std::advance(It, SuperArgTokens.size()); - It = CurrArgTokens.erase(It); - } - } -} - -void MacroParamMap::dumpToStream(llvm::raw_ostream &Out, - const Preprocessor &PP) const { - for (const std::pair<const IdentifierInfo *, ArgTokensTy> Pair : *this) { - Out << Pair.first->getName() << " -> "; - dumpArgTokensToStream(Out, PP, Pair.second); - Out << '\n'; - } -} - -static void dumpArgTokensToStream(llvm::raw_ostream &Out, - const Preprocessor &PP, - const ArgTokensTy &Toks) { - TokenPrinter Printer(Out, PP); - for (Token Tok : Toks) - Printer.printToken(Tok); -} - -void TokenPrinter::printToken(const Token &Tok) { - // TODO: Handle GNU extensions where hash and hashhash occurs right before - // __VA_ARGS__. - // cppreference.com: "some compilers offer an extension that allows ## to - // appear after a comma and before __VA_ARGS__, in which case the ## does - // nothing when the variable arguments are present, but removes the comma when - // the variable arguments are not present: this makes it possible to define - // macros such as fprintf (stderr, format, ##__VA_ARGS__)" - // FIXME: Handle named variadic macro parameters (also a GNU extension). - - // If this is the first token to be printed, don't print space. - if (PrevTok.isNot(tok::unknown)) { - // If the tokens were already space separated, or if they must be to avoid - // them being implicitly pasted, add a space between them. - if(Tok.hasLeadingSpace() || ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, - Tok)) { - // AvoidConcat doesn't check for ##, don't print a space around it. - if (PrevTok.isNot(tok::hashhash) && Tok.isNot(tok::hashhash)) { - OS << ' '; - } - } - } - - if (!Tok.isOneOf(tok::hash, tok::hashhash)) { - if (PrevTok.is(tok::hash)) - OS << '\"' << PP.getSpelling(Tok) << '\"'; - else - OS << PP.getSpelling(Tok); - } - - PrevPrevTok = PrevTok; - PrevTok = Tok; -} diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp deleted file mode 100644 index 9abc5de..0000000 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ /dev/null @@ -1,400 +0,0 @@ -//===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the SarifDiagnostics object. -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/PathDiagnostic.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/Version.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/ConvertUTF.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/Path.h" - -using namespace llvm; -using namespace clang; -using namespace ento; - -namespace { -class SarifDiagnostics : public PathDiagnosticConsumer { - std::string OutputFile; - const LangOptions &LO; - -public: - SarifDiagnostics(const std::string &Output, const LangOptions &LO) - : OutputFile(Output), LO(LO) {} - ~SarifDiagnostics() override = default; - - void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, - FilesMade *FM) override; - - StringRef getName() const override { return "SarifDiagnostics"; } - PathGenerationScheme getGenerationScheme() const override { return Minimal; } - bool supportsLogicalOpControlFlow() const override { return true; } - bool supportsCrossFileDiagnostics() const override { return true; } -}; -} // end anonymous namespace - -void ento::createSarifDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &Output, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - - // TODO: Emit an error here. - if (Output.empty()) - return; - - C.push_back(new SarifDiagnostics(Output, PP.getLangOpts())); - createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP, - CTU); -} - -static StringRef getFileName(const FileEntry &FE) { - StringRef Filename = FE.tryGetRealPathName(); - if (Filename.empty()) - Filename = FE.getName(); - return Filename; -} - -static std::string percentEncodeURICharacter(char C) { - // RFC 3986 claims alpha, numeric, and this handful of - // characters are not reserved for the path component and - // should be written out directly. Otherwise, percent - // encode the character and write that out instead of the - // reserved character. - if (llvm::isAlnum(C) || - StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C)) - return std::string(&C, 1); - return "%" + llvm::toHex(StringRef(&C, 1)); -} - -static std::string fileNameToURI(StringRef Filename) { - llvm::SmallString<32> Ret = StringRef("file://"); - - // Get the root name to see if it has a URI authority. - StringRef Root = sys::path::root_name(Filename); - if (Root.startswith("//")) { - // There is an authority, so add it to the URI. - Ret += Root.drop_front(2).str(); - } else if (!Root.empty()) { - // There is no authority, so end the component and add the root to the URI. - Ret += Twine("/" + Root).str(); - } - - auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename); - assert(Iter != End && "Expected there to be a non-root path component."); - // Add the rest of the path components, encoding any reserved characters; - // we skip past the first path component, as it was handled it above. - std::for_each(++Iter, End, [&Ret](StringRef Component) { - // For reasons unknown to me, we may get a backslash with Windows native - // paths for the initial backslash following the drive component, which - // we need to ignore as a URI path part. - if (Component == "\\") - return; - - // Add the separator between the previous path part and the one being - // currently processed. - Ret += "/"; - - // URI encode the part. - for (char C : Component) { - Ret += percentEncodeURICharacter(C); - } - }); - - return std::string(Ret); -} - -static json::Object createArtifactLocation(const FileEntry &FE) { - return json::Object{{"uri", fileNameToURI(getFileName(FE))}}; -} - -static json::Object createArtifact(const FileEntry &FE) { - return json::Object{{"location", createArtifactLocation(FE)}, - {"roles", json::Array{"resultFile"}}, - {"length", FE.getSize()}, - {"mimeType", "text/plain"}}; -} - -static json::Object createArtifactLocation(const FileEntry &FE, - json::Array &Artifacts) { - std::string FileURI = fileNameToURI(getFileName(FE)); - - // See if the Artifacts array contains this URI already. If it does not, - // create a new artifact object to add to the array. - auto I = llvm::find_if(Artifacts, [&](const json::Value &File) { - if (const json::Object *Obj = File.getAsObject()) { - if (const json::Object *FileLoc = Obj->getObject("location")) { - Optional<StringRef> URI = FileLoc->getString("uri"); - return URI && URI->equals(FileURI); - } - } - return false; - }); - - // Calculate the index within the artifact array so it can be stored in - // the JSON object. - auto Index = static_cast<unsigned>(std::distance(Artifacts.begin(), I)); - if (I == Artifacts.end()) - Artifacts.push_back(createArtifact(FE)); - - return json::Object{{"uri", FileURI}, {"index", Index}}; -} - -static unsigned int adjustColumnPos(const SourceManager &SM, SourceLocation Loc, - unsigned int TokenLen = 0) { - assert(!Loc.isInvalid() && "invalid Loc when adjusting column position"); - - std::pair<FileID, unsigned> LocInfo = SM.getDecomposedExpansionLoc(Loc); - assert(LocInfo.second > SM.getExpansionColumnNumber(Loc) && - "position in file is before column number?"); - - bool InvalidBuffer = false; - const MemoryBuffer *Buf = SM.getBuffer(LocInfo.first, &InvalidBuffer); - assert(!InvalidBuffer && "got an invalid buffer for the location's file"); - assert(Buf->getBufferSize() >= (LocInfo.second + TokenLen) && - "token extends past end of buffer?"); - - // Adjust the offset to be the start of the line, since we'll be counting - // Unicode characters from there until our column offset. - unsigned int Off = LocInfo.second - (SM.getExpansionColumnNumber(Loc) - 1); - unsigned int Ret = 1; - while (Off < (LocInfo.second + TokenLen)) { - Off += getNumBytesForUTF8(Buf->getBuffer()[Off]); - Ret++; - } - - return Ret; -} - -static json::Object createTextRegion(const LangOptions &LO, SourceRange R, - const SourceManager &SM) { - json::Object Region{ - {"startLine", SM.getExpansionLineNumber(R.getBegin())}, - {"startColumn", adjustColumnPos(SM, R.getBegin())}, - }; - if (R.getBegin() == R.getEnd()) { - Region["endColumn"] = adjustColumnPos(SM, R.getBegin()); - } else { - Region["endLine"] = SM.getExpansionLineNumber(R.getEnd()); - Region["endColumn"] = adjustColumnPos( - SM, R.getEnd(), - Lexer::MeasureTokenLength(R.getEnd(), SM, LO)); - } - return Region; -} - -static json::Object createPhysicalLocation(const LangOptions &LO, - SourceRange R, const FileEntry &FE, - const SourceManager &SMgr, - json::Array &Artifacts) { - return json::Object{ - {{"artifactLocation", createArtifactLocation(FE, Artifacts)}, - {"region", createTextRegion(LO, R, SMgr)}}}; -} - -enum class Importance { Important, Essential, Unimportant }; - -static StringRef importanceToStr(Importance I) { - switch (I) { - case Importance::Important: - return "important"; - case Importance::Essential: - return "essential"; - case Importance::Unimportant: - return "unimportant"; - } - llvm_unreachable("Fully covered switch is not so fully covered"); -} - -static json::Object createThreadFlowLocation(json::Object &&Location, - Importance I) { - return json::Object{{"location", std::move(Location)}, - {"importance", importanceToStr(I)}}; -} - -static json::Object createMessage(StringRef Text) { - return json::Object{{"text", Text.str()}}; -} - -static json::Object createLocation(json::Object &&PhysicalLocation, - StringRef Message = "") { - json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}}; - if (!Message.empty()) - Ret.insert({"message", createMessage(Message)}); - return Ret; -} - -static Importance calculateImportance(const PathDiagnosticPiece &Piece) { - switch (Piece.getKind()) { - case PathDiagnosticPiece::Call: - case PathDiagnosticPiece::Macro: - case PathDiagnosticPiece::Note: - case PathDiagnosticPiece::PopUp: - // FIXME: What should be reported here? - break; - case PathDiagnosticPiece::Event: - return Piece.getTagStr() == "ConditionBRVisitor" ? Importance::Important - : Importance::Essential; - case PathDiagnosticPiece::ControlFlow: - return Importance::Unimportant; - } - return Importance::Unimportant; -} - -static json::Object createThreadFlow(const LangOptions &LO, - const PathPieces &Pieces, - json::Array &Artifacts) { - const SourceManager &SMgr = Pieces.front()->getLocation().getManager(); - json::Array Locations; - for (const auto &Piece : Pieces) { - const PathDiagnosticLocation &P = Piece->getLocation(); - Locations.push_back(createThreadFlowLocation( - createLocation(createPhysicalLocation( - LO, P.asRange(), - *P.asLocation().getExpansionLoc().getFileEntry(), - SMgr, Artifacts), - Piece->getString()), - calculateImportance(*Piece))); - } - return json::Object{{"locations", std::move(Locations)}}; -} - -static json::Object createCodeFlow(const LangOptions &LO, - const PathPieces &Pieces, - json::Array &Artifacts) { - return json::Object{ - {"threadFlows", json::Array{createThreadFlow(LO, Pieces, Artifacts)}}}; -} - -static json::Object createResult(const LangOptions &LO, - const PathDiagnostic &Diag, - json::Array &Artifacts, - const StringMap<unsigned> &RuleMapping) { - const PathPieces &Path = Diag.path.flatten(false); - const SourceManager &SMgr = Path.front()->getLocation().getManager(); - - auto Iter = RuleMapping.find(Diag.getCheckerName()); - assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?"); - - return json::Object{ - {"message", createMessage(Diag.getVerboseDescription())}, - {"codeFlows", json::Array{createCodeFlow(LO, Path, Artifacts)}}, - {"locations", - json::Array{createLocation(createPhysicalLocation( - LO, Diag.getLocation().asRange(), - *Diag.getLocation().asLocation().getExpansionLoc().getFileEntry(), - SMgr, Artifacts))}}, - {"ruleIndex", Iter->getValue()}, - {"ruleId", Diag.getCheckerName()}}; -} - -static StringRef getRuleDescription(StringRef CheckName) { - return llvm::StringSwitch<StringRef>(CheckName) -#define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ - .Case(FULLNAME, HELPTEXT) -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - ; -} - -static StringRef getRuleHelpURIStr(StringRef CheckName) { - return llvm::StringSwitch<StringRef>(CheckName) -#define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ - .Case(FULLNAME, DOC_URI) -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - ; -} - -static json::Object createRule(const PathDiagnostic &Diag) { - StringRef CheckName = Diag.getCheckerName(); - json::Object Ret{ - {"fullDescription", createMessage(getRuleDescription(CheckName))}, - {"name", CheckName}, - {"id", CheckName}}; - - std::string RuleURI = std::string(getRuleHelpURIStr(CheckName)); - if (!RuleURI.empty()) - Ret["helpUri"] = RuleURI; - - return Ret; -} - -static json::Array createRules(std::vector<const PathDiagnostic *> &Diags, - StringMap<unsigned> &RuleMapping) { - json::Array Rules; - llvm::StringSet<> Seen; - - llvm::for_each(Diags, [&](const PathDiagnostic *D) { - StringRef RuleID = D->getCheckerName(); - std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(RuleID); - if (P.second) { - RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index. - Rules.push_back(createRule(*D)); - } - }); - - return Rules; -} - -static json::Object createTool(std::vector<const PathDiagnostic *> &Diags, - StringMap<unsigned> &RuleMapping) { - return json::Object{ - {"driver", json::Object{{"name", "clang"}, - {"fullName", "clang static analyzer"}, - {"language", "en-US"}, - {"version", getClangFullVersion()}, - {"rules", createRules(Diags, RuleMapping)}}}}; -} - -static json::Object createRun(const LangOptions &LO, - std::vector<const PathDiagnostic *> &Diags) { - json::Array Results, Artifacts; - StringMap<unsigned> RuleMapping; - json::Object Tool = createTool(Diags, RuleMapping); - - llvm::for_each(Diags, [&](const PathDiagnostic *D) { - Results.push_back(createResult(LO, *D, Artifacts, RuleMapping)); - }); - - return json::Object{{"tool", std::move(Tool)}, - {"results", std::move(Results)}, - {"artifacts", std::move(Artifacts)}, - {"columnKind", "unicodeCodePoints"}}; -} - -void SarifDiagnostics::FlushDiagnosticsImpl( - std::vector<const PathDiagnostic *> &Diags, FilesMade *) { - // We currently overwrite the file if it already exists. However, it may be - // useful to add a feature someday that allows the user to append a run to an - // existing SARIF file. One danger from that approach is that the size of the - // file can become large very quickly, so decoding into JSON to append a run - // may be an expensive operation. - std::error_code EC; - llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_Text); - if (EC) { - llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; - return; - } - json::Object Sarif{ - {"$schema", - "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"}, - {"version", "2.1.0"}, - {"runs", json::Array{createRun(LO, Diags)}}}; - OS << llvm::formatv("{0:2}\n", json::Value(std::move(Sarif))); -} diff --git a/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp deleted file mode 100644 index ae2bad7..0000000 --- a/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp +++ /dev/null @@ -1,155 +0,0 @@ -//===--- TextDiagnostics.cpp - Text Diagnostics for Paths -------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the TextDiagnostics object. -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/PathDiagnostic.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Basic/Version.h" -#include "clang/CrossTU/CrossTranslationUnit.h" -#include "clang/Frontend/ASTUnit.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" -#include "clang/Tooling/Core/Replacement.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Casting.h" - -using namespace clang; -using namespace ento; -using namespace tooling; - -namespace { -/// Emitsd minimal diagnostics (report message + notes) for the 'none' output -/// type to the standard error, or to to compliment many others. Emits detailed -/// diagnostics in textual format for the 'text' output type. -class TextDiagnostics : public PathDiagnosticConsumer { - PathDiagnosticConsumerOptions DiagOpts; - DiagnosticsEngine &DiagEng; - const LangOptions &LO; - bool ShouldDisplayPathNotes; - -public: - TextDiagnostics(PathDiagnosticConsumerOptions DiagOpts, - DiagnosticsEngine &DiagEng, const LangOptions &LO, - bool ShouldDisplayPathNotes) - : DiagOpts(std::move(DiagOpts)), DiagEng(DiagEng), LO(LO), - ShouldDisplayPathNotes(ShouldDisplayPathNotes) {} - ~TextDiagnostics() override {} - - StringRef getName() const override { return "TextDiagnostics"; } - - bool supportsLogicalOpControlFlow() const override { return true; } - bool supportsCrossFileDiagnostics() const override { return true; } - - PathGenerationScheme getGenerationScheme() const override { - return ShouldDisplayPathNotes ? Minimal : None; - } - - void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) override { - unsigned WarnID = - DiagOpts.ShouldDisplayWarningsAsErrors - ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0") - : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); - unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0"); - SourceManager &SM = DiagEng.getSourceManager(); - - Replacements Repls; - auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String, - ArrayRef<SourceRange> Ranges, - ArrayRef<FixItHint> Fixits) { - if (!DiagOpts.ShouldApplyFixIts) { - DiagEng.Report(Loc, ID) << String << Ranges << Fixits; - return; - } - - DiagEng.Report(Loc, ID) << String << Ranges; - for (const FixItHint &Hint : Fixits) { - Replacement Repl(SM, Hint.RemoveRange, Hint.CodeToInsert); - - if (llvm::Error Err = Repls.add(Repl)) { - llvm::errs() << "Error applying replacement " << Repl.toString() - << ": " << Err << "\n"; - } - } - }; - - for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), - E = Diags.end(); - I != E; ++I) { - const PathDiagnostic *PD = *I; - std::string WarningMsg = (DiagOpts.ShouldDisplayDiagnosticName - ? " [" + PD->getCheckerName() + "]" - : "") - .str(); - - reportPiece(WarnID, PD->getLocation().asLocation(), - (PD->getShortDescription() + WarningMsg).str(), - PD->path.back()->getRanges(), PD->path.back()->getFixits()); - - // First, add extra notes, even if paths should not be included. - for (const auto &Piece : PD->path) { - if (!isa<PathDiagnosticNotePiece>(Piece.get())) - continue; - - reportPiece(NoteID, Piece->getLocation().asLocation(), - Piece->getString(), Piece->getRanges(), - Piece->getFixits()); - } - - if (!ShouldDisplayPathNotes) - continue; - - // Then, add the path notes if necessary. - PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true); - for (const auto &Piece : FlatPath) { - if (isa<PathDiagnosticNotePiece>(Piece.get())) - continue; - - reportPiece(NoteID, Piece->getLocation().asLocation(), - Piece->getString(), Piece->getRanges(), - Piece->getFixits()); - } - } - - if (Repls.empty()) - return; - - Rewriter Rewrite(SM, LO); - if (!applyAllReplacements(Repls, Rewrite)) { - llvm::errs() << "An error occured during applying fix-it.\n"; - } - - Rewrite.overwriteChangedFiles(); - } -}; -} // end anonymous namespace - -void ento::createTextPathDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &Prefix, const clang::Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), - PP.getLangOpts(), - /*ShouldDisplayPathNotes=*/true)); -} - -void ento::createTextMinimalPathDiagnosticConsumer( - PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &Prefix, const clang::Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), - PP.getLangOpts(), - /*ShouldDisplayPathNotes=*/false)); -} diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index f41f834..74f0db4 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -21,6 +21,7 @@ #include "clang/Analysis/CallGraph.h" #include "clang/Analysis/CodeInjector.h" #include "clang/Analysis/PathDiagnostic.h" +#include "clang/Analysis/PathDiagnosticConsumers.h" #include "clang/Basic/SourceManager.h" #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Frontend/CompilerInstance.h" @@ -30,7 +31,6 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/PostOrderIterator.h" @@ -152,7 +152,7 @@ public: case PD_##NAME: \ CREATEFN(Opts->getDiagOpts(), PathConsumers, OutDir, PP, CTU); \ break; -#include "clang/StaticAnalyzer/Core/Analyses.def" +#include "clang/Analysis/PathDiagnosticConsumers.def" default: llvm_unreachable("Unknown analyzer output type!"); } |