32#include "llvm/ADT/RewriteBuffer.h"
33#include "llvm/ADT/STLExtras.h"
34#include "llvm/ADT/Sequence.h"
35#include "llvm/ADT/SmallString.h"
36#include "llvm/ADT/StringRef.h"
37#include "llvm/ADT/iterator_range.h"
38#include "llvm/Support/Errc.h"
39#include "llvm/Support/ErrorHandling.h"
40#include "llvm/Support/FileSystem.h"
41#include "llvm/Support/IOSandbox.h"
42#include "llvm/Support/Path.h"
43#include "llvm/Support/raw_ostream.h"
49#include <system_error>
55using llvm::RewriteBuffer;
66 PathDiagnosticConsumerOptions DiagOpts;
67 std::string Directory;
68 bool createdDir =
false;
70 const Preprocessor &PP;
71 const bool SupportsCrossFileDiagnostics;
72 llvm::StringSet<> EmittedHashes;
77 HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,
78 const std::string &OutputDir,
const Preprocessor &pp,
79 bool supportsMultipleFiles)
80 : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),
81 SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
83 ~HTMLDiagnostics()
override { FlushDiagnostics(
nullptr); }
85 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
86 FilesMade *filesMade)
override;
90 bool supportsCrossFileDiagnostics()
const override {
91 return SupportsCrossFileDiagnostics;
94 unsigned ProcessMacroPiece(raw_ostream &os,
const PathDiagnosticMacroPiece &P,
97 unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID,
98 const PathDiagnosticControlFlowPiece &P,
101 void HandlePiece(Rewriter &R, FileID BugFileID,
const PathDiagnosticPiece &P,
102 const std::vector<SourceRange> &PopUpRanges,
unsigned num,
105 void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range,
106 const char *HighlightStart =
"<span class=\"mrange\">",
107 const char *HighlightEnd =
"</span>");
109 void ReportDiag(
const PathDiagnostic &D, FilesMade *filesMade);
112 std::string GenerateHTML(
const PathDiagnostic &D, Rewriter &R,
113 const SourceManager &SMgr,
const PathPieces &path,
114 const char *declName);
117 void FinalizeHTML(
const PathDiagnostic &D, Rewriter &R,
118 const SourceManager &SMgr,
const PathPieces &path,
119 FileID FID, FileEntryRef Entry,
const char *declName);
122 void RewriteFile(Rewriter &R,
const PathPieces &path, FileID FID);
124 PathGenerationScheme getGenerationScheme()
const override {
129 void addArrowSVGs(Rewriter &R, FileID BugFileID,
130 const ArrowMap &ArrowIndices);
133 StringRef showHelpJavascript();
136 StringRef generateKeyboardNavigationJavascript();
139 StringRef generateArrowDrawingJavascript();
142 std::string showRelevantLinesJavascript(
const PathDiagnostic &D,
143 const PathPieces &path);
146 void dumpCoverageData(
const PathDiagnostic &D,
const PathPieces &path,
147 llvm::raw_string_ostream &os);
154unsigned getPathSizeWithoutArrows(
const PathPieces &Path) {
155 unsigned TotalPieces = Path.size();
156 unsigned TotalArrowPieces = llvm::count_if(
158 return TotalPieces - TotalArrowPieces;
161class ArrowMap :
public std::vector<unsigned> {
162 using Base = std::vector<unsigned>;
165 ArrowMap(
unsigned Size) : Base(
Size, 0) {}
166 unsigned getTotalNumberOfArrows()
const {
return at(0); }
169llvm::raw_ostream &
operator<<(llvm::raw_ostream &
OS,
const ArrowMap &Indices) {
171 llvm::interleave(Indices,
OS,
",");
182 bool SupportMultipleFiles) {
185 if (OutputDir.empty())
188 C.emplace_back(std::make_unique<HTMLDiagnostics>(
189 std::move(DiagOpts), OutputDir, PP, SupportMultipleFiles));
192void ento::createHTMLDiagnosticConsumer(
203 createTextMinimalPathDiagnosticConsumer(DiagOpts,
C, OutputDir, PP, CTU,
210void ento::createHTMLSingleFileDiagnosticConsumer(
215 createTextMinimalPathDiagnosticConsumer(DiagOpts,
C, OutputDir, PP, CTU,
222void ento::createPlistHTMLDiagnosticConsumer(
228 DiagOpts,
C, std::string(llvm::sys::path::parent_path(prefix)), PP,
true);
230 MacroExpansions,
true);
231 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts),
C, prefix, PP,
232 CTU, MacroExpansions);
235void ento::createSarifHTMLDiagnosticConsumer(
241 DiagOpts,
C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,
245 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts),
C, sarif_file,
246 PP, CTU, MacroExpansions);
253void HTMLDiagnostics::FlushDiagnosticsImpl(
254 std::vector<const PathDiagnostic *> &Diags,
255 FilesMade *filesMade) {
256 for (
const auto Diag : Diags)
257 ReportDiag(*
Diag, filesMade);
260void HTMLDiagnostics::ReportDiag(
const PathDiagnostic& D,
261 FilesMade *filesMade) {
263 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
268 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
269 llvm::errs() <<
"warning: could not create directory '"
270 << Directory <<
"': " << ec.message() <<
'\n';
283 assert(!path.empty());
284 const SourceManager &SMgr = path.front()->getLocation().getManager();
287 Rewriter
R(
const_cast<SourceManager&
>(SMgr), PP.
getLangOpts());
290 SmallString<128> declName(
"unknown");
293 if (
const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
294 declName = ND->getDeclName().getAsString();
296 if (
const Stmt *Body = DeclWithIssue->getBody()) {
303 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
307 SmallString<32> IssueHash =
309 auto [It, IsNew] = EmittedHashes.insert(IssueHash);
315 std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
316 if (report.empty()) {
317 llvm::errs() <<
"warning: no diagnostics generated for main file.\n";
324 SmallString<128> FileNameStr;
325 llvm::raw_svector_ostream
FileName(FileNameStr);
338 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
343 << declName.c_str() <<
"-" << offsetDecl <<
"-";
346 FileName << StringRef(IssueHash).substr(0, 6).str() <<
".html";
348 SmallString<128> ResultPath;
349 llvm::sys::path::append(ResultPath, Directory,
FileName.str());
350 if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) {
351 llvm::errs() <<
"warning: could not make '" << ResultPath
352 <<
"' absolute: " << EC.message() <<
'\n';
356 if (std::error_code EC = llvm::sys::fs::openFileForReadWrite(
357 ResultPath, FD, llvm::sys::fs::CD_CreateNew,
358 llvm::sys::fs::OF_Text)) {
364 if (EC != llvm::errc::file_exists) {
365 llvm::errs() <<
"warning: could not create file in '" << Directory
366 <<
"': " << EC.message() <<
'\n';
367 }
else if (filesMade) {
371 filesMade->addDiagnostic(D,
getName(),
372 llvm::sys::path::filename(ResultPath));
377 llvm::raw_fd_ostream os(FD,
true);
380 filesMade->addDiagnostic(D,
getName(),
381 llvm::sys::path::filename(ResultPath));
387std::string HTMLDiagnostics::GenerateHTML(
const PathDiagnostic& D, Rewriter &R,
388 const SourceManager& SMgr,
const PathPieces& path,
const char *declName) {
390 std::vector<FileID> FileIDs;
391 for (
auto I : path) {
392 FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
393 if (llvm::is_contained(FileIDs, FID))
396 FileIDs.push_back(FID);
397 RewriteFile(R, path, FID);
400 if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
402 for (
auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
404 llvm::raw_string_ostream os(s);
406 if (I != FileIDs.begin())
407 os <<
"<hr class=divider>\n";
409 os <<
"<div id=File" << I->getHashValue() <<
">\n";
412 if (I != FileIDs.begin())
413 os <<
"<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
414 <<
"\">←</a></div>";
421 os <<
"<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
422 <<
"\">→</a></div>";
430 for (
auto I : llvm::drop_begin(FileIDs)) {
432 llvm::raw_string_ostream os(s);
434 const RewriteBuffer *Buf =
R.getRewriteBufferFor(I);
442 const RewriteBuffer *Buf =
R.getRewriteBufferFor(FileIDs[0]);
448 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
450 FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName);
453 llvm::raw_string_ostream os(file);
460void HTMLDiagnostics::dumpCoverageData(
461 const PathDiagnostic &D,
462 const PathPieces &path,
463 llvm::raw_string_ostream &os) {
467 os <<
"var relevant_lines = {";
468 for (
auto I = ExecutedLines.begin(),
469 E = ExecutedLines.end(); I != E; ++I) {
470 if (I != ExecutedLines.begin())
473 os <<
"\"" << I->first.getHashValue() <<
"\": {";
474 for (
unsigned LineNo : I->second) {
475 if (LineNo != *(I->second.begin()))
478 os <<
"\"" << LineNo <<
"\": 1";
486std::string HTMLDiagnostics::showRelevantLinesJavascript(
487 const PathDiagnostic &D,
const PathPieces &path) {
489 llvm::raw_string_ostream os(s);
490 os <<
"<script type='text/javascript'>\n";
491 dumpCoverageData(D, path, os);
494var filterCounterexample = function (hide) {
495 var tables = document.getElementsByClassName("code");
496 for (var t=0; t<tables.length; t++) {
497 var table = tables[t];
498 var file_id = table.getAttribute("data-fileid");
499 var lines_in_fid = relevant_lines[file_id];
503 var lines = table.getElementsByClassName("codeline");
504 for (var i=0; i<lines.length; i++) {
506 var lineNo = el.getAttribute("data-linenumber");
507 if (!lines_in_fid[lineNo]) {
509 el.setAttribute("hidden", "");
511 el.removeAttribute("hidden");
518window.addEventListener("keydown", function (event) {
519 if (event.defaultPrevented) {
523 if (event.shiftKey && event.keyCode == 83) {
524 var checked = document.getElementsByName("showCounterexample")[0].checked;
525 filterCounterexample(!checked);
526 document.getElementsByName("showCounterexample")[0].click();
530 event.preventDefault();
533document.addEventListener("DOMContentLoaded", function() {
534 document.querySelector('input[name="showCounterexample"]').onchange=
536 filterCounterexample(this.checked);
542 <input type="checkbox" name="showCounterexample" id="showCounterexample" />
543 <label for="showCounterexample">
544 Show only relevant lines
546 <input type="checkbox" name="showArrows"
547 id="showArrows" style="margin-left: 10px" />
548 <label for="showArrows">
549 Show control flow arrows
557void HTMLDiagnostics::FinalizeHTML(
const PathDiagnostic &D, Rewriter &R,
558 const SourceManager &SMgr,
559 const PathPieces &path, FileID FID,
560 FileEntryRef Entry,
const char *declName) {
565 llvm::SmallString<0> DirName;
567 if (llvm::sys::path::is_relative(Entry.
getName())) {
568 llvm::sys::fs::current_path(DirName);
572 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
573 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
578 generateKeyboardNavigationJavascript());
581 generateArrowDrawingJavascript());
585 showRelevantLinesJavascript(D, path));
590 llvm::raw_string_ostream os(s);
592 os <<
"<!-- REPORTHEADER -->\n"
593 <<
"<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
594 "<tr><td class=\"rowname\">File:</td><td>"
597 <<
"</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
598 "<a href=\"#EndPath\">line "
606 unsigned NumExtraPieces = 0;
607 for (
const auto &Piece : path) {
608 if (
const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
614 os <<
"<tr><td class=\"rowname\">Note:</td><td>"
615 <<
"<a href=\"#Note" << NumExtraPieces <<
"\">line "
616 << LineNumber <<
", column " << ColumnNumber <<
"</a><br />"
623 for (
const std::string &Metadata :
630<!-- REPORTSUMMARYEXTRA -->
631<h3>Annotated Source Code</h3>
632<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
633 to see keyboard shortcuts</p>
634<input type="checkbox" class="spoilerhider" id="showinvocation" />
635<label for="showinvocation" >Show analyzer invocation</label>
636<div class="spoiler">clang -cc1 )<<<";
640<div id='tooltiphint' hidden="true">
641 <p>Keyboard shortcuts: </p>
643 <li>Use 'j/k' keys for keyboard navigation</li>
644 <li>Use 'Shift+S' to show/hide relevant lines</li>
645 <li>Use '?' to toggle this window</li>
647 <a href="#" onclick="toggleHelp(); return false;">Close</a>
657 llvm::raw_string_ostream os(s);
660 if (!BugDesc.empty())
661 os <<
"\n<!-- BUGDESC " << BugDesc <<
" -->\n";
664 if (!BugType.empty())
665 os <<
"\n<!-- BUGTYPE " << BugType <<
" -->\n";
674 if (!BugCategory.empty())
675 os <<
"\n<!-- BUGCATEGORY " << BugCategory <<
" -->\n";
677 os <<
"\n<!-- BUGFILE " << DirName << Entry.
getName() <<
" -->\n";
679 os <<
"\n<!-- FILENAME " << llvm::sys::path::filename(Entry.
getName()) <<
" -->\n";
681 os <<
"\n<!-- FUNCTIONNAME " << declName <<
" -->\n";
683 os <<
"\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT "
686 os <<
"\n<!-- BUGLINE "
690 os <<
"\n<!-- BUGCOLUMN "
694 os <<
"\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) <<
" -->\n";
697 os <<
"\n<!-- BUGMETAEND -->\n";
706StringRef HTMLDiagnostics::showHelpJavascript() {
708<script type='text/javascript'>
710var toggleHelp = function() {
711 var hint = document.querySelector("#tooltiphint");
712 var attributeName = "hidden";
713 if (hint.hasAttribute(attributeName)) {
714 hint.removeAttribute(attributeName);
716 hint.setAttribute("hidden", "true");
719window.addEventListener("keydown", function (event) {
720 if (event.defaultPrevented) {
723 if (event.key == "?") {
728 event.preventDefault();
735 return !(
Range.getBegin().isMacroID() ||
Range.getEnd().isMacroID());
740 const std::vector<SourceRange> &PopUpRanges) {
741 for (
const auto &Range : PopUpRanges) {
746 "<table class='variable_popup'><tbody>",
752 const PathDiagnosticPopUpPiece &Piece,
753 std::vector<SourceRange> &PopUpRanges,
754 unsigned int LastReportedPieceIndex,
755 unsigned int PopUpPieceIndex) {
756 SmallString<256> Buf;
757 llvm::raw_svector_ostream
Out(Buf);
764 Out <<
"<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
765 << LastReportedPieceIndex;
768 Out <<
'.' << PopUpPieceIndex;
770 Out <<
"</div></td><td>" << Piece.
getString() <<
"</td></tr>";
773 if (!llvm::is_contained(PopUpRanges, Range)) {
775 PopUpRanges.push_back(Range);
777 Out <<
"</tbody></table></span>";
779 "<span class='variable'>", Buf.c_str(),
788void HTMLDiagnostics::RewriteFile(Rewriter &R,
const PathPieces &path,
797 unsigned TotalPieces = getPathSizeWithoutArrows(path);
798 unsigned TotalNotePieces =
802 unsigned PopUpPieceCount =
807 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
808 unsigned NumRegularPieces = TotalRegularPieces;
809 unsigned NumNotePieces = TotalNotePieces;
810 unsigned NumberOfArrows = 0;
812 std::map<int, int> IndexMap;
813 ArrowMap ArrowIndices(TotalRegularPieces + 1);
816 std::vector<SourceRange> PopUpRanges;
818 const auto &Piece = *I.get();
821 ++IndexMap[NumRegularPieces];
826 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
829 }
else if (isArrowPiece(Piece)) {
830 NumberOfArrows = ProcessControlFlowPiece(
832 ArrowIndices[NumRegularPieces] = NumberOfArrows;
835 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
838 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
841 ArrowIndices[0] = NumberOfArrows;
851 assert(ArrowIndices.back() == 0 &&
852 "No arrows should be after the last event");
854 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
855 "Incorrect arrow indices map");
859 NumRegularPieces = TotalRegularPieces;
861 const auto &Piece = *I.get();
863 if (
const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
864 int PopUpPieceIndex = IndexMap[NumRegularPieces];
874 if (PopUpPieceIndex > 0)
875 --IndexMap[NumRegularPieces];
885 addArrowSVGs(R, FID, ArrowIndices);
894void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
895 const PathDiagnosticPiece &P,
896 const std::vector<SourceRange> &PopUpRanges,
897 unsigned num,
unsigned max) {
905 SourceManager &SM =
R.getSourceMgr();
906 assert(&Pos.
getManager() == &SM &&
"SourceManagers are different!");
909 if (LPosInfo.first != BugFileID)
913 const char *FileStart = Buf.getBufferStart();
919 const char *LineStart = TokInstantiationPtr-ColNo;
922 const char *LineEnd = TokInstantiationPtr;
923 const char *FileEnd = Buf.getBufferEnd();
924 while (*LineEnd !=
'\n' && LineEnd != FileEnd)
929 for (
const char* c = LineStart; c != TokInstantiationPtr; ++c)
930 PosNo += *c ==
'\t' ? 8 : 1;
934 const char *
Kind =
nullptr;
936 bool SuppressIndex = (
max == 1);
945 SuppressIndex =
true;
949 llvm_unreachable(
"Calls and extra notes should already be handled");
953 llvm::raw_string_ostream os(sbuf);
955 os <<
"\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
964 os <<
"\" class=\"msg";
966 os <<
" msg" <<
Kind;
967 os <<
"\" style=\"margin-left:" << PosNo <<
"ex";
973 unsigned max_token = 0;
975 unsigned len = Msg.size();
985 if (cnt > max_token) max_token = cnt;
994 const unsigned max_line = 120;
996 if (max_token >= max_line)
999 unsigned characters = max_line;
1000 unsigned lines = len / max_line;
1003 for (; characters > max_token; --characters)
1004 if (len / characters > lines) {
1010 em = characters / 2;
1013 if (em < max_line/2)
1014 os <<
"; max-width:" << em <<
"em";
1017 os <<
"; max-width:100em";
1021 if (!SuppressIndex) {
1022 os <<
"<table class=\"msgT\"><tr><td valign=\"top\">";
1023 os <<
"<div class=\"PathIndex";
1024 if (Kind) os <<
" PathIndex" << Kind;
1025 os <<
"\">" << num <<
"</div>";
1028 os <<
"</td><td><div class=\"PathNav\"><a href=\"#Path"
1030 <<
"\" title=\"Previous event ("
1032 <<
")\">←</a></div>";
1038 if (
const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
1039 os <<
"Within the expansion of the macro '";
1043 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
1047 const char* MacroName = LocInfo.second + BufferInfo.data();
1049 BufferInfo.begin(), MacroName, BufferInfo.end());
1052 rawLexer.LexFromRawLexer(TheTok);
1053 for (
unsigned i = 0, n = TheTok.
getLength(); i < n; ++i)
1059 if (!SuppressIndex) {
1062 os <<
"<td><div class=\"PathNav\"><a href=\"#";
1066 os <<
"Path" << (num + 1);
1067 os <<
"\" title=\"Next event ("
1069 <<
")\">→</a></div></td>";
1072 os <<
"</tr></table>";
1076 ProcessMacroPiece(os, *MP, 0);
1081 if (!SuppressIndex) {
1084 os <<
"<td><div class=\"PathNav\"><a href=\"#";
1088 os <<
"Path" << (num + 1);
1089 os <<
"\" title=\"Next event ("
1091 <<
")\">→</a></div></td>";
1094 os <<
"</tr></table>";
1098 os <<
"</div></td></tr>";
1102 unsigned DisplayPos = LineEnd - FileStart;
1103 if (LineEnd != FileEnd)
1105 SourceLocation Loc =
1108 R.InsertTextBefore(Loc, os.str());
1111 ArrayRef<SourceRange> Ranges = P.
getRanges();
1112 for (
const auto &Range : Ranges) {
1114 if (llvm::is_contained(PopUpRanges, Range))
1122 unsigned x = n % (
'z' -
'a');
1128 os << char(
'a' + x);
1131unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1132 const PathDiagnosticMacroPiece& P,
1134 for (
const auto &subPiece : P.
subPieces) {
1135 if (
const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1136 num = ProcessMacroPiece(os, *MP, num);
1140 if (
const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1141 os <<
"<div class=\"msg msgEvent\" style=\"width:94%; "
1142 "margin-left:5px\">"
1143 "<table class=\"msgT\"><tr>"
1144 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1146 os <<
"</div></td><td valign=\"top\">"
1148 <<
"</td></tr></table></div>\n";
1155void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1156 const ArrowMap &ArrowIndices) {
1158 llvm::raw_string_ostream
OS(S);
1161<style type="text/css">
1168 pointer-events: none;
1172 stroke-opacity: 0.2;
1174 marker-end: url(#arrowhead);
1178 stroke-opacity: 0.6;
1180 marker-end: url(#arrowheadSelected);
1190<svg xmlns="http://www.w3.org/2000/svg">
1192 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1193 viewBox="0 0 10 10" refX="3" refY="5"
1194 markerWidth="4" markerHeight="4">
1195 <path d="M 0 0 L 10 5 L 0 10 z" />
1197 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1198 viewBox="0 0 10 10" refX="3" refY="5"
1199 markerWidth="4" markerHeight="4">
1200 <path d="M 0 0 L 10 5 L 0 10 z" />
1203 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1206 for (
unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1207 OS <<
" <path class=\"arrow\" id=\"arrow" << Index <<
"\"/>\n";
1213<script type='text/javascript'>
1214const arrowIndices = )<<<";
1216 OS << ArrowIndices << "\n</script>\n";
1218 R.InsertTextBefore(
R.getSourceMgr().getLocForStartOfFile(BugFileID),
1225 llvm::raw_string_ostream
OS(
Result);
1226 OS <<
"<span id=\"" << ClassName << Index <<
"\">";
1238unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1239 Rewriter &R, FileID BugFileID,
const PathDiagnosticControlFlowPiece &P,
1241 for (
const PathDiagnosticLocationPair &LPair : P) {
1245 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1247 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1254void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1256 const char *HighlightStart,
1257 const char *HighlightEnd) {
1258 SourceManager &SM =
R.getSourceMgr();
1259 const LangOptions &LangOpts =
R.getLangOpts();
1268 HighlightStart, HighlightEnd,
1272StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1274<script type='text/javascript'>
1275var digitMatcher = new RegExp("[0-9]+");
1277var querySelectorAllArray = function(selector) {
1278 return Array.prototype.slice.call(
1279 document.querySelectorAll(selector));
1282document.addEventListener("DOMContentLoaded", function() {
1283 querySelectorAllArray(".PathNav > a").forEach(
1284 function(currentValue, currentIndex) {
1285 var hrefValue = currentValue.getAttribute("href");
1286 currentValue.onclick = function() {
1287 scrollTo(document.querySelector(hrefValue));
1293var findNum = function() {
1294 var s = document.querySelector(".msg.selected");
1295 if (!s || s.id == "EndPath") {
1298 var out = parseInt(digitMatcher.exec(s.id)[0]);
1302var classListAdd = function(el, theClass) {
1303 if(!el.className.baseVal)
1304 el.className += " " + theClass;
1306 el.className.baseVal += " " + theClass;
1309var classListRemove = function(el, theClass) {
1310 var className = (!el.className.baseVal) ?
1311 el.className : el.className.baseVal;
1312 className = className.replace(" " + theClass, "");
1313 if(!el.className.baseVal)
1314 el.className = className;
1316 el.className.baseVal = className;
1319var scrollTo = function(el) {
1320 querySelectorAllArray(".selected").forEach(function(s) {
1321 classListRemove(s, "selected");
1323 classListAdd(el, "selected");
1324 window.scrollBy(0, el.getBoundingClientRect().top -
1325 (window.innerHeight / 2));
1326 highlightArrowsForSelectedEvent();
1329var move = function(num, up, numItems) {
1330 if (num == 1 && up || num == numItems - 1 && !up) {
1332 } else if (num == 0 && up) {
1333 return numItems - 1;
1334 } else if (num == 0 && !up) {
1335 return 1 % numItems;
1337 return up ? num - 1 : num + 1;
1340var numToId = function(num) {
1342 return document.getElementById("EndPath")
1344 return document.getElementById("Path" + num);
1347var navigateTo = function(up) {
1348 var numItems = document.querySelectorAll(
1349 ".line > .msgEvent, .line > .msgControl").length;
1350 var currentSelected = findNum();
1351 var newSelected = move(currentSelected, up, numItems);
1352 var newEl = numToId(newSelected, numItems);
1354 // Scroll element into center.
1358window.addEventListener("keydown", function (event) {
1359 if (event.defaultPrevented) {
1363 if (event.keyCode == 74) {
1364 navigateTo(/*up=*/false);
1366 } else if (event.keyCode == 75) {
1367 navigateTo(/*up=*/true);
1371 event.preventDefault();
1377StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1379<script type='text/javascript'>
1380// Return range of numbers from a range [lower, upper).
1381function range(lower, upper) {
1383 for (var i = lower; i <= upper; ++i) {
1389var getRelatedArrowIndices = function(pathId) {
1390 // HTML numeration of events is a bit different than it is in the path.
1391 // Everything is rotated one step to the right, so the last element
1392 // (error diagnostic) has index 0.
1394 // arrowIndices has at least 2 elements
1395 pathId = arrowIndices.length - 1;
1398 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1401var highlightArrowsForSelectedEvent = function() {
1402 const selectedNum = findNum();
1403 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1404 arrowIndicesToHighlight.forEach((index) => {
1405 var arrow = document.querySelector("#arrow" + index);
1407 classListAdd(arrow, "selected")
1412var getAbsoluteBoundingRect = function(element) {
1413 const relative = element.getBoundingClientRect();
1415 left: relative.left + window.pageXOffset,
1416 right: relative.right + window.pageXOffset,
1417 top: relative.top + window.pageYOffset,
1418 bottom: relative.bottom + window.pageYOffset,
1419 height: relative.height,
1420 width: relative.width
1424var drawArrow = function(index) {
1425 // This function is based on the great answer from SO:
1426 // https://stackoverflow.com/a/39575674/11582326
1427 var start = document.querySelector("#start" + index);
1428 var end = document.querySelector("#end" + index);
1429 var arrow = document.querySelector("#arrow" + index);
1431 var startRect = getAbsoluteBoundingRect(start);
1432 var endRect = getAbsoluteBoundingRect(end);
1434 // It is an arrow from a token to itself, no need to visualize it.
1435 if (startRect.top == endRect.top &&
1436 startRect.left == endRect.left)
1439 // Each arrow is a very simple Bézier curve, with two nodes and
1440 // two handles. So, we need to calculate four points in the window:
1442 var posStart = { x: 0, y: 0 };
1444 var posEnd = { x: 0, y: 0 };
1445 // * handle for the start node
1446 var startHandle = { x: 0, y: 0 };
1447 // * handle for the end node
1448 var endHandle = { x: 0, y: 0 };
1449 // One can visualize it as follows:
1465 // NOTE: (0, 0) is the top left corner of the window.
1467 // We have 3 similar, but still different scenarios to cover:
1469 // 1. Two tokens on different lines.
1474 // In this situation, we draw arrow on the left curving to the left.
1475 // 2. Two tokens on the same line, and the destination is on the right.
1480 // In this situation, we draw arrow above curving upwards.
1481 // 3. Two tokens on the same line, and the destination is on the left.
1485 // In this situation, we draw arrow below curving downwards.
1486 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1487 startRect.top >= endRect.top + 5;
1488 const leftToRight = startRect.left < endRect.left;
1490 // NOTE: various magic constants are chosen empirically for
1491 // better positioning and look
1492 if (onDifferentLines) {
1494 const topToBottom = startRect.top < endRect.top;
1495 posStart.x = startRect.left - 1;
1496 // We don't want to start it at the top left corner of the token,
1497 // it doesn't feel like this is where the arrow comes from.
1498 // For this reason, we start it in the middle of the left side
1500 posStart.y = startRect.top + startRect.height / 2;
1502 // End node has arrow head and we give it a bit more space.
1503 posEnd.x = endRect.left - 4;
1504 posEnd.y = endRect.top;
1506 // Utility object with x and y offsets for handles.
1508 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1509 // overlap much with top-to-bottom curves (much more frequent).
1510 x: topToBottom ? 15 : 25,
1511 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1514 // When destination is on the different line, we can make a
1515 // curvier arrow because we have space for it.
1516 // So, instead of using
1518 // startHandle.x = posStart.x - curvature.x
1519 // endHandle.x = posEnd.x - curvature.x
1521 // We use the leftmost of these two values for both handles.
1522 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1523 endHandle.x = startHandle.x;
1525 // Curving downwards from the start node...
1526 startHandle.y = posStart.y + curvature.y;
1527 // ... and upwards from the end node.
1528 endHandle.y = posEnd.y - curvature.y;
1530 } else if (leftToRight) {
1532 // Starting from the top right corner...
1533 posStart.x = startRect.right - 1;
1534 posStart.y = startRect.top;
1536 // ...and ending at the top left corner of the end token.
1537 posEnd.x = endRect.left + 1;
1538 posEnd.y = endRect.top - 1;
1540 // Utility object with x and y offsets for handles.
1542 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1546 // Curving to the right...
1547 startHandle.x = posStart.x + curvature.x;
1548 // ... and upwards from the start node.
1549 startHandle.y = posStart.y - curvature.y;
1551 // And to the left...
1552 endHandle.x = posEnd.x - curvature.x;
1553 // ... and upwards from the end node.
1554 endHandle.y = posEnd.y - curvature.y;
1558 // Starting from the bottom right corner...
1559 posStart.x = startRect.right;
1560 posStart.y = startRect.bottom;
1562 // ...and ending also at the bottom right corner, but of the end token.
1563 posEnd.x = endRect.right - 1;
1564 posEnd.y = endRect.bottom + 1;
1566 // Utility object with x and y offsets for handles.
1568 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1572 // Curving to the left...
1573 startHandle.x = posStart.x - curvature.x;
1574 // ... and downwards from the start node.
1575 startHandle.y = posStart.y + curvature.y;
1577 // And to the right...
1578 endHandle.x = posEnd.x + curvature.x;
1579 // ... and downwards from the end node.
1580 endHandle.y = posEnd.y + curvature.y;
1583 // Put it all together into a path.
1584 // More information on the format:
1585 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1586 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1587 "C" + startHandle.x + "," + startHandle.y + " " +
1588 endHandle.x + "," + endHandle.y + " " +
1589 posEnd.x + "," + posEnd.y;
1591 arrow.setAttribute("d", pathStr);
1594var drawArrows = function() {
1595 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1596 for (var i = 0; i < numOfArrows; ++i) {
1601var toggleArrows = function(event) {
1602 const arrows = document.querySelector("#arrows");
1603 if (event.target.checked) {
1604 arrows.setAttribute("visibility", "visible");
1606 arrows.setAttribute("visibility", "hidden");
1610window.addEventListener("resize", drawArrows);
1611document.addEventListener("DOMContentLoaded", function() {
1612 // Whenever we show invocation, locations change, i.e. we
1613 // need to redraw arrows.
1615 .querySelector('input[id="showinvocation"]')
1616 .addEventListener("click", drawArrows);
1617 // Hiding irrelevant lines also should cause arrow rerender.
1619 .querySelector('input[name="showCounterexample"]')
1620 .addEventListener("change", drawArrows);
1622 .querySelector('input[name="showArrows"]')
1623 .addEventListener("change", toggleArrows);
1625 // Default highlighting for the last event.
1626 highlightArrowsForSelectedEvent();
static bool shouldDisplayPopUpRange(const SourceRange &Range)
static void EmitAlphaCounter(raw_ostream &os, unsigned n)
static std::string getSpanBeginForControl(const char *ClassName, unsigned Index)
static void createHTMLDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, bool SupportMultipleFiles)
Creates and registers an HTML diagnostic consumer, without any additional text consumer.
static void HandlePopUpPieceStartTag(Rewriter &R, const std::vector< SourceRange > &PopUpRanges)
static std::string getSpanBeginForControlEnd(unsigned Index)
static void HandlePopUpPieceEndTag(Rewriter &R, const PathDiagnosticPopUpPiece &Piece, std::vector< SourceRange > &PopUpRanges, unsigned int LastReportedPieceIndex, unsigned int PopUpPieceIndex)
static std::string getSpanBeginForControlStart(unsigned Index)
#define HTML_DIAGNOSTICS_NAME
Result
Implement __builtin_bit_cast and related operations.
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified.
static DiagnosticBuilder Diag(DiagnosticsEngine *Diags, const LangOptions &Features, FullSourceLoc TokLoc, const char *TokBegin, const char *TokRangeBegin, const char *TokRangeEnd, unsigned DiagID)
Produce a diagnostic highlighting some portion of a literal.
Defines the clang::Preprocessor interface.
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
__DEVICE__ int max(int __a, int __b)
static CharSourceRange getTokenRange(SourceRange R)
SourceLocation getEnd() const
SourceLocation getBegin() const
StringRef getName() const
The name of this FileEntry.
FullSourceLoc getExpansionLoc() const
const char * getCharacterData(bool *Invalid=nullptr) const
unsigned getExpansionColumnNumber(bool *Invalid=nullptr) const
StringRef getBufferData(bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
FileIDAndOffset getDecomposedLoc() const
Decompose the specified location into a raw FileID + Offset pair.
const SourceManager & getManager() const
unsigned getExpansionLineNumber(bool *Invalid=nullptr) const
static CharSourceRange getAsCharRange(SourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Given a token range, produce a corresponding CharSourceRange that is not a token range.
MacroExpansionContext tracks the macro expansions processed by the Preprocessor.
Engages in a tight little dance with the lexer to efficiently preprocess tokens.
SourceManager & getSourceManager() const
const LangOptions & getLangOpts() const
bool isValid() const
Return true if this is a valid SourceLocation object.
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
FileIDAndOffset getDecomposedExpansionLoc(SourceLocation Loc) const
Decompose the specified location into a raw FileID + Offset pair.
unsigned getColumnNumber(FileID FID, unsigned FilePos, bool *Invalid=nullptr) const
Return the column # for the specified file position.
OptionalFileEntryRef getFileEntryRefForID(FileID FID) const
Returns the FileEntryRef for the provided FileID.
SourceLocation getLocForEndOfFile(FileID FID) const
Return the source location corresponding to the last byte of the specified file.
llvm::MemoryBufferRef getBufferOrFake(FileID FID, SourceLocation Loc=SourceLocation()) const
Return the buffer for the specified FileID.
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file.
SourceLocation getExpansionLoc(SourceLocation Loc) const
Given a SourceLocation object Loc, return the expansion location referenced by the ID.
unsigned getLength() const
This class is used for tools that requires cross translation unit capability.
PathDiagnosticRange asRange() const
FullSourceLoc asLocation() const
ArrayRef< SourceRange > getRanges() const
Return the SourceRanges associated with this PathDiagnosticPiece.
virtual PathDiagnosticLocation getLocation() const =0
StringRef getString() const
PathDiagnosticLocation getLocation() const override
meta_iterator meta_end() const
PathDiagnosticLocation getUniqueingLoc() const
Get the location on which the report should be uniqued.
StringRef getVerboseDescription() const
const Decl * getDeclWithIssue() const
Return the semantic context where an issue occurred.
const FilesToLineNumsMap & getExecutedLines() const
StringRef getBugType() const
StringRef getCategory() const
meta_iterator meta_begin() const
SmallString< 32 > getIssueHash(const SourceManager &SrcMgr, const LangOptions &LangOpts) const
Get a hash that identifies the issue.
PathDiagnosticLocation getLocation() const
PathPieces flatten(bool ShouldFlattenMacros) const
std::vector< std::unique_ptr< PathDiagnosticConsumer > > PathDiagnosticConsumers
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
void createPlistDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, const MacroExpansionContext &MacroExpansions, bool SupportsMultipleFiles)
Creates and registers a Plist diagnostic consumer, without any additional text consumer.
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
std::map< FileID, std::set< unsigned > > FilesToLineNumsMap
File IDs mapped to sets of line numbers.
void createSarifDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP)
Creates and registers a SARIF diagnostic consumer, without any additional text consumer.
void AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID, StringRef title)
void HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E, const char *StartTag, const char *EndTag, bool IsTokenRange=true)
HighlightRange - Highlight a range in the source code with the specified start/end tags.
RelexRewriteCacheRef instantiateRelexRewriteCache()
If you need to rewrite the same file multiple times, you can instantiate a RelexRewriteCache and refe...
void AddLineNumbers(Rewriter &R, FileID FID)
void SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
SyntaxHighlight - Relex the specified FileID and annotate the HTML with information about keywords,...
void HighlightMacros(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
HighlightMacros - This uses the macro table state from the end of the file, to reexpand macros and in...
void EscapeText(Rewriter &R, FileID FID, bool EscapeSpaces=false, bool ReplaceTabs=false)
EscapeText - HTMLize a specified file so that special characters are are translated so that they are ...
std::shared_ptr< RelexRewriteCache > RelexRewriteCacheRef
StringRef getName(const HeaderType T)
@ Number
Just a number, nothing else.
Top level wrappers for InstallAPI frontend operations.
bool isa(CodeGen::Address addr)
CustomizableOptional< FileEntryRef > OptionalFileEntryRef
std::pair< FileID, unsigned > FileIDAndOffset
const StreamingDiagnostic & operator<<(const StreamingDiagnostic &DB, const ConceptReference *C)
Insertion operator for diagnostics.
U cast(CodeGen::Address addr)
std::optional< CharSourceRange > getExpansionRangeInFile(CharSourceRange Range, FileID FID, const SourceManager &SM)
Maps both endpoints of Range to their macro expansion, so that the range can be shown to a user.
These options tweak the behavior of path diangostic consumers.
bool ShouldWriteVerboseReportFilename
If the consumer intends to produce multiple output files, should it use a pseudo-random file name or ...
std::string ToolInvocation
Run-line of the tool that produced the diagnostic.