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);
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,
792 unsigned TotalPieces = getPathSizeWithoutArrows(path);
793 unsigned TotalNotePieces =
797 unsigned PopUpPieceCount =
802 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
803 unsigned NumRegularPieces = TotalRegularPieces;
804 unsigned NumNotePieces = TotalNotePieces;
805 unsigned NumberOfArrows = 0;
807 std::map<int, int> IndexMap;
808 ArrowMap ArrowIndices(TotalRegularPieces + 1);
811 std::vector<SourceRange> PopUpRanges;
813 const auto &Piece = *I.get();
816 ++IndexMap[NumRegularPieces];
821 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
824 }
else if (isArrowPiece(Piece)) {
825 NumberOfArrows = ProcessControlFlowPiece(
827 ArrowIndices[NumRegularPieces] = NumberOfArrows;
830 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
833 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
836 ArrowIndices[0] = NumberOfArrows;
846 assert(ArrowIndices.back() == 0 &&
847 "No arrows should be after the last event");
849 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
850 "Incorrect arrow indices map");
854 NumRegularPieces = TotalRegularPieces;
856 const auto &Piece = *I.get();
858 if (
const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
859 int PopUpPieceIndex = IndexMap[NumRegularPieces];
869 if (PopUpPieceIndex > 0)
870 --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) {
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>";
1100 unsigned DisplayPos = LineEnd - FileStart;
1101 SourceLocation Loc =
1102 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
1107 ArrayRef<SourceRange> Ranges = P.
getRanges();
1108 for (
const auto &Range : Ranges) {
1110 if (llvm::is_contained(PopUpRanges, Range))
1118 unsigned x = n % (
'z' -
'a');
1124 os << char(
'a' + x);
1127unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1128 const PathDiagnosticMacroPiece& P,
1130 for (
const auto &subPiece : P.
subPieces) {
1131 if (
const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1132 num = ProcessMacroPiece(os, *MP, num);
1136 if (
const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1137 os <<
"<div class=\"msg msgEvent\" style=\"width:94%; "
1138 "margin-left:5px\">"
1139 "<table class=\"msgT\"><tr>"
1140 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1142 os <<
"</div></td><td valign=\"top\">"
1144 <<
"</td></tr></table></div>\n";
1151void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1152 const ArrowMap &ArrowIndices) {
1154 llvm::raw_string_ostream
OS(S);
1157<style type="text/css">
1164 pointer-events: none;
1168 stroke-opacity: 0.2;
1170 marker-end: url(#arrowhead);
1174 stroke-opacity: 0.6;
1176 marker-end: url(#arrowheadSelected);
1186<svg xmlns="http://www.w3.org/2000/svg">
1188 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1189 viewBox="0 0 10 10" refX="3" refY="5"
1190 markerWidth="4" markerHeight="4">
1191 <path d="M 0 0 L 10 5 L 0 10 z" />
1193 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1194 viewBox="0 0 10 10" refX="3" refY="5"
1195 markerWidth="4" markerHeight="4">
1196 <path d="M 0 0 L 10 5 L 0 10 z" />
1199 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1202 for (
unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1203 OS <<
" <path class=\"arrow\" id=\"arrow" << Index <<
"\"/>\n";
1209<script type='text/javascript'>
1210const arrowIndices = )<<<";
1212 OS << ArrowIndices << "\n</script>\n";
1221 llvm::raw_string_ostream
OS(
Result);
1222 OS <<
"<span id=\"" << ClassName << Index <<
"\">";
1234unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1235 Rewriter &R, FileID BugFileID,
const PathDiagnosticControlFlowPiece &P,
1237 for (
const PathDiagnosticLocationPair &LPair : P) {
1241 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1243 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1250void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1252 const char *HighlightStart,
1253 const char *HighlightEnd) {
1257 SourceLocation InstantiationStart =
SM.getExpansionLoc(
Range.getBegin());
1258 unsigned StartLineNo =
SM.getExpansionLineNumber(InstantiationStart);
1260 SourceLocation InstantiationEnd =
SM.getExpansionLoc(
Range.getEnd());
1261 unsigned EndLineNo =
SM.getExpansionLineNumber(InstantiationEnd);
1263 if (EndLineNo < StartLineNo)
1266 if (
SM.getFileID(InstantiationStart) != BugFileID ||
1267 SM.getFileID(InstantiationEnd) != BugFileID)
1271 unsigned EndColNo =
SM.getExpansionColumnNumber(InstantiationEnd);
1272 unsigned OldEndColNo = EndColNo;
1288StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1290<script type='text/javascript'>
1291var digitMatcher = new RegExp("[0-9]+");
1293var querySelectorAllArray = function(selector) {
1294 return Array.prototype.slice.call(
1295 document.querySelectorAll(selector));
1298document.addEventListener("DOMContentLoaded", function() {
1299 querySelectorAllArray(".PathNav > a").forEach(
1300 function(currentValue, currentIndex) {
1301 var hrefValue = currentValue.getAttribute("href");
1302 currentValue.onclick = function() {
1303 scrollTo(document.querySelector(hrefValue));
1309var findNum = function() {
1310 var s = document.querySelector(".msg.selected");
1311 if (!s || s.id == "EndPath") {
1314 var out = parseInt(digitMatcher.exec(s.id)[0]);
1318var classListAdd = function(el, theClass) {
1319 if(!el.className.baseVal)
1320 el.className += " " + theClass;
1322 el.className.baseVal += " " + theClass;
1325var classListRemove = function(el, theClass) {
1326 var className = (!el.className.baseVal) ?
1327 el.className : el.className.baseVal;
1328 className = className.replace(" " + theClass, "");
1329 if(!el.className.baseVal)
1330 el.className = className;
1332 el.className.baseVal = className;
1335var scrollTo = function(el) {
1336 querySelectorAllArray(".selected").forEach(function(s) {
1337 classListRemove(s, "selected");
1339 classListAdd(el, "selected");
1340 window.scrollBy(0, el.getBoundingClientRect().top -
1341 (window.innerHeight / 2));
1342 highlightArrowsForSelectedEvent();
1345var move = function(num, up, numItems) {
1346 if (num == 1 && up || num == numItems - 1 && !up) {
1348 } else if (num == 0 && up) {
1349 return numItems - 1;
1350 } else if (num == 0 && !up) {
1351 return 1 % numItems;
1353 return up ? num - 1 : num + 1;
1356var numToId = function(num) {
1358 return document.getElementById("EndPath")
1360 return document.getElementById("Path" + num);
1363var navigateTo = function(up) {
1364 var numItems = document.querySelectorAll(
1365 ".line > .msgEvent, .line > .msgControl").length;
1366 var currentSelected = findNum();
1367 var newSelected = move(currentSelected, up, numItems);
1368 var newEl = numToId(newSelected, numItems);
1370 // Scroll element into center.
1374window.addEventListener("keydown", function (event) {
1375 if (event.defaultPrevented) {
1379 if (event.keyCode == 74) {
1380 navigateTo(/*up=*/false);
1382 } else if (event.keyCode == 75) {
1383 navigateTo(/*up=*/true);
1387 event.preventDefault();
1393StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1395<script type='text/javascript'>
1396// Return range of numbers from a range [lower, upper).
1397function range(lower, upper) {
1399 for (var i = lower; i <= upper; ++i) {
1405var getRelatedArrowIndices = function(pathId) {
1406 // HTML numeration of events is a bit different than it is in the path.
1407 // Everything is rotated one step to the right, so the last element
1408 // (error diagnostic) has index 0.
1410 // arrowIndices has at least 2 elements
1411 pathId = arrowIndices.length - 1;
1414 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1417var highlightArrowsForSelectedEvent = function() {
1418 const selectedNum = findNum();
1419 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1420 arrowIndicesToHighlight.forEach((index) => {
1421 var arrow = document.querySelector("#arrow" + index);
1423 classListAdd(arrow, "selected")
1428var getAbsoluteBoundingRect = function(element) {
1429 const relative = element.getBoundingClientRect();
1431 left: relative.left + window.pageXOffset,
1432 right: relative.right + window.pageXOffset,
1433 top: relative.top + window.pageYOffset,
1434 bottom: relative.bottom + window.pageYOffset,
1435 height: relative.height,
1436 width: relative.width
1440var drawArrow = function(index) {
1441 // This function is based on the great answer from SO:
1442 // https://stackoverflow.com/a/39575674/11582326
1443 var start = document.querySelector("#start" + index);
1444 var end = document.querySelector("#end" + index);
1445 var arrow = document.querySelector("#arrow" + index);
1447 var startRect = getAbsoluteBoundingRect(start);
1448 var endRect = getAbsoluteBoundingRect(end);
1450 // It is an arrow from a token to itself, no need to visualize it.
1451 if (startRect.top == endRect.top &&
1452 startRect.left == endRect.left)
1455 // Each arrow is a very simple Bézier curve, with two nodes and
1456 // two handles. So, we need to calculate four points in the window:
1458 var posStart = { x: 0, y: 0 };
1460 var posEnd = { x: 0, y: 0 };
1461 // * handle for the start node
1462 var startHandle = { x: 0, y: 0 };
1463 // * handle for the end node
1464 var endHandle = { x: 0, y: 0 };
1465 // One can visualize it as follows:
1481 // NOTE: (0, 0) is the top left corner of the window.
1483 // We have 3 similar, but still different scenarios to cover:
1485 // 1. Two tokens on different lines.
1490 // In this situation, we draw arrow on the left curving to the left.
1491 // 2. Two tokens on the same line, and the destination is on the right.
1496 // In this situation, we draw arrow above curving upwards.
1497 // 3. Two tokens on the same line, and the destination is on the left.
1501 // In this situation, we draw arrow below curving downwards.
1502 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1503 startRect.top >= endRect.top + 5;
1504 const leftToRight = startRect.left < endRect.left;
1506 // NOTE: various magic constants are chosen empirically for
1507 // better positioning and look
1508 if (onDifferentLines) {
1510 const topToBottom = startRect.top < endRect.top;
1511 posStart.x = startRect.left - 1;
1512 // We don't want to start it at the top left corner of the token,
1513 // it doesn't feel like this is where the arrow comes from.
1514 // For this reason, we start it in the middle of the left side
1516 posStart.y = startRect.top + startRect.height / 2;
1518 // End node has arrow head and we give it a bit more space.
1519 posEnd.x = endRect.left - 4;
1520 posEnd.y = endRect.top;
1522 // Utility object with x and y offsets for handles.
1524 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1525 // overlap much with top-to-bottom curves (much more frequent).
1526 x: topToBottom ? 15 : 25,
1527 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1530 // When destination is on the different line, we can make a
1531 // curvier arrow because we have space for it.
1532 // So, instead of using
1534 // startHandle.x = posStart.x - curvature.x
1535 // endHandle.x = posEnd.x - curvature.x
1537 // We use the leftmost of these two values for both handles.
1538 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1539 endHandle.x = startHandle.x;
1541 // Curving downwards from the start node...
1542 startHandle.y = posStart.y + curvature.y;
1543 // ... and upwards from the end node.
1544 endHandle.y = posEnd.y - curvature.y;
1546 } else if (leftToRight) {
1548 // Starting from the top right corner...
1549 posStart.x = startRect.right - 1;
1550 posStart.y = startRect.top;
1552 // ...and ending at the top left corner of the end token.
1553 posEnd.x = endRect.left + 1;
1554 posEnd.y = endRect.top - 1;
1556 // Utility object with x and y offsets for handles.
1558 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1562 // Curving to the right...
1563 startHandle.x = posStart.x + curvature.x;
1564 // ... and upwards from the start node.
1565 startHandle.y = posStart.y - curvature.y;
1567 // And to the left...
1568 endHandle.x = posEnd.x - curvature.x;
1569 // ... and upwards from the end node.
1570 endHandle.y = posEnd.y - curvature.y;
1574 // Starting from the bottom right corner...
1575 posStart.x = startRect.right;
1576 posStart.y = startRect.bottom;
1578 // ...and ending also at the bottom right corner, but of the end token.
1579 posEnd.x = endRect.right - 1;
1580 posEnd.y = endRect.bottom + 1;
1582 // Utility object with x and y offsets for handles.
1584 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1588 // Curving to the left...
1589 startHandle.x = posStart.x - curvature.x;
1590 // ... and downwards from the start node.
1591 startHandle.y = posStart.y + curvature.y;
1593 // And to the right...
1594 endHandle.x = posEnd.x + curvature.x;
1595 // ... and downwards from the end node.
1596 endHandle.y = posEnd.y + curvature.y;
1599 // Put it all together into a path.
1600 // More information on the format:
1601 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1602 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1603 "C" + startHandle.x + "," + startHandle.y + " " +
1604 endHandle.x + "," + endHandle.y + " " +
1605 posEnd.x + "," + posEnd.y;
1607 arrow.setAttribute("d", pathStr);
1610var drawArrows = function() {
1611 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1612 for (var i = 0; i < numOfArrows; ++i) {
1617var toggleArrows = function(event) {
1618 const arrows = document.querySelector("#arrows");
1619 if (event.target.checked) {
1620 arrows.setAttribute("visibility", "visible");
1622 arrows.setAttribute("visibility", "hidden");
1626window.addEventListener("resize", drawArrows);
1627document.addEventListener("DOMContentLoaded", function() {
1628 // Whenever we show invocation, locations change, i.e. we
1629 // need to redraw arrows.
1631 .querySelector('input[id="showinvocation"]')
1632 .addEventListener("click", drawArrows);
1633 // Hiding irrelevant lines also should cause arrow rerender.
1635 .querySelector('input[name="showCounterexample"]')
1636 .addEventListener("change", drawArrows);
1638 .querySelector('input[name="showArrows"]')
1639 .addEventListener("change", toggleArrows);
1641 // Default highlighting for the last event.
1642 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
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)
__device__ __2f16 float __ockl_bool s
__device__ __2f16 float c
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 InsertTextBefore(SourceLocation Loc, StringRef Str)
InsertText - Insert the specified string at the specified location in the original buffer.
SourceManager & getSourceMgr() const
const LangOptions & getLangOpts() const
const llvm::RewriteBuffer * getRewriteBufferFor(FileID FID) const
getRewriteBufferFor - Return the rewrite buffer for the specified FileID.
bool InsertTextAfter(SourceLocation Loc, StringRef Str)
InsertTextAfter - Insert the specified string at the specified location in the original buffer.
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)
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
@ Result
The result type of a method or function.
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.