#include "clang/Analysis/FlowSensitive/AdornedCFG.h"
#include "clang/Analysis/FlowSensitive/DebugSupport.h"
#include "clang/Analysis/FlowSensitive/Logger.h"
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include "HTMLLogger.inc"
namespace clang::dataflow {
namespace {
llvm::Expected<std::string> renderSVG(llvm::StringRef DotGraph);
using StreamFactory = std::function<std::unique_ptr<llvm::raw_ostream>()>;
class ModelDumper {
public:
ModelDumper(llvm::json::OStream &JOS, const Environment &Env)
: JOS(JOS), Env(Env) {}
void dump(Value &V) {
JOS.attribute("value_id", llvm::to_string(&V));
if (!Visited.insert(&V).second)
return;
JOS.attribute("kind", debugString(V.getKind()));
switch (V.getKind()) {
case Value::Kind::Integer:
case Value::Kind::TopBool:
case Value::Kind::AtomicBool:
case Value::Kind::FormulaBool:
break;
case Value::Kind::Pointer:
JOS.attributeObject(
"pointee", [&] { dump(cast<PointerValue>(V).getPointeeLoc()); });
break;
}
for (const auto& Prop : V.properties())
JOS.attributeObject(("p:" + Prop.first()).str(),
[&] { dump(*Prop.second); });
if (auto *B = llvm::dyn_cast<BoolValue>(&V)) {
JOS.attribute("formula", llvm::to_string(B->formula()));
JOS.attribute("truth", Env.proves(B->formula()) ? "true"
: Env.proves(Env.arena().makeNot(B->formula()))
? "false"
: "unknown");
}
}
void dump(const StorageLocation &L) {
JOS.attribute("location", llvm::to_string(&L));
if (!Visited.insert(&L).second)
return;
JOS.attribute("type", L.getType().getAsString());
if (!L.getType()->isRecordType())
if (auto *V = Env.getValue(L))
dump(*V);
if (auto *RLoc = dyn_cast<RecordStorageLocation>(&L)) {
for (const auto &Child : RLoc->children())
JOS.attributeObject("f:" + Child.first->getNameAsString(), [&] {
if (Child.second)
if (Value *Val = Env.getValue(*Child.second))
dump(*Val);
});
for (const auto &SyntheticField : RLoc->synthetic_fields())
JOS.attributeObject(("sf:" + SyntheticField.first()).str(),
[&] { dump(*SyntheticField.second); });
}
}
llvm::DenseSet<const void*> Visited;
llvm::json::OStream &JOS;
const Environment &Env;
};
class HTMLLogger : public Logger {
struct Iteration {
const CFGBlock *Block;
unsigned Iter;
bool PostVisit;
bool Converged;
};
StreamFactory Streams;
std::unique_ptr<llvm::raw_ostream> OS;
std::string JSON;
llvm::raw_string_ostream JStringStream{JSON};
llvm::json::OStream JOS{JStringStream, 2};
const AdornedCFG *ACFG;
std::vector<Iteration> Iters;
llvm::DenseMap<const CFGBlock *, llvm::SmallVector<size_t>> BlockIters;
llvm::BitVector BlockConverged;
std::string ContextLogs;
unsigned ElementIndex;
public:
explicit HTMLLogger(StreamFactory Streams) : Streams(std::move(Streams)) {}
void beginAnalysis(const AdornedCFG &ACFG,
TypeErasedDataflowAnalysis &A) override {
OS = Streams();
this->ACFG = &ACFG;
*OS << llvm::StringRef(HTMLLogger_html).split("<?INJECT?>").first;
BlockConverged.resize(ACFG.getCFG().getNumBlockIDs());
const auto &D = ACFG.getDecl();
const auto &SM = A.getASTContext().getSourceManager();
*OS << "<title>";
if (const auto *ND = dyn_cast<NamedDecl>(&D))
*OS << ND->getNameAsString() << " at ";
*OS << SM.getFilename(D.getLocation()) << ":"
<< SM.getSpellingLineNumber(D.getLocation());
*OS << "</title>\n";
*OS << "<style>" << HTMLLogger_css << "</style>\n";
*OS << "<script>" << HTMLLogger_js << "</script>\n";
writeCode();
JOS.objectBegin();
JOS.attributeBegin("states");
JOS.objectBegin();
}
void endAnalysis() override {
JOS.objectEnd();
JOS.attributeEnd();
JOS.attributeArray("timeline", [&] {
for (const auto &E : Iters) {
JOS.object([&] {
JOS.attribute("block", blockID(E.Block->getBlockID()));
JOS.attribute("iter", E.Iter);
JOS.attribute("post_visit", E.PostVisit);
JOS.attribute("converged", E.Converged);
});
}
});
JOS.attributeObject("cfg", [&] {
for (const auto &E : BlockIters)
writeBlock(*E.first, E.second);
});
JOS.objectEnd();
writeCFG();
*OS << "<script>var HTMLLoggerData = \n";
*OS << JSON;
*OS << ";\n</script>\n";
*OS << llvm::StringRef(HTMLLogger_html).split("<?INJECT?>").second;
}
void enterBlock(const CFGBlock &B, bool PostVisit) override {
llvm::SmallVector<size_t> &BIter = BlockIters[&B];
unsigned IterNum = BIter.size() + 1;
BIter.push_back(Iters.size());
Iters.push_back({&B, IterNum, PostVisit, false});
if (!PostVisit)
BlockConverged[B.getBlockID()] = false;
ElementIndex = 0;
}
void enterElement(const CFGElement &E) override {
++ElementIndex;
}
static std::string blockID(unsigned Block) {
return llvm::formatv("B{0}", Block);
}
static std::string eltID(unsigned Block, unsigned Element) {
return llvm::formatv("B{0}.{1}", Block, Element);
}
static std::string iterID(unsigned Block, unsigned Iter) {
return llvm::formatv("B{0}:{1}", Block, Iter);
}
static std::string elementIterID(unsigned Block, unsigned Iter,
unsigned Element) {
return llvm::formatv("B{0}:{1}_B{0}.{2}", Block, Iter, Element);
}
void recordState(TypeErasedDataflowAnalysisState &State) override {
unsigned Block = Iters.back().Block->getBlockID();
unsigned Iter = Iters.back().Iter;
bool PostVisit = Iters.back().PostVisit;
JOS.attributeObject(elementIterID(Block, Iter, ElementIndex), [&] {
JOS.attribute("block", blockID(Block));
JOS.attribute("iter", Iter);
JOS.attribute("post_visit", PostVisit);
JOS.attribute("element", ElementIndex);
if (ElementIndex > 0) {
auto S =
Iters.back().Block->Elements[ElementIndex - 1].getAs<CFGStmt>();
if (const Expr *E = S ? llvm::dyn_cast<Expr>(S->getStmt()) : nullptr) {
if (E->isPRValue()) {
if (!E->getType()->isRecordType())
if (auto *V = State.Env.getValue(*E))
JOS.attributeObject(
"value", [&] { ModelDumper(JOS, State.Env).dump(*V); });
} else {
if (auto *Loc = State.Env.getStorageLocation(*E))
JOS.attributeObject(
"value", [&] { ModelDumper(JOS, State.Env).dump(*Loc); });
}
}
}
if (!ContextLogs.empty()) {
JOS.attribute("logs", ContextLogs);
ContextLogs.clear();
}
{
std::string BuiltinLattice;
llvm::raw_string_ostream BuiltinLatticeS(BuiltinLattice);
State.Env.dump(BuiltinLatticeS);
JOS.attribute("builtinLattice", BuiltinLattice);
}
});
}
void blockConverged() override {
Iters.back().Converged = true;
BlockConverged[Iters.back().Block->getBlockID()] = true;
}
void logText(llvm::StringRef S) override {
ContextLogs.append(S.begin(), S.end());
ContextLogs.push_back('\n');
}
private:
void writeBlock(const CFGBlock &B, llvm::ArrayRef<size_t> ItersForB) {
JOS.attributeObject(blockID(B.getBlockID()), [&] {
JOS.attributeArray("iters", [&] {
for (size_t IterIdx : ItersForB) {
const Iteration &Iter = Iters[IterIdx];
JOS.object([&] {
JOS.attribute("iter", Iter.Iter);
JOS.attribute("post_visit", Iter.PostVisit);
JOS.attribute("converged", Iter.Converged);
});
}
});
JOS.attributeArray("elements", [&] {
for (const auto &Elt : B.Elements) {
std::string Dump;
llvm::raw_string_ostream DumpS(Dump);
Elt.dumpToStream(DumpS);
JOS.value(Dump);
}
});
});
}
void writeCode() {
const auto &AST = ACFG->getDecl().getASTContext();
bool Invalid = false;
auto Range = clang::Lexer::makeFileCharRange(
CharSourceRange::getTokenRange(ACFG->getDecl().getSourceRange()),
AST.getSourceManager(), AST.getLangOpts());
if (Range.isInvalid())
return;
llvm::StringRef Code = clang::Lexer::getSourceText(
Range, AST.getSourceManager(), AST.getLangOpts(), &Invalid);
if (Invalid)
return;
struct TokenInfo {
enum : unsigned { Missing = static_cast<unsigned>(-1) };
unsigned BB = Missing;
unsigned BBPriority = 0;
unsigned Elt = Missing;
unsigned EltPriority = 0;
SmallVector<unsigned> Elts;
void assign(unsigned BB, unsigned Elt, unsigned RangeLen) {
if (this->BB != Missing && BB != this->BB && BBPriority <= RangeLen)
return;
if (BB != this->BB) {
this->BB = BB;
Elts.clear();
BBPriority = RangeLen;
}
BBPriority = std::min(BBPriority, RangeLen);
Elts.push_back(Elt);
if (this->Elt == Missing || EltPriority > RangeLen)
this->Elt = Elt;
}
bool operator==(const TokenInfo &Other) const {
return std::tie(BB, Elt, Elts) ==
std::tie(Other.BB, Other.Elt, Other.Elts);
}
void write(llvm::raw_ostream &OS) const {
OS << "class='c";
if (BB != Missing)
OS << " " << blockID(BB);
for (unsigned Elt : Elts)
OS << " " << eltID(BB, Elt);
OS << "'";
if (Elt != Missing)
OS << " data-elt='" << eltID(BB, Elt) << "'";
if (BB != Missing)
OS << " data-bb='" << blockID(BB) << "'";
}
};
std::vector<TokenInfo> State(Code.size());
for (const auto *Block : ACFG->getCFG()) {
unsigned EltIndex = 0;
for (const auto& Elt : *Block) {
++EltIndex;
if (const auto S = Elt.getAs<CFGStmt>()) {
auto EltRange = clang::Lexer::makeFileCharRange(
CharSourceRange::getTokenRange(S->getStmt()->getSourceRange()),
AST.getSourceManager(), AST.getLangOpts());
if (EltRange.isInvalid())
continue;
if (EltRange.getBegin() < Range.getBegin() ||
EltRange.getEnd() >= Range.getEnd() ||
EltRange.getEnd() < Range.getBegin() ||
EltRange.getEnd() >= Range.getEnd())
continue;
unsigned Off = EltRange.getBegin().getRawEncoding() -
Range.getBegin().getRawEncoding();
unsigned Len = EltRange.getEnd().getRawEncoding() -
EltRange.getBegin().getRawEncoding();
for (unsigned I = 0; I < Len; ++I)
State[Off + I].assign(Block->getBlockID(), EltIndex, Len);
}
}
}
unsigned Line =
AST.getSourceManager().getSpellingLineNumber(Range.getBegin());
*OS << "<template data-copy='code'>\n";
*OS << "<code class='filename'>";
llvm::printHTMLEscaped(
llvm::sys::path::filename(
AST.getSourceManager().getFilename(Range.getBegin())),
*OS);
*OS << "</code>";
*OS << "<code class='line' data-line='" << Line++ << "'>";
for (unsigned I = 0; I < Code.size(); ++I) {
bool NeedOpen = I == 0 || !(State[I] == State[I-1]);
bool NeedClose = I + 1 == Code.size() || !(State[I] == State[I + 1]);
if (NeedOpen) {
*OS << "<span ";
State[I].write(*OS);
*OS << ">";
}
if (Code[I] == '\n')
*OS << "</code>\n<code class='line' data-line='" << Line++ << "'>";
else
llvm::printHTMLEscaped(Code.substr(I, 1), *OS);
if (NeedClose) *OS << "</span>";
}
*OS << "</code>\n";
*OS << "</template>";
}
void writeCFG() {
*OS << "<template data-copy='cfg'>\n";
if (auto SVG = renderSVG(buildCFGDot(ACFG->getCFG())))
*OS << *SVG;
else
*OS << "Can't draw CFG: " << toString(SVG.takeError());
*OS << "</template>\n";
}
std::string buildCFGDot(const clang::CFG &CFG) {
std::string Graph;
llvm::raw_string_ostream GraphS(Graph);
GraphS << R"(digraph {
tooltip=" "
node[class=bb, shape=square, fontname="sans-serif", tooltip=" "]
edge[tooltip = " "]
)";
for (unsigned I = 0; I < CFG.getNumBlockIDs(); ++I) {
std::string Name = blockID(I);
const char *ConvergenceMarker = (const char *)u8"\\n\u2192\u007c";
if (BlockConverged[I])
Name += ConvergenceMarker;
GraphS << " " << blockID(I) << " [id=" << blockID(I) << " label=\""
<< Name << "\"]\n";
}
for (const auto *Block : CFG) {
for (const auto &Succ : Block->succs()) {
if (Succ.getReachableBlock())
GraphS << " " << blockID(Block->getBlockID()) << " -> "
<< blockID(Succ.getReachableBlock()->getBlockID()) << "\n";
}
}
GraphS << "}\n";
return Graph;
}
};
llvm::Expected<std::string> renderSVG(llvm::StringRef DotGraph) {
std::string DotPath;
if (const auto *FromEnv = ::getenv("GRAPHVIZ_DOT"))
DotPath = FromEnv;
else {
auto FromPath = llvm::sys::findProgramByName("dot");
if (!FromPath)
return llvm::createStringError(FromPath.getError(),
"'dot' not found on PATH");
DotPath = FromPath.get();
}
llvm::SmallString<256> Input, Output;
int InputFD;
if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".dot", InputFD,
Input))
return llvm::createStringError(EC, "failed to create `dot` temp input");
llvm::raw_fd_ostream(InputFD, true) << DotGraph;
auto DeleteInput =
llvm::make_scope_exit([&] { llvm::sys::fs::remove(Input); });
if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".svg", Output))
return llvm::createStringError(EC, "failed to create `dot` temp output");
auto DeleteOutput =
llvm::make_scope_exit([&] { llvm::sys::fs::remove(Output); });
std::vector<std::optional<llvm::StringRef>> Redirects = {
Input, Output,
std::nullopt};
std::string ErrMsg;
int Code = llvm::sys::ExecuteAndWait(
DotPath, {"dot", "-Tsvg"}, std::nullopt, Redirects,
0, 0, &ErrMsg);
if (!ErrMsg.empty())
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"'dot' failed: " + ErrMsg);
if (Code != 0)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"'dot' failed (" + llvm::Twine(Code) + ")");
auto Buf = llvm::MemoryBuffer::getFile(Output);
if (!Buf)
return llvm::createStringError(Buf.getError(), "Can't read `dot` output");
llvm::StringRef Result = Buf.get()->getBuffer();
auto Pos = Result.find("<svg");
if (Pos == llvm::StringRef::npos)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Can't find <svg> tag in `dot` output");
return Result.substr(Pos).str();
}
}
std::unique_ptr<Logger>
Logger::html(std::function<std::unique_ptr<llvm::raw_ostream>()> Streams) {
return std::make_unique<HTMLLogger>(std::move(Streams));
}
}