31#include "llvm/ADT/RewriteBuffer.h"
32#include "llvm/ADT/STLExtras.h"
33#include "llvm/ADT/Sequence.h"
34#include "llvm/ADT/SmallString.h"
35#include "llvm/ADT/StringRef.h"
36#include "llvm/ADT/iterator_range.h"
37#include "llvm/Support/Errc.h"
38#include "llvm/Support/ErrorHandling.h"
39#include "llvm/Support/FileSystem.h"
40#include "llvm/Support/IOSandbox.h"
41#include "llvm/Support/Path.h"
42#include "llvm/Support/raw_ostream.h"
48#include <system_error>
54using llvm::RewriteBuffer;
65 PathDiagnosticConsumerOptions DiagOpts;
66 std::string Directory;
67 bool createdDir =
false;
69 const Preprocessor &PP;
70 const bool SupportsCrossFileDiagnostics;
71 llvm::StringSet<> EmittedHashes;
76 HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,
77 const std::string &OutputDir,
const Preprocessor &pp,
78 bool supportsMultipleFiles)
79 : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),
80 SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
82 ~HTMLDiagnostics()
override { FlushDiagnostics(
nullptr); }
84 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
85 FilesMade *filesMade)
override;
89 bool supportsCrossFileDiagnostics()
const override {
90 return SupportsCrossFileDiagnostics;
93 unsigned ProcessMacroPiece(raw_ostream &os,
const PathDiagnosticMacroPiece &P,
96 unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID,
97 const PathDiagnosticControlFlowPiece &P,
100 void HandlePiece(Rewriter &R, FileID BugFileID,
const PathDiagnosticPiece &P,
101 const std::vector<SourceRange> &PopUpRanges,
unsigned num,
104 void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range,
105 const char *HighlightStart =
"<span class=\"mrange\">",
106 const char *HighlightEnd =
"</span>");
108 void ReportDiag(
const PathDiagnostic &D, FilesMade *filesMade);
111 std::string GenerateHTML(
const PathDiagnostic &D, Rewriter &R,
112 const SourceManager &SMgr,
const PathPieces &path,
113 const char *declName);
116 void FinalizeHTML(
const PathDiagnostic &D, Rewriter &R,
117 const SourceManager &SMgr,
const PathPieces &path,
118 FileID FID, FileEntryRef Entry,
const char *declName);
121 void RewriteFile(Rewriter &R,
const PathPieces &path, FileID FID);
123 PathGenerationScheme getGenerationScheme()
const override {
128 void addArrowSVGs(Rewriter &R, FileID BugFileID,
129 const ArrowMap &ArrowIndices);
132 StringRef showHelpJavascript();
135 StringRef generateKeyboardNavigationJavascript();
138 StringRef generateArrowDrawingJavascript();
141 std::string showRelevantLinesJavascript(
const PathDiagnostic &D,
142 const PathPieces &path);
145 void dumpCoverageData(
const PathDiagnostic &D,
const PathPieces &path,
146 llvm::raw_string_ostream &os);
153unsigned getPathSizeWithoutArrows(
const PathPieces &Path) {
154 unsigned TotalPieces = Path.size();
155 unsigned TotalArrowPieces = llvm::count_if(
157 return TotalPieces - TotalArrowPieces;
160class ArrowMap :
public std::vector<unsigned> {
161 using Base = std::vector<unsigned>;
164 ArrowMap(
unsigned Size) : Base(
Size, 0) {}
165 unsigned getTotalNumberOfArrows()
const {
return at(0); }
168llvm::raw_ostream &
operator<<(llvm::raw_ostream &
OS,
const ArrowMap &Indices) {
170 llvm::interleave(Indices,
OS,
",");
181 bool SupportMultipleFiles) {
184 if (OutputDir.empty())
187 C.emplace_back(std::make_unique<HTMLDiagnostics>(
188 std::move(DiagOpts), OutputDir, PP, SupportMultipleFiles));
191void ento::createHTMLDiagnosticConsumer(
202 createTextMinimalPathDiagnosticConsumer(DiagOpts,
C, OutputDir, PP, CTU,
209void ento::createHTMLSingleFileDiagnosticConsumer(
214 createTextMinimalPathDiagnosticConsumer(DiagOpts,
C, OutputDir, PP, CTU,
221void ento::createPlistHTMLDiagnosticConsumer(
227 DiagOpts,
C, std::string(llvm::sys::path::parent_path(prefix)), PP,
true);
229 MacroExpansions,
true);
230 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts),
C, prefix, PP,
231 CTU, MacroExpansions);
234void ento::createSarifHTMLDiagnosticConsumer(
240 DiagOpts,
C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,
244 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts),
C, sarif_file,
245 PP, CTU, MacroExpansions);
252void HTMLDiagnostics::FlushDiagnosticsImpl(
253 std::vector<const PathDiagnostic *> &Diags,
254 FilesMade *filesMade) {
255 for (
const auto Diag : Diags)
256 ReportDiag(*
Diag, filesMade);
259void HTMLDiagnostics::ReportDiag(
const PathDiagnostic& D,
260 FilesMade *filesMade) {
262 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
267 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
268 llvm::errs() <<
"warning: could not create directory '"
269 << Directory <<
"': " << ec.message() <<
'\n';
282 assert(!path.empty());
283 const SourceManager &SMgr = path.front()->getLocation().getManager();
286 Rewriter
R(
const_cast<SourceManager&
>(SMgr), PP.
getLangOpts());
289 SmallString<128> declName(
"unknown");
292 if (
const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
293 declName = ND->getDeclName().getAsString();
295 if (
const Stmt *Body = DeclWithIssue->getBody()) {
302 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
306 SmallString<32> IssueHash =
308 auto [It, IsNew] = EmittedHashes.insert(IssueHash);
314 std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
315 if (report.empty()) {
316 llvm::errs() <<
"warning: no diagnostics generated for main file.\n";
323 SmallString<128> FileNameStr;
324 llvm::raw_svector_ostream
FileName(FileNameStr);
337 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
342 << declName.c_str() <<
"-" << offsetDecl <<
"-";
345 FileName << StringRef(IssueHash).substr(0, 6).str() <<
".html";
347 SmallString<128> ResultPath;
348 llvm::sys::path::append(ResultPath, Directory,
FileName.str());
349 if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) {
350 llvm::errs() <<
"warning: could not make '" << ResultPath
351 <<
"' absolute: " << EC.message() <<
'\n';
355 if (std::error_code EC = llvm::sys::fs::openFileForReadWrite(
356 ResultPath, FD, llvm::sys::fs::CD_CreateNew,
357 llvm::sys::fs::OF_Text)) {
363 if (EC != llvm::errc::file_exists) {
364 llvm::errs() <<
"warning: could not create file in '" << Directory
365 <<
"': " << EC.message() <<
'\n';
366 }
else if (filesMade) {
370 filesMade->addDiagnostic(D,
getName(),
371 llvm::sys::path::filename(ResultPath));
376 llvm::raw_fd_ostream os(FD,
true);
379 filesMade->addDiagnostic(D,
getName(),
380 llvm::sys::path::filename(ResultPath));
386std::string HTMLDiagnostics::GenerateHTML(
const PathDiagnostic& D, Rewriter &R,
387 const SourceManager& SMgr,
const PathPieces& path,
const char *declName) {
389 std::vector<FileID> FileIDs;
390 for (
auto I : path) {
391 FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
392 if (llvm::is_contained(FileIDs, FID))
395 FileIDs.push_back(FID);
396 RewriteFile(R, path, FID);
399 if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
401 for (
auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
403 llvm::raw_string_ostream os(s);
405 if (I != FileIDs.begin())
406 os <<
"<hr class=divider>\n";
408 os <<
"<div id=File" << I->getHashValue() <<
">\n";
411 if (I != FileIDs.begin())
412 os <<
"<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
413 <<
"\">←</a></div>";
420 os <<
"<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
421 <<
"\">→</a></div>";
429 for (
auto I : llvm::drop_begin(FileIDs)) {
431 llvm::raw_string_ostream os(s);
433 const RewriteBuffer *Buf =
R.getRewriteBufferFor(I);
441 const RewriteBuffer *Buf =
R.getRewriteBufferFor(FileIDs[0]);
447 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
449 FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName);
452 llvm::raw_string_ostream os(file);
459void HTMLDiagnostics::dumpCoverageData(
460 const PathDiagnostic &D,
461 const PathPieces &path,
462 llvm::raw_string_ostream &os) {
466 os <<
"var relevant_lines = {";
467 for (
auto I = ExecutedLines.begin(),
468 E = ExecutedLines.end(); I != E; ++I) {
469 if (I != ExecutedLines.begin())
472 os <<
"\"" << I->first.getHashValue() <<
"\": {";
473 for (
unsigned LineNo : I->second) {
474 if (LineNo != *(I->second.begin()))
477 os <<
"\"" << LineNo <<
"\": 1";
485std::string HTMLDiagnostics::showRelevantLinesJavascript(
486 const PathDiagnostic &D,
const PathPieces &path) {
488 llvm::raw_string_ostream os(s);
489 os <<
"<script type='text/javascript'>\n";
490 dumpCoverageData(D, path, os);
493var filterCounterexample = function (hide) {
494 var tables = document.getElementsByClassName("code");
495 for (var t=0; t<tables.length; t++) {
496 var table = tables[t];
497 var file_id = table.getAttribute("data-fileid");
498 var lines_in_fid = relevant_lines[file_id];
502 var lines = table.getElementsByClassName("codeline");
503 for (var i=0; i<lines.length; i++) {
505 var lineNo = el.getAttribute("data-linenumber");
506 if (!lines_in_fid[lineNo]) {
508 el.setAttribute("hidden", "");
510 el.removeAttribute("hidden");
517window.addEventListener("keydown", function (event) {
518 if (event.defaultPrevented) {
522 if (event.shiftKey && event.keyCode == 83) {
523 var checked = document.getElementsByName("showCounterexample")[0].checked;
524 filterCounterexample(!checked);
525 document.getElementsByName("showCounterexample")[0].click();
529 event.preventDefault();
532document.addEventListener("DOMContentLoaded", function() {
533 document.querySelector('input[name="showCounterexample"]').onchange=
535 filterCounterexample(this.checked);
541 <input type="checkbox" name="showCounterexample" id="showCounterexample" />
542 <label for="showCounterexample">
543 Show only relevant lines
545 <input type="checkbox" name="showArrows"
546 id="showArrows" style="margin-left: 10px" />
547 <label for="showArrows">
548 Show control flow arrows
556void HTMLDiagnostics::FinalizeHTML(
const PathDiagnostic &D, Rewriter &R,
557 const SourceManager &SMgr,
558 const PathPieces &path, FileID FID,
559 FileEntryRef Entry,
const char *declName) {
564 llvm::SmallString<0> DirName;
566 if (llvm::sys::path::is_relative(Entry.
getName())) {
567 llvm::sys::fs::current_path(DirName);
571 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
572 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
577 generateKeyboardNavigationJavascript());
580 generateArrowDrawingJavascript());
584 showRelevantLinesJavascript(D, path));
589 llvm::raw_string_ostream os(s);
591 os <<
"<!-- REPORTHEADER -->\n"
592 <<
"<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
593 "<tr><td class=\"rowname\">File:</td><td>"
596 <<
"</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
597 "<a href=\"#EndPath\">line "
605 unsigned NumExtraPieces = 0;
606 for (
const auto &Piece : path) {
607 if (
const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
613 os <<
"<tr><td class=\"rowname\">Note:</td><td>"
614 <<
"<a href=\"#Note" << NumExtraPieces <<
"\">line "
615 << LineNumber <<
", column " << ColumnNumber <<
"</a><br />"
622 for (
const std::string &Metadata :
629<!-- REPORTSUMMARYEXTRA -->
630<h3>Annotated Source Code</h3>
631<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
632 to see keyboard shortcuts</p>
633<input type="checkbox" class="spoilerhider" id="showinvocation" />
634<label for="showinvocation" >Show analyzer invocation</label>
635<div class="spoiler">clang -cc1 )<<<";
639<div id='tooltiphint' hidden="true">
640 <p>Keyboard shortcuts: </p>
642 <li>Use 'j/k' keys for keyboard navigation</li>
643 <li>Use 'Shift+S' to show/hide relevant lines</li>
644 <li>Use '?' to toggle this window</li>
646 <a href="#" onclick="toggleHelp(); return false;">Close</a>
656 llvm::raw_string_ostream os(s);
659 if (!BugDesc.empty())
660 os <<
"\n<!-- BUGDESC " << BugDesc <<
" -->\n";
663 if (!BugType.empty())
664 os <<
"\n<!-- BUGTYPE " << BugType <<
" -->\n";
673 if (!BugCategory.empty())
674 os <<
"\n<!-- BUGCATEGORY " << BugCategory <<
" -->\n";
676 os <<
"\n<!-- BUGFILE " << DirName << Entry.
getName() <<
" -->\n";
678 os <<
"\n<!-- FILENAME " << llvm::sys::path::filename(Entry.
getName()) <<
" -->\n";
680 os <<
"\n<!-- FUNCTIONNAME " << declName <<
" -->\n";
682 os <<
"\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT "
685 os <<
"\n<!-- BUGLINE "
689 os <<
"\n<!-- BUGCOLUMN "
693 os <<
"\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) <<
" -->\n";
696 os <<
"\n<!-- BUGMETAEND -->\n";
705StringRef HTMLDiagnostics::showHelpJavascript() {
707<script type='text/javascript'>
709var toggleHelp = function() {
710 var hint = document.querySelector("#tooltiphint");
711 var attributeName = "hidden";
712 if (hint.hasAttribute(attributeName)) {
713 hint.removeAttribute(attributeName);
715 hint.setAttribute("hidden", "true");
718window.addEventListener("keydown", function (event) {
719 if (event.defaultPrevented) {
722 if (event.key == "?") {
727 event.preventDefault();
734 return !(
Range.getBegin().isMacroID() ||
Range.getEnd().isMacroID());
739 const std::vector<SourceRange> &PopUpRanges) {
740 for (
const auto &Range : PopUpRanges) {
745 "<table class='variable_popup'><tbody>",
751 const PathDiagnosticPopUpPiece &Piece,
752 std::vector<SourceRange> &PopUpRanges,
753 unsigned int LastReportedPieceIndex,
754 unsigned int PopUpPieceIndex) {
755 SmallString<256> Buf;
756 llvm::raw_svector_ostream
Out(Buf);
763 Out <<
"<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
764 << LastReportedPieceIndex;
767 Out <<
'.' << PopUpPieceIndex;
769 Out <<
"</div></td><td>" << Piece.
getString() <<
"</td></tr>";
772 if (!llvm::is_contained(PopUpRanges, Range)) {
774 PopUpRanges.push_back(Range);
776 Out <<
"</tbody></table></span>";
778 "<span class='variable'>", Buf.c_str(),
787void HTMLDiagnostics::RewriteFile(Rewriter &R,
const PathPieces &path,
796 unsigned TotalPieces = getPathSizeWithoutArrows(path);
797 unsigned TotalNotePieces =
801 unsigned PopUpPieceCount =
806 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
807 unsigned NumRegularPieces = TotalRegularPieces;
808 unsigned NumNotePieces = TotalNotePieces;
809 unsigned NumberOfArrows = 0;
811 std::map<int, int> IndexMap;
812 ArrowMap ArrowIndices(TotalRegularPieces + 1);
815 std::vector<SourceRange> PopUpRanges;
817 const auto &Piece = *I.get();
820 ++IndexMap[NumRegularPieces];
825 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
828 }
else if (isArrowPiece(Piece)) {
829 NumberOfArrows = ProcessControlFlowPiece(
831 ArrowIndices[NumRegularPieces] = NumberOfArrows;
834 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
837 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
840 ArrowIndices[0] = NumberOfArrows;
850 assert(ArrowIndices.back() == 0 &&
851 "No arrows should be after the last event");
853 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
854 "Incorrect arrow indices map");
858 NumRegularPieces = TotalRegularPieces;
860 const auto &Piece = *I.get();
862 if (
const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
863 int PopUpPieceIndex = IndexMap[NumRegularPieces];
873 if (PopUpPieceIndex > 0)
874 --IndexMap[NumRegularPieces];
884 addArrowSVGs(R, FID, ArrowIndices);
893void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
894 const PathDiagnosticPiece &P,
895 const std::vector<SourceRange> &PopUpRanges,
896 unsigned num,
unsigned max) {
904 SourceManager &
SM =
R.getSourceMgr();
905 assert(&Pos.
getManager() == &
SM &&
"SourceManagers are different!");
908 if (LPosInfo.first != BugFileID)
911 llvm::MemoryBufferRef Buf =
SM.getBufferOrFake(LPosInfo.first);
912 const char *FileStart = Buf.getBufferStart();
916 unsigned ColNo =
SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
918 const char *LineStart = TokInstantiationPtr-ColNo;
921 const char *LineEnd = TokInstantiationPtr;
922 const char *FileEnd = Buf.getBufferEnd();
923 while (*LineEnd !=
'\n' && LineEnd != FileEnd)
928 for (
const char* c = LineStart; c != TokInstantiationPtr; ++c)
929 PosNo += *c ==
'\t' ? 8 : 1;
933 const char *
Kind =
nullptr;
935 bool SuppressIndex = (
max == 1);
944 SuppressIndex =
true;
948 llvm_unreachable(
"Calls and extra notes should already be handled");
952 llvm::raw_string_ostream os(sbuf);
954 os <<
"\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
963 os <<
"\" class=\"msg";
965 os <<
" msg" <<
Kind;
966 os <<
"\" style=\"margin-left:" << PosNo <<
"ex";
972 unsigned max_token = 0;
974 unsigned len = Msg.size();
984 if (cnt > max_token) max_token = cnt;
993 const unsigned max_line = 120;
995 if (max_token >= max_line)
998 unsigned characters = max_line;
999 unsigned lines = len / max_line;
1002 for (; characters > max_token; --characters)
1003 if (len / characters > lines) {
1009 em = characters / 2;
1012 if (em < max_line/2)
1013 os <<
"; max-width:" << em <<
"em";
1016 os <<
"; max-width:100em";
1020 if (!SuppressIndex) {
1021 os <<
"<table class=\"msgT\"><tr><td valign=\"top\">";
1022 os <<
"<div class=\"PathIndex";
1023 if (Kind) os <<
" PathIndex" << Kind;
1024 os <<
"\">" << num <<
"</div>";
1027 os <<
"</td><td><div class=\"PathNav\"><a href=\"#Path"
1029 <<
"\" title=\"Previous event ("
1031 <<
")\">←</a></div>";
1037 if (
const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
1038 os <<
"Within the expansion of the macro '";
1042 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
1046 const char* MacroName = LocInfo.second + BufferInfo.data();
1047 Lexer rawLexer(
SM.getLocForStartOfFile(LocInfo.first), PP.
getLangOpts(),
1048 BufferInfo.begin(), MacroName, BufferInfo.end());
1051 rawLexer.LexFromRawLexer(TheTok);
1052 for (
unsigned i = 0, n = TheTok.
getLength(); i < n; ++i)
1058 if (!SuppressIndex) {
1061 os <<
"<td><div class=\"PathNav\"><a href=\"#";
1065 os <<
"Path" << (num + 1);
1066 os <<
"\" title=\"Next event ("
1068 <<
")\">→</a></div></td>";
1071 os <<
"</tr></table>";
1075 ProcessMacroPiece(os, *MP, 0);
1080 if (!SuppressIndex) {
1083 os <<
"<td><div class=\"PathNav\"><a href=\"#";
1087 os <<
"Path" << (num + 1);
1088 os <<
"\" title=\"Next event ("
1090 <<
")\">→</a></div></td>";
1093 os <<
"</tr></table>";
1097 os <<
"</div></td></tr>";
1101 unsigned DisplayPos = LineEnd - FileStart;
1102 if (LineEnd != FileEnd)
1104 SourceLocation Loc =
1105 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
1107 R.InsertTextBefore(Loc, os.str());
1110 ArrayRef<SourceRange> Ranges = P.
getRanges();
1111 for (
const auto &Range : Ranges) {
1113 if (llvm::is_contained(PopUpRanges, Range))
1121 unsigned x = n % (
'z' -
'a');
1127 os << char(
'a' + x);
1130unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1131 const PathDiagnosticMacroPiece& P,
1133 for (
const auto &subPiece : P.
subPieces) {
1134 if (
const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1135 num = ProcessMacroPiece(os, *MP, num);
1139 if (
const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1140 os <<
"<div class=\"msg msgEvent\" style=\"width:94%; "
1141 "margin-left:5px\">"
1142 "<table class=\"msgT\"><tr>"
1143 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1145 os <<
"</div></td><td valign=\"top\">"
1147 <<
"</td></tr></table></div>\n";
1154void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1155 const ArrowMap &ArrowIndices) {
1157 llvm::raw_string_ostream
OS(S);
1160<style type="text/css">
1167 pointer-events: none;
1171 stroke-opacity: 0.2;
1173 marker-end: url(#arrowhead);
1177 stroke-opacity: 0.6;
1179 marker-end: url(#arrowheadSelected);
1189<svg xmlns="http://www.w3.org/2000/svg">
1191 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1192 viewBox="0 0 10 10" refX="3" refY="5"
1193 markerWidth="4" markerHeight="4">
1194 <path d="M 0 0 L 10 5 L 0 10 z" />
1196 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1197 viewBox="0 0 10 10" refX="3" refY="5"
1198 markerWidth="4" markerHeight="4">
1199 <path d="M 0 0 L 10 5 L 0 10 z" />
1202 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1205 for (
unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1206 OS <<
" <path class=\"arrow\" id=\"arrow" << Index <<
"\"/>\n";
1212<script type='text/javascript'>
1213const arrowIndices = )<<<";
1215 OS << ArrowIndices << "\n</script>\n";
1217 R.InsertTextBefore(
R.getSourceMgr().getLocForStartOfFile(BugFileID),
1224 llvm::raw_string_ostream
OS(
Result);
1225 OS <<
"<span id=\"" << ClassName << Index <<
"\">";
1237unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1238 Rewriter &R, FileID BugFileID,
const PathDiagnosticControlFlowPiece &P,
1240 for (
const PathDiagnosticLocationPair &LPair : P) {
1244 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1246 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1253void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1255 const char *HighlightStart,
1256 const char *HighlightEnd) {
1257 SourceManager &
SM =
R.getSourceMgr();
1258 const LangOptions &LangOpts =
R.getLangOpts();
1260 SourceLocation InstantiationStart =
SM.getExpansionLoc(
Range.getBegin());
1261 unsigned StartLineNo =
SM.getExpansionLineNumber(InstantiationStart);
1263 SourceLocation InstantiationEnd =
SM.getExpansionLoc(
Range.getEnd());
1264 unsigned EndLineNo =
SM.getExpansionLineNumber(InstantiationEnd);
1266 if (EndLineNo < StartLineNo)
1269 if (
SM.getFileID(InstantiationStart) != BugFileID ||
1270 SM.getFileID(InstantiationEnd) != BugFileID)
1274 unsigned EndColNo =
SM.getExpansionColumnNumber(InstantiationEnd);
1275 unsigned OldEndColNo = EndColNo;
1291StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1293<script type='text/javascript'>
1294var digitMatcher = new RegExp("[0-9]+");
1296var querySelectorAllArray = function(selector) {
1297 return Array.prototype.slice.call(
1298 document.querySelectorAll(selector));
1301document.addEventListener("DOMContentLoaded", function() {
1302 querySelectorAllArray(".PathNav > a").forEach(
1303 function(currentValue, currentIndex) {
1304 var hrefValue = currentValue.getAttribute("href");
1305 currentValue.onclick = function() {
1306 scrollTo(document.querySelector(hrefValue));
1312var findNum = function() {
1313 var s = document.querySelector(".msg.selected");
1314 if (!s || s.id == "EndPath") {
1317 var out = parseInt(digitMatcher.exec(s.id)[0]);
1321var classListAdd = function(el, theClass) {
1322 if(!el.className.baseVal)
1323 el.className += " " + theClass;
1325 el.className.baseVal += " " + theClass;
1328var classListRemove = function(el, theClass) {
1329 var className = (!el.className.baseVal) ?
1330 el.className : el.className.baseVal;
1331 className = className.replace(" " + theClass, "");
1332 if(!el.className.baseVal)
1333 el.className = className;
1335 el.className.baseVal = className;
1338var scrollTo = function(el) {
1339 querySelectorAllArray(".selected").forEach(function(s) {
1340 classListRemove(s, "selected");
1342 classListAdd(el, "selected");
1343 window.scrollBy(0, el.getBoundingClientRect().top -
1344 (window.innerHeight / 2));
1345 highlightArrowsForSelectedEvent();
1348var move = function(num, up, numItems) {
1349 if (num == 1 && up || num == numItems - 1 && !up) {
1351 } else if (num == 0 && up) {
1352 return numItems - 1;
1353 } else if (num == 0 && !up) {
1354 return 1 % numItems;
1356 return up ? num - 1 : num + 1;
1359var numToId = function(num) {
1361 return document.getElementById("EndPath")
1363 return document.getElementById("Path" + num);
1366var navigateTo = function(up) {
1367 var numItems = document.querySelectorAll(
1368 ".line > .msgEvent, .line > .msgControl").length;
1369 var currentSelected = findNum();
1370 var newSelected = move(currentSelected, up, numItems);
1371 var newEl = numToId(newSelected, numItems);
1373 // Scroll element into center.
1377window.addEventListener("keydown", function (event) {
1378 if (event.defaultPrevented) {
1382 if (event.keyCode == 74) {
1383 navigateTo(/*up=*/false);
1385 } else if (event.keyCode == 75) {
1386 navigateTo(/*up=*/true);
1390 event.preventDefault();
1396StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1398<script type='text/javascript'>
1399// Return range of numbers from a range [lower, upper).
1400function range(lower, upper) {
1402 for (var i = lower; i <= upper; ++i) {
1408var getRelatedArrowIndices = function(pathId) {
1409 // HTML numeration of events is a bit different than it is in the path.
1410 // Everything is rotated one step to the right, so the last element
1411 // (error diagnostic) has index 0.
1413 // arrowIndices has at least 2 elements
1414 pathId = arrowIndices.length - 1;
1417 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1420var highlightArrowsForSelectedEvent = function() {
1421 const selectedNum = findNum();
1422 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1423 arrowIndicesToHighlight.forEach((index) => {
1424 var arrow = document.querySelector("#arrow" + index);
1426 classListAdd(arrow, "selected")
1431var getAbsoluteBoundingRect = function(element) {
1432 const relative = element.getBoundingClientRect();
1434 left: relative.left + window.pageXOffset,
1435 right: relative.right + window.pageXOffset,
1436 top: relative.top + window.pageYOffset,
1437 bottom: relative.bottom + window.pageYOffset,
1438 height: relative.height,
1439 width: relative.width
1443var drawArrow = function(index) {
1444 // This function is based on the great answer from SO:
1445 // https://stackoverflow.com/a/39575674/11582326
1446 var start = document.querySelector("#start" + index);
1447 var end = document.querySelector("#end" + index);
1448 var arrow = document.querySelector("#arrow" + index);
1450 var startRect = getAbsoluteBoundingRect(start);
1451 var endRect = getAbsoluteBoundingRect(end);
1453 // It is an arrow from a token to itself, no need to visualize it.
1454 if (startRect.top == endRect.top &&
1455 startRect.left == endRect.left)
1458 // Each arrow is a very simple Bézier curve, with two nodes and
1459 // two handles. So, we need to calculate four points in the window:
1461 var posStart = { x: 0, y: 0 };
1463 var posEnd = { x: 0, y: 0 };
1464 // * handle for the start node
1465 var startHandle = { x: 0, y: 0 };
1466 // * handle for the end node
1467 var endHandle = { x: 0, y: 0 };
1468 // One can visualize it as follows:
1484 // NOTE: (0, 0) is the top left corner of the window.
1486 // We have 3 similar, but still different scenarios to cover:
1488 // 1. Two tokens on different lines.
1493 // In this situation, we draw arrow on the left curving to the left.
1494 // 2. Two tokens on the same line, and the destination is on the right.
1499 // In this situation, we draw arrow above curving upwards.
1500 // 3. Two tokens on the same line, and the destination is on the left.
1504 // In this situation, we draw arrow below curving downwards.
1505 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1506 startRect.top >= endRect.top + 5;
1507 const leftToRight = startRect.left < endRect.left;
1509 // NOTE: various magic constants are chosen empirically for
1510 // better positioning and look
1511 if (onDifferentLines) {
1513 const topToBottom = startRect.top < endRect.top;
1514 posStart.x = startRect.left - 1;
1515 // We don't want to start it at the top left corner of the token,
1516 // it doesn't feel like this is where the arrow comes from.
1517 // For this reason, we start it in the middle of the left side
1519 posStart.y = startRect.top + startRect.height / 2;
1521 // End node has arrow head and we give it a bit more space.
1522 posEnd.x = endRect.left - 4;
1523 posEnd.y = endRect.top;
1525 // Utility object with x and y offsets for handles.
1527 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1528 // overlap much with top-to-bottom curves (much more frequent).
1529 x: topToBottom ? 15 : 25,
1530 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1533 // When destination is on the different line, we can make a
1534 // curvier arrow because we have space for it.
1535 // So, instead of using
1537 // startHandle.x = posStart.x - curvature.x
1538 // endHandle.x = posEnd.x - curvature.x
1540 // We use the leftmost of these two values for both handles.
1541 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1542 endHandle.x = startHandle.x;
1544 // Curving downwards from the start node...
1545 startHandle.y = posStart.y + curvature.y;
1546 // ... and upwards from the end node.
1547 endHandle.y = posEnd.y - curvature.y;
1549 } else if (leftToRight) {
1551 // Starting from the top right corner...
1552 posStart.x = startRect.right - 1;
1553 posStart.y = startRect.top;
1555 // ...and ending at the top left corner of the end token.
1556 posEnd.x = endRect.left + 1;
1557 posEnd.y = endRect.top - 1;
1559 // Utility object with x and y offsets for handles.
1561 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1565 // Curving to the right...
1566 startHandle.x = posStart.x + curvature.x;
1567 // ... and upwards from the start node.
1568 startHandle.y = posStart.y - curvature.y;
1570 // And to the left...
1571 endHandle.x = posEnd.x - curvature.x;
1572 // ... and upwards from the end node.
1573 endHandle.y = posEnd.y - curvature.y;
1577 // Starting from the bottom right corner...
1578 posStart.x = startRect.right;
1579 posStart.y = startRect.bottom;
1581 // ...and ending also at the bottom right corner, but of the end token.
1582 posEnd.x = endRect.right - 1;
1583 posEnd.y = endRect.bottom + 1;
1585 // Utility object with x and y offsets for handles.
1587 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1591 // Curving to the left...
1592 startHandle.x = posStart.x - curvature.x;
1593 // ... and downwards from the start node.
1594 startHandle.y = posStart.y + curvature.y;
1596 // And to the right...
1597 endHandle.x = posEnd.x + curvature.x;
1598 // ... and downwards from the end node.
1599 endHandle.y = posEnd.y + curvature.y;
1602 // Put it all together into a path.
1603 // More information on the format:
1604 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1605 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1606 "C" + startHandle.x + "," + startHandle.y + " " +
1607 endHandle.x + "," + endHandle.y + " " +
1608 posEnd.x + "," + posEnd.y;
1610 arrow.setAttribute("d", pathStr);
1613var drawArrows = function() {
1614 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1615 for (var i = 0; i < numOfArrows; ++i) {
1620var toggleArrows = function(event) {
1621 const arrows = document.querySelector("#arrows");
1622 if (event.target.checked) {
1623 arrows.setAttribute("visibility", "visible");
1625 arrows.setAttribute("visibility", "hidden");
1629window.addEventListener("resize", drawArrows);
1630document.addEventListener("DOMContentLoaded", function() {
1631 // Whenever we show invocation, locations change, i.e. we
1632 // need to redraw arrows.
1634 .querySelector('input[id="showinvocation"]')
1635 .addEventListener("click", drawArrows);
1636 // Hiding irrelevant lines also should cause arrow rerender.
1638 .querySelector('input[name="showCounterexample"]')
1639 .addEventListener("change", drawArrows);
1641 .querySelector('input[name="showArrows"]')
1642 .addEventListener("change", toggleArrows);
1644 // Default highlighting for the last event.
1645 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)
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 unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
MeasureTokenLength - Relex the token at the specified location and return its length in bytes in the ...
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.
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.
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.
The JSON file list parser is used to communicate input to InstallAPI.
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)
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.