clang: lib/Analysis/FlowSensitive/HTMLLogger.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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"
72
73#include "HTMLLogger.inc"
74
76namespace {
77
78
80
81using StreamFactory = std::function<std::unique_ptrllvm::raw\_ostream()>;
82
83
84class ModelDumper {
85public:
86 ModelDumper(llvm::json::OStream &JOS, const Environment &Env)
88
90 JOS.attribute("value_id", llvm::to_string(&V));
91 if (.insert(&V).second)
92 return;
93
95
96 switch (V.getKind()) {
101 break;
103 JOS.attributeObject(
104 "pointee", [&] { dump(cast(V).getPointeeLoc()); });
105 break;
106 }
107
108 for (const auto& Prop : V.properties())
109 JOS.attributeObject(("p:" + Prop.first()).str(),
110 [&] { dump(*Prop.second); });
111
112
113
114 if (auto *B = llvm::dyn_cast(&V)) {
115 JOS.attribute("formula", llvm::to_string(B->formula()));
116 JOS.attribute("truth", Env.proves(B->formula()) ? "true"
117 : Env.proves(Env.arena().makeNot(B->formula()))
118 ? "false"
119 : "unknown");
120 }
121 }
122 void dump(const StorageLocation &L) {
123 JOS.attribute("location", llvm::to_string(&L));
124 if (.insert(&L).second)
125 return;
126
127 JOS.attribute("type", L.getType().getAsString());
128 if (!L.getType()->isRecordType())
129 if (auto *V = Env.getValue(L))
131
132 if (auto *RLoc = dyn_cast(&L)) {
133 for (const auto &Child : RLoc->children())
134 JOS.attributeObject("f:" + Child.first->getNameAsString(), [&] {
135 if (Child.second)
136 dump(*Child.second);
137 });
138
139 for (const auto &SyntheticField : RLoc->synthetic_fields())
140 JOS.attributeObject(("sf:" + SyntheticField.first()).str(),
141 [&] { dump(*SyntheticField.second); });
142 }
143 }
144
148};
149
150class HTMLLogger : public Logger {
151 struct Iteration {
156 };
157
158 StreamFactory Streams;
159 std::unique_ptrllvm::raw\_ostream OS;
160 std::string JSON;
161 llvm::raw_string_ostream JStringStream{JSON};
162 llvm::json::OStream JOS{JStringStream, 2};
163
164 const AdornedCFG *ACFG;
165
166 std::vector Iters;
167
168 llvm::DenseMap<const CFGBlock *, llvm::SmallVector<size_t>> BlockIters;
169
170 llvm::BitVector BlockConverged;
171
172 std::string ContextLogs;
173
174 unsigned ElementIndex;
175
176public:
177 explicit HTMLLogger(StreamFactory Streams) : Streams(std::move(Streams)) {}
178 void beginAnalysis(const AdornedCFG &ACFG,
179 TypeErasedDataflowAnalysis &A) override {
180 OS = Streams();
181 this->ACFG = &ACFG;
182 *OS << llvm::StringRef(HTMLLogger_html).split("").first;
183
184 BlockConverged.resize(ACFG.getCFG().getNumBlockIDs());
185
186 const auto &D = ACFG.getDecl();
187 const auto &SM = A.getASTContext().getSourceManager();
188 *OS << "";</p> <p> 189 if (const auto *ND = dyn_cast<NamedDecl>(&<a href="CheckExprLifetime%5F8cpp.html#a0a83134bd8a30628eb51b51c4cc5f709" title="null" rel="noopener noreferrer">D</a>))</p> <p> 190 *OS << ND->getNameAsString() << " at ";</p> <p> 191 *OS << <a href="Basic%5F2Cuda%5F8cpp.html#ab6882f8bbaa96cde60db40335e62eb2e" title="null" rel="noopener noreferrer">SM</a>.getFilename(<a href="CheckExprLifetime%5F8cpp.html#a0a83134bd8a30628eb51b51c4cc5f709" title="null" rel="noopener noreferrer">D</a>.getLocation()) << ":"</p> <p> 192 << <a href="Basic%5F2Cuda%5F8cpp.html#ab6882f8bbaa96cde60db40335e62eb2e" title="null" rel="noopener noreferrer">SM</a>.getSpellingLineNumber(<a href="CheckExprLifetime%5F8cpp.html#a0a83134bd8a30628eb51b51c4cc5f709" title="null" rel="noopener noreferrer">D</a>.getLocation());</p> <p> 193 *OS << "\n";
194
195 *OS << "\n";
196 *OS << "\n";
197
198 writeCode();
199 JOS.objectBegin();
200 JOS.attributeBegin("states");
201 JOS.objectBegin();
202 }
203
204
205 void endAnalysis() override {
206 JOS.objectEnd();
207 JOS.attributeEnd();
208
209 JOS.attributeArray("timeline", [&] {
210 for (const auto &E : Iters) {
211 JOS.object([&] {
212 JOS.attribute("block", blockID(E.Block->getBlockID()));
213 JOS.attribute("iter", E.Iter);
214 JOS.attribute("post_visit", E.PostVisit);
215 JOS.attribute("converged", E.Converged);
216 });
217 }
218 });
219 JOS.attributeObject("cfg", [&] {
220 for (const auto &E : BlockIters)
221 writeBlock(*E.first, E.second);
222 });
223
224 JOS.objectEnd();
225
226 writeCFG();
227
228 *OS << "\n";
231 *OS << llvm::StringRef(HTMLLogger_html).split("").second;
232 }
233
234 void enterBlock(const CFGBlock &B, bool PostVisit) override {
236 unsigned IterNum = BIter.size() + 1;
237 BIter.push_back(Iters.size());
238 Iters.push_back({&B, IterNum, PostVisit, false});
240 BlockConverged[B.getBlockID()] = false;
241 ElementIndex = 0;
242 }
243 void enterElement(const CFGElement &E) override {
244 ++ElementIndex;
245 }
246
247 static std::string blockID(unsigned Block) {
248 return llvm::formatv("B{0}", Block);
249 }
250 static std::string eltID(unsigned Block, unsigned Element) {
251 return llvm::formatv("B{0}.{1}", Block, Element);
252 }
253 static std::string iterID(unsigned Block, unsigned Iter) {
254 return llvm::formatv("B{0}:{1}", Block, Iter);
255 }
256 static std::string elementIterID(unsigned Block, unsigned Iter,
257 unsigned Element) {
258 return llvm::formatv("B{0}:{1}_B{0}.{2}", Block, Iter, Element);
259 }
260
261
262
263
264
265
266
267 void recordState(TypeErasedDataflowAnalysisState &State) override {
268 unsigned Block = Iters.back().Block->getBlockID();
269 unsigned Iter = Iters.back().Iter;
270 bool PostVisit = Iters.back().PostVisit;
271 JOS.attributeObject(elementIterID(Block, Iter, ElementIndex), [&] {
272 JOS.attribute("block", blockID(Block));
273 JOS.attribute("iter", Iter);
275 JOS.attribute("element", ElementIndex);
276
277
278 if (ElementIndex > 0) {
279 auto S =
280 Iters.back().Block->Elements[ElementIndex - 1].getAs();
281 if (const Expr *E = S ? llvm::dyn_cast(S->getStmt()) : nullptr) {
282 if (E->isPRValue()) {
283 if (!E->getType()->isRecordType())
284 if (auto *V = State.Env.getValue(*E))
285 JOS.attributeObject(
286 "value", [&] { ModelDumper(JOS, State.Env).dump(*V); });
287 } else {
288 if (auto *Loc = State.Env.getStorageLocation(*E))
289 JOS.attributeObject(
290 "value", [&] { ModelDumper(JOS, State.Env).dump(*Loc); });
291 }
292 }
293 }
294 if (!ContextLogs.empty()) {
295 JOS.attribute("logs", ContextLogs);
296 ContextLogs.clear();
297 }
298 {
299 std::string BuiltinLattice;
300 llvm::raw_string_ostream BuiltinLatticeS(BuiltinLattice);
301 State.Env.dump(BuiltinLatticeS);
302 JOS.attribute("builtinLattice", BuiltinLattice);
303 }
304 });
305 }
306 void blockConverged() override {
307 Iters.back().Converged = true;
308 BlockConverged[Iters.back().Block->getBlockID()] = true;
309 }
310
311 void logText(llvm::StringRef S) override {
312 ContextLogs.append(S.begin(), S.end());
313 ContextLogs.push_back('\n');
314 }
315
316private:
317
318
319
321 JOS.attributeObject(blockID(B.getBlockID()), [&] {
322 JOS.attributeArray("iters", [&] {
323 for (size_t IterIdx : ItersForB) {
324 const Iteration &Iter = Iters[IterIdx];
325 JOS.object([&] {
326 JOS.attribute("iter", Iter.Iter);
327 JOS.attribute("post_visit", Iter.PostVisit);
328 JOS.attribute("converged", Iter.Converged);
329 });
330 }
331 });
332 JOS.attributeArray("elements", [&] {
333 for (const auto &Elt : B.Elements) {
334 std::string Dump;
335 llvm::raw_string_ostream DumpS(Dump);
336 Elt.dumpToStream(DumpS);
337 JOS.value(Dump);
338 }
339 });
340 });
341 }
342
343
344
345
346
347 void writeCode() {
348 const auto &AST = ACFG->getDecl().getASTContext();
350
351
352
353
354
356 CharSourceRange::getTokenRange(ACFG->getDecl().getSourceRange()),
357 AST.getSourceManager(), AST.getLangOpts());
358 if (Range.isInvalid())
359 return;
361 Range, AST.getSourceManager(), AST.getLangOpts(), &Invalid);
362 if (Invalid)
363 return;
364
365
366 struct TokenInfo {
367 enum : unsigned { Missing = static_cast<unsigned>(-1) };
368
369
370
371 unsigned BB = Missing;
372 unsigned BBPriority = 0;
373
374 unsigned Elt = Missing;
375 unsigned EltPriority = 0;
376
377 SmallVector Elts;
378
379
380
381
382
383
384
385 void assign(unsigned BB, unsigned Elt, unsigned RangeLen) {
386
387 if (this->BB != Missing && BB != this->BB && BBPriority <= RangeLen)
388 return;
389 if (BB != this->BB) {
390 this->BB = BB;
391 Elts.clear();
392 BBPriority = RangeLen;
393 }
394 BBPriority = std::min(BBPriority, RangeLen);
395 Elts.push_back(Elt);
396 if (this->Elt == Missing || EltPriority > RangeLen)
397 this->Elt = Elt;
398 }
399 bool operator==(const TokenInfo &Other) const {
400 return std::tie(BB, Elt, Elts) ==
402 }
403
404 void write(llvm::raw_ostream &OS) const {
405 OS << "class='c";
406 if (BB != Missing)
407 OS << " " << blockID(BB);
408 for (unsigned Elt : Elts)
409 OS << " " << eltID(BB, Elt);
410 OS << "'";
411
412 if (Elt != Missing)
413 OS << " data-elt='" << eltID(BB, Elt) << "'";
414 if (BB != Missing)
415 OS << " data-bb='" << blockID(BB) << "'";
416 }
417 };
418
419
420
421 std::vector State(Code.size());
422 for (const auto *Block : ACFG->getCFG()) {
423 unsigned EltIndex = 0;
424 for (const auto& Elt : *Block) {
425 ++EltIndex;
426 if (const auto S = Elt.getAs()) {
428 CharSourceRange::getTokenRange(S->getStmt()->getSourceRange()),
429 AST.getSourceManager(), AST.getLangOpts());
430 if (EltRange.isInvalid())
431 continue;
432 if (EltRange.getBegin() < Range.getBegin() ||
433 EltRange.getEnd() >= Range.getEnd() ||
434 EltRange.getEnd() < Range.getBegin() ||
435 EltRange.getEnd() >= Range.getEnd())
436 continue;
437 unsigned Off = EltRange.getBegin().getRawEncoding() -
438 Range.getBegin().getRawEncoding();
439 unsigned Len = EltRange.getEnd().getRawEncoding() -
440 EltRange.getBegin().getRawEncoding();
441 for (unsigned I = 0; I < Len; ++I)
442 State[Off + I].assign(Block->getBlockID(), EltIndex, Len);
443 }
444 }
445 }
446
447
448 unsigned Line =
449 AST.getSourceManager().getSpellingLineNumber(Range.getBegin());
450 *OS << "\n";
451 *OS << "";
452 llvm::printHTMLEscaped(
453 llvm::sys::path::filename(
454 AST.getSourceManager().getFilename(Range.getBegin())),
455 *OS);
456 *OS << "";
457 *OS << "";
458 for (unsigned I = 0; I < Code.size(); ++I) {
459
460
461 bool NeedOpen = I == 0 || !(State[I] == State[I-1]);
462 bool NeedClose = I + 1 == Code.size() || !(State[I] == State[I + 1]);
463 if (NeedOpen) {
464 *OS << "
465 State[I].write(*OS); 466 *OS << ">"; 467 } 468 if (Code[I] == '\n') 469 *OS << "\n 470 else 471 llvm::printHTMLEscaped(Code.substr(I, 1), *OS); 472 if (NeedClose) *OS << "";
473 }
474 *OS << "\n";
475 *OS << "";
476 }
477
478
479
480
481 void writeCFG() {
482 *OS << "\n";
483 if (auto SVG = renderSVG(buildCFGDot(ACFG->getCFG())))
484 *OS << *SVG;
485 else
486 *OS << "Can't draw CFG: " << toString(SVG.takeError());
487 *OS << "\n";
488 }
489
490
491 std::string buildCFGDot(const clang::CFG &CFG) {
492 std::string Graph;
493 llvm::raw_string_ostream GraphS(Graph);
494
495 GraphS << R"(digraph {
496 tooltip=" "
497 node[class=bb, shape=square, fontname="sans-serif", tooltip=" "]
498 edge[tooltip = " "]
499)";
500 for (unsigned I = 0; I < CFG.getNumBlockIDs(); ++I) {
501 std::string Name = blockID(I);
502
503 const char *ConvergenceMarker = (const char *)u8"\\n\u2192\u007c";
504 if (BlockConverged[I])
505 Name += ConvergenceMarker;
506 GraphS << " " << blockID(I) << " [id=" << blockID(I) << " label=\""
507 << Name << "\"]\n";
508 }
509 for (const auto *Block : CFG) {
510 for (const auto &Succ : Block->succs()) {
511 if (Succ.getReachableBlock())
512 GraphS << " " << blockID(Block->getBlockID()) << " -> "
513 << blockID(Succ.getReachableBlock()->getBlockID()) << "\n";
514 }
515 }
516 GraphS << "}\n";
517 return Graph;
518 }
519};
520
521
523 std::string DotPath;
524 if (const auto *FromEnv = ::getenv("GRAPHVIZ_DOT"))
525 DotPath = FromEnv;
526 else {
527 auto FromPath = llvm::sys::findProgramByName("dot");
528 if (!FromPath)
529 return llvm::createStringError(FromPath.getError(),
530 "'dot' not found on PATH");
531 DotPath = FromPath.get();
532 }
533
534
535
537 int InputFD;
538 if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".dot", InputFD,
539 Input))
540 return llvm::createStringError(EC, "failed to create `dot` temp input");
541 llvm::raw_fd_ostream(InputFD, true) << DotGraph;
542 auto DeleteInput =
543 llvm::make_scope_exit([&] { llvm::sys::fs::remove(Input); });
544 if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".svg", Output))
545 return llvm::createStringError(EC, "failed to create `dot` temp output");
546 auto DeleteOutput =
547 llvm::make_scope_exit([&] { llvm::sys::fs::remove(Output); });
548
549 std::vector<std::optionalllvm::StringRef> Redirects = {
550 Input, Output,
551 std::nullopt};
552 std::string ErrMsg;
553 int Code = llvm::sys::ExecuteAndWait(
554 DotPath, {"dot", "-Tsvg"}, std::nullopt, Redirects,
555 0, 0, &ErrMsg);
556 if (!ErrMsg.empty())
557 return llvm::createStringError(llvm::inconvertibleErrorCode(),
558 "'dot' failed: " + ErrMsg);
559 if (Code != 0)
560 return llvm::createStringError(llvm::inconvertibleErrorCode(),
561 "'dot' failed (" + llvm::Twine(Code) + ")");
562
563 auto Buf = llvm::MemoryBuffer::getFile(Output);
564 if (!Buf)
565 return llvm::createStringError(Buf.getError(), "Can't read `dot` output");
566
567
568 llvm::StringRef Result = Buf.get()->getBuffer();
569 auto Pos = Result.find("<svg");
570 if (Pos == llvm::StringRef::npos)
571 return llvm::createStringError(llvm::inconvertibleErrorCode(),
572 "Can't find
573 return Result.substr(Pos).str();
574}
575
576}
577
578std::unique_ptr
579Logger::html(std::function<std::unique_ptrllvm::raw\_ostream()> Streams) {
580 return std::make_unique(std::move(Streams));
581}
582
583}
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.
unsigned getNumBlockIDs() const
Returns the total number of BlockIDs allocated (which start at 0).
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.
if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
@ Other
Other implicit parameter.