diff options
author | 2020-08-03 14:31:31 +0000 | |
---|---|---|
committer | 2020-08-03 14:31:31 +0000 | |
commit | e5dd70708596ae51455a0ffa086a00c5b29f8583 (patch) | |
tree | 5d676f27b570bacf71e786c3b5cff3e6f6679b59 /gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | |
parent | Import LLVM 10.0.0 release including clang, lld and lldb. (diff) | |
download | wireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.tar.xz wireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.zip |
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom
tested by plenty
Diffstat (limited to 'gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp')
-rw-r--r-- | gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | 1119 |
1 files changed, 1119 insertions, 0 deletions
diff --git a/gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp new file mode 100644 index 00000000000..002b6070ddc --- /dev/null +++ b/gnu/llvm/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -0,0 +1,1119 @@ +//===- 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/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/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/IssueHash.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 { + std::string Directory; + bool createdDir = false; + bool noDir = false; + const Preprocessor &PP; + AnalyzerOptions &AnalyzerOpts; + const bool SupportsCrossFileDiagnostics; + +public: + HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, + const std::string& prefix, + const Preprocessor &pp, + bool supportsMultipleFiles) + : Directory(prefix), PP(pp), AnalyzerOpts(AnalyzerOpts), + 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( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { + C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); +} + +void ento::createHTMLSingleFileDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { + C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); +} + +//===----------------------------------------------------------------------===// +// 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 (!AnalyzerOpts.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(AnalyzerOpts.FullCompilerInvocation); + 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(SMgr, 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]+"); + +document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll(".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) { + document.querySelectorAll(".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> + )<<<"; +} |