64#include "llvm/ADT/DenseMap.h"
65#include "llvm/ADT/ScopeExit.h"
66#include "llvm/Support/Error.h"
67#include "llvm/Support/FormatVariadic.h"
68#include "llvm/Support/JSON.h"
69#include "llvm/Support/Program.h"
70#include "llvm/Support/ScopedPrinter.h"
71#include "llvm/Support/raw_ostream.h"
73#include "HTMLLogger.inc"
81using StreamFactory = std::function<std::unique_ptr<llvm::raw_ostream>()>;
86 ModelDumper(llvm::json::OStream &JOS,
const Environment &Env)
90 JOS.attribute(
"value_id", llvm::to_string(&
V));
96 switch (
V.getKind()) {
105 "pointee", [&] {
dump(cast<PointerValue>(
V).getPointeeLoc()); });
109 for (
const auto& Prop :
V.properties())
110 JOS.attributeObject((
"p:" + Prop.first()).str(),
111 [&] {
dump(*Prop.second); });
115 if (
auto *B = llvm::dyn_cast<BoolValue>(&
V)) {
116 JOS.attribute(
"formula", llvm::to_string(B->formula()));
118 "truth",
Env.flowConditionImplies(B->formula()) ?
"true"
119 :
Env.flowConditionImplies(
Env.arena().makeNot(B->formula()))
124 void dump(
const StorageLocation &L) {
125 JOS.attribute(
"location", llvm::to_string(&L));
126 if (!
Visited.insert(&L).second)
129 JOS.attribute(
"type", L.getType().getAsString());
130 if (
auto *
V =
Env.getValue(L))
133 if (
auto *RLoc = dyn_cast<RecordStorageLocation>(&L)) {
134 for (
const auto &Child : RLoc->children())
135 JOS.attributeObject(
"f:" + Child.first->getNameAsString(), [&] {
137 if (Value *Val = Env.getValue(*Child.second))
148class HTMLLogger :
public Logger {
155 StreamFactory Streams;
156 std::unique_ptr<llvm::raw_ostream> OS;
157 std::optional<llvm::json::OStream>
JOS;
159 const ControlFlowContext *CFG;
161 std::vector<Iteration> Iters;
163 llvm::DenseMap<const CFGBlock *, llvm::SmallVector<Iteration>> BlockIters;
165 std::string ContextLogs;
167 unsigned ElementIndex;
170 explicit HTMLLogger(StreamFactory Streams) : Streams(
std::move(Streams)) {}
171 void beginAnalysis(
const ControlFlowContext &CFG,
172 TypeErasedDataflowAnalysis &A)
override {
175 *OS << llvm::StringRef(HTMLLogger_html).split(
"<?INJECT?>").first;
177 const auto &D = CFG.getDecl();
178 const auto &
SM = A.getASTContext().getSourceManager();
180 if (
const auto *ND = dyn_cast<NamedDecl>(&D))
181 *OS << ND->getNameAsString() <<
" at ";
182 *OS <<
SM.getFilename(D.getLocation()) <<
":"
183 <<
SM.getSpellingLineNumber(D.getLocation());
186 *OS <<
"<style>" << HTMLLogger_css <<
"</style>\n";
187 *OS <<
"<script>" << HTMLLogger_js <<
"</script>\n";
192 *OS <<
"<script>var HTMLLoggerData = \n";
195 JOS->attributeBegin(
"states");
200 void endAnalysis()
override {
204 JOS->attributeArray(
"timeline", [&] {
205 for (
const auto &E : Iters) {
207 JOS->attribute(
"block", blockID(E.Block->getBlockID()));
208 JOS->attribute(
"iter", E.Iter);
209 JOS->attribute(
"post_visit", E.PostVisit);
213 JOS->attributeObject(
"cfg", [&] {
214 for (
const auto &E : BlockIters)
215 writeBlock(*E.first, E.second);
220 *OS <<
";\n</script>\n";
221 *OS << llvm::StringRef(HTMLLogger_html).split(
"<?INJECT?>").second;
224 void enterBlock(
const CFGBlock &B,
bool PostVisit)
override {
226 unsigned IterNum = BIter.size() + 1;
227 BIter.push_back({&B, IterNum,
PostVisit});
228 Iters.push_back({&B, IterNum,
PostVisit});
231 void enterElement(
const CFGElement &E)
override {
235 static std::string blockID(
unsigned Block) {
236 return llvm::formatv(
"B{0}",
Block);
238 static std::string eltID(
unsigned Block,
unsigned Element) {
239 return llvm::formatv(
"B{0}.{1}",
Block, Element);
241 static std::string iterID(
unsigned Block,
unsigned Iter) {
242 return llvm::formatv(
"B{0}:{1}",
Block,
Iter);
244 static std::string elementIterID(
unsigned Block,
unsigned Iter,
246 return llvm::formatv(
"B{0}:{1}_B{0}.{2}",
Block,
Iter, Element);
255 void recordState(TypeErasedDataflowAnalysisState &State)
override {
256 unsigned Block = Iters.back().Block->getBlockID();
257 unsigned Iter = Iters.back().Iter;
259 JOS->attributeObject(elementIterID(
Block,
Iter, ElementIndex), [&] {
260 JOS->attribute(
"block", blockID(
Block));
263 JOS->attribute(
"element", ElementIndex);
266 if (ElementIndex > 0) {
268 Iters.back().
Block->Elements[ElementIndex - 1].getAs<CFGStmt>();
269 if (
const Expr *E = S ? llvm::dyn_cast<Expr>(S->getStmt()) :
nullptr) {
270 if (E->isPRValue()) {
271 if (auto *V = State.Env.getValue(*E))
272 JOS->attributeObject(
273 "value", [&] { ModelDumper(*JOS, State.Env).dump(*V); });
275 if (auto *Loc = State.Env.getStorageLocation(*E))
276 JOS->attributeObject(
277 "value", [&] { ModelDumper(*JOS, State.Env).dump(*Loc); });
281 if (!ContextLogs.empty()) {
282 JOS->attribute(
"logs", ContextLogs);
286 std::string BuiltinLattice;
287 llvm::raw_string_ostream BuiltinLatticeS(BuiltinLattice);
288 State.Env.dump(BuiltinLatticeS);
289 JOS->attribute(
"builtinLattice", BuiltinLattice);
293 void blockConverged()
override { logText(
"Block converged"); }
295 void logText(llvm::StringRef S)
override {
296 ContextLogs.append(S.begin(), S.end());
297 ContextLogs.push_back(
'\n');
305 JOS->attributeObject(blockID(B.getBlockID()), [&] {
306 JOS->attributeArray(
"iters", [&] {
307 for (const auto &Iter : ItersForB) {
309 JOS->attribute(
"iter", Iter.Iter);
310 JOS->attribute(
"post_visit", Iter.PostVisit);
314 JOS->attributeArray(
"elements", [&] {
315 for (
const auto &Elt : B.Elements) {
317 llvm::raw_string_ostream DumpS(Dump);
318 Elt.dumpToStream(DumpS);
330 const auto &AST = CFG->getDecl().getASTContext();
338 CharSourceRange::getTokenRange(CFG->getDecl().getSourceRange()),
339 AST.getSourceManager(), AST.getLangOpts());
340 if (
Range.isInvalid())
343 Range, AST.getSourceManager(), AST.getLangOpts(), &Invalid);
347 static constexpr unsigned Missing = -1;
352 unsigned BB = Missing;
353 unsigned BBPriority = 0;
355 unsigned Elt = Missing;
356 unsigned EltPriority = 0;
358 SmallVector<unsigned> Elts;
366 void assign(
unsigned BB,
unsigned Elt,
unsigned RangeLen) {
368 if (this->BB != Missing && BB != this->BB && BBPriority <= RangeLen)
370 if (BB != this->BB) {
373 BBPriority = RangeLen;
375 BBPriority = std::min(BBPriority, RangeLen);
377 if (this->Elt == Missing || EltPriority > RangeLen)
380 bool operator==(
const TokenInfo &Other)
const {
381 return std::tie(BB, Elt, Elts) ==
385 void write(llvm::raw_ostream &OS)
const {
388 OS <<
" " << blockID(BB);
389 for (
unsigned Elt : Elts)
390 OS <<
" " << eltID(BB, Elt);
394 OS <<
" data-elt='" << eltID(BB, Elt) <<
"'";
396 OS <<
" data-bb='" << blockID(BB) <<
"'";
402 std::vector<TokenInfo> State(Code.size());
403 for (
const auto *
Block : CFG->getCFG()) {
404 unsigned EltIndex = 0;
405 for (
const auto& Elt : *
Block) {
407 if (
const auto S = Elt.getAs<CFGStmt>()) {
409 CharSourceRange::getTokenRange(S->getStmt()->getSourceRange()),
410 AST.getSourceManager(), AST.getLangOpts());
411 if (EltRange.isInvalid())
413 if (EltRange.getBegin() <
Range.getBegin() ||
414 EltRange.getEnd() >=
Range.getEnd() ||
415 EltRange.getEnd() <
Range.getBegin() ||
416 EltRange.getEnd() >=
Range.getEnd())
418 unsigned Off = EltRange.getBegin().getRawEncoding() -
419 Range.getBegin().getRawEncoding();
420 unsigned Len = EltRange.getEnd().getRawEncoding() -
421 EltRange.getBegin().getRawEncoding();
422 for (
unsigned I = 0; I < Len; ++I)
423 State[Off + I].assign(
Block->getBlockID(), EltIndex, Len);
430 AST.getSourceManager().getSpellingLineNumber(
Range.getBegin());
431 *OS <<
"<template data-copy='code'>\n";
432 *OS <<
"<code class='filename'>";
433 llvm::printHTMLEscaped(
434 llvm::sys::path::filename(
435 AST.getSourceManager().getFilename(
Range.getBegin())),
438 *OS <<
"<code class='line' data-line='" << Line++ <<
"'>";
439 for (
unsigned I = 0; I < Code.size(); ++I) {
442 bool NeedOpen = I == 0 || !(State[I] == State[I-1]);
443 bool NeedClose = I + 1 == Code.size() || !(State[I] == State[I + 1]);
450 *OS <<
"</code>\n<code class='line' data-line='" << Line++ <<
"'>";
452 llvm::printHTMLEscaped(Code.substr(I, 1), *OS);
453 if (NeedClose) *OS <<
"</span>";
456 *OS <<
"</template>";
463 *OS <<
"<template data-copy='cfg'>\n";
464 if (
auto SVG = renderSVG(buildCFGDot(CFG->getCFG())))
467 *OS <<
"Can't draw CFG: " <<
toString(SVG.takeError());
468 *OS <<
"</template>\n";
472 static std::string buildCFGDot(
const clang::CFG &CFG) {
474 llvm::raw_string_ostream GraphS(Graph);
476 GraphS << R
"(digraph {
478 node[class=bb, shape=square, fontname="sans-serif", tooltip=" "]
481 for (
unsigned I = 0; I < CFG.getNumBlockIDs(); ++I)
482 GraphS <<
" " << blockID(I) <<
" [id=" << blockID(I) <<
"]\n";
483 for (
const auto *
Block : CFG) {
484 for (
const auto &Succ :
Block->succs()) {
485 if (Succ.getReachableBlock())
486 GraphS <<
" " << blockID(
Block->getBlockID()) <<
" -> "
487 << blockID(Succ.getReachableBlock()->getBlockID()) <<
"\n";
498 if (
const auto *FromEnv = ::getenv(
"GRAPHVIZ_DOT"))
501 auto FromPath = llvm::sys::findProgramByName(
"dot");
503 return llvm::createStringError(FromPath.getError(),
504 "'dot' not found on PATH");
505 DotPath = FromPath.get();
512 if (
auto EC = llvm::sys::fs::createTemporaryFile(
"analysis",
".dot", InputFD,
514 return llvm::createStringError(EC,
"failed to create `dot` temp input");
515 llvm::raw_fd_ostream(InputFD,
true) << DotGraph;
517 llvm::make_scope_exit([&] { llvm::sys::fs::remove(Input); });
518 if (
auto EC = llvm::sys::fs::createTemporaryFile(
"analysis",
".svg", Output))
519 return llvm::createStringError(EC,
"failed to create `dot` temp output");
521 llvm::make_scope_exit([&] { llvm::sys::fs::remove(Output); });
523 std::vector<std::optional<llvm::StringRef>> Redirects = {
527 int Code = llvm::sys::ExecuteAndWait(
528 DotPath, {
"dot",
"-Tsvg"}, std::nullopt, Redirects,
531 return llvm::createStringError(llvm::inconvertibleErrorCode(),
532 "'dot' failed: " + ErrMsg);
534 return llvm::createStringError(llvm::inconvertibleErrorCode(),
535 "'dot' failed (" + llvm::Twine(Code) +
")");
537 auto Buf = llvm::MemoryBuffer::getFile(Output);
539 return llvm::createStringError(Buf.getError(),
"Can't read `dot` output");
542 llvm::StringRef Result = Buf.get()->getBuffer();
543 auto Pos = Result.find(
"<svg");
544 if (Pos == llvm::StringRef::npos)
545 return llvm::createStringError(llvm::inconvertibleErrorCode(),
546 "Can't find <svg> tag in `dot` output");
547 return Result.substr(Pos).str();
552std::unique_ptr<Logger>
553Logger::html(std::function<std::unique_ptr<llvm::raw_ostream>()> Streams) {
554 return std::make_unique<HTMLLogger>(std::move(Streams));
static void dump(llvm::raw_ostream &OS, StringRef FunctionName, ArrayRef< CounterExpression > Expressions, ArrayRef< CounterMappingRegion > Regions)
llvm::json::OStream & JOS
llvm::DenseSet< const void * > Visited
static std::string toString(const clang::SanitizerSet &Sanitizers)
Produce a string containing comma-separated names of sanitizers in Sanitizers set.
Defines the SourceManager interface.
Represents a source-level, intra-procedural CFG that represents the control-flow of a Stmt.
static StringRef getSourceText(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts, bool *Invalid=nullptr)
Returns a string for the source that the range encompasses.
static CharSourceRange makeFileCharRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Accepts a range and returns a character range with file locations.
Dataflow Directional Tag Classes.
llvm::StringRef debugString(Value::Kind Kind)
Returns a string representation of a value kind.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
if(T->getSizeExpr()) TRY_TO(TraverseStmt(T -> getSizeExpr()))