/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Analysis/FlowSensitive/HTMLLogger.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- HTMLLogger.cpp ----------------------------------------------------===// |
2 | | // |
3 | | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | | // See https://llvm.org/LICENSE.txt for license information. |
5 | | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | // |
9 | | // This file implements the HTML logger. Given a directory dir/, we write |
10 | | // dir/0.html for the first analysis, etc. |
11 | | // These files contain a visualization that allows inspecting the CFG and the |
12 | | // state of the analysis at each point. |
13 | | // Static assets (HTMLLogger.js, HTMLLogger.css) and SVG graphs etc are embedded |
14 | | // so each output file is self-contained. |
15 | | // |
16 | | // VIEWS |
17 | | // |
18 | | // The timeline and function view are always shown. These allow selecting basic |
19 | | // blocks, statements within them, and processing iterations (BBs are visited |
20 | | // multiple times when e.g. loops are involved). |
21 | | // These are written directly into the HTML body. |
22 | | // |
23 | | // There are also listings of particular basic blocks, and dumps of the state |
24 | | // at particular analysis points (i.e. BB2 iteration 3 statement 2). |
25 | | // These are only shown when the relevant BB/analysis point is *selected*. |
26 | | // |
27 | | // DATA AND TEMPLATES |
28 | | // |
29 | | // The HTML proper is mostly static. |
30 | | // The analysis data is in a JSON object HTMLLoggerData which is embedded as |
31 | | // a <script> in the <head>. |
32 | | // This gets rendered into DOM by a simple template processor which substitutes |
33 | | // the data into <template> tags embedded in the HTML. (see inflate() in JS). |
34 | | // |
35 | | // SELECTION |
36 | | // |
37 | | // This is the only real interactive mechanism. |
38 | | // |
39 | | // At any given time, there are several named selections, e.g.: |
40 | | // bb: B2 (basic block 0 is selected) |
41 | | // elt: B2.4 (statement 4 is selected) |
42 | | // iter: B2:1 (iteration 1 of the basic block is selected) |
43 | | // hover: B3 (hovering over basic block 3) |
44 | | // |
45 | | // The selection is updated by mouse events: hover by moving the mouse and |
46 | | // others by clicking. Elements that are click targets generally have attributes |
47 | | // (id or data-foo) that define what they should select. |
48 | | // See watchSelection() in JS for the exact logic. |
49 | | // |
50 | | // When the "bb" selection is set to "B2": |
51 | | // - sections <section data-selection="bb"> get shown |
52 | | // - templates under such sections get re-rendered |
53 | | // - elements with class/id "B2" get class "bb-select" |
54 | | // |
55 | | //===----------------------------------------------------------------------===// |
56 | | |
57 | | #include "clang/Analysis/FlowSensitive/ControlFlowContext.h" |
58 | | #include "clang/Analysis/FlowSensitive/DebugSupport.h" |
59 | | #include "clang/Analysis/FlowSensitive/Logger.h" |
60 | | #include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h" |
61 | | #include "clang/Analysis/FlowSensitive/Value.h" |
62 | | #include "clang/Basic/SourceManager.h" |
63 | | #include "clang/Lex/Lexer.h" |
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 | | // Defines assets: HTMLLogger_{html_js,css} |
73 | | #include "HTMLLogger.inc" |
74 | | |
75 | | namespace clang::dataflow { |
76 | | namespace { |
77 | | |
78 | | // Render a graphviz graph specification to SVG using the `dot` tool. |
79 | | llvm::Expected<std::string> renderSVG(llvm::StringRef DotGraph); |
80 | | |
81 | | using StreamFactory = std::function<std::unique_ptr<llvm::raw_ostream>()>; |
82 | | |
83 | | // Recursively dumps Values/StorageLocations as JSON |
84 | | class ModelDumper { |
85 | | public: |
86 | | ModelDumper(llvm::json::OStream &JOS, const Environment &Env) |
87 | 6 | : JOS(JOS), Env(Env) {} |
88 | | |
89 | 6 | void dump(Value &V) { |
90 | 6 | JOS.attribute("value_id", llvm::to_string(&V)); |
91 | 6 | if (!Visited.insert(&V).second) |
92 | 0 | return; |
93 | | |
94 | 6 | JOS.attribute("kind", debugString(V.getKind())); |
95 | | |
96 | 6 | switch (V.getKind()) { |
97 | 4 | case Value::Kind::Integer: |
98 | 4 | case Value::Kind::TopBool: |
99 | 6 | case Value::Kind::AtomicBool: |
100 | 6 | break; |
101 | 0 | case Value::Kind::Reference: |
102 | 0 | JOS.attributeObject( |
103 | 0 | "referent", [&] { dump(cast<ReferenceValue>(V).getReferentLoc()); }); |
104 | 0 | break; |
105 | 0 | case Value::Kind::Pointer: |
106 | 0 | JOS.attributeObject( |
107 | 0 | "pointee", [&] { dump(cast<PointerValue>(V).getPointeeLoc()); }); |
108 | 0 | break; |
109 | 0 | case Value::Kind::Struct: |
110 | 0 | for (const auto &Child : cast<StructValue>(V).children()) |
111 | 0 | JOS.attributeObject("f:" + Child.first->getNameAsString(), |
112 | 0 | [&] { dump(*Child.second); }); |
113 | 0 | break; |
114 | 0 | case Value::Kind::Disjunction: { |
115 | 0 | auto &VV = cast<DisjunctionValue>(V); |
116 | 0 | JOS.attributeObject("lhs", [&] { dump(VV.getLeftSubValue()); }); |
117 | 0 | JOS.attributeObject("rhs", [&] { dump(VV.getRightSubValue()); }); |
118 | 0 | break; |
119 | 4 | } |
120 | 0 | case Value::Kind::Conjunction: { |
121 | 0 | auto &VV = cast<ConjunctionValue>(V); |
122 | 0 | JOS.attributeObject("lhs", [&] { dump(VV.getLeftSubValue()); }); |
123 | 0 | JOS.attributeObject("rhs", [&] { dump(VV.getRightSubValue()); }); |
124 | 0 | break; |
125 | 4 | } |
126 | 0 | case Value::Kind::Negation: { |
127 | 0 | auto &VV = cast<NegationValue>(V); |
128 | 0 | JOS.attributeObject("not", [&] { dump(VV.getSubVal()); }); |
129 | 0 | break; |
130 | 4 | } |
131 | 0 | case Value::Kind::Implication: { |
132 | 0 | auto &VV = cast<ImplicationValue>(V); |
133 | 0 | JOS.attributeObject("if", [&] { dump(VV.getLeftSubValue()); }); |
134 | 0 | JOS.attributeObject("then", [&] { dump(VV.getRightSubValue()); }); |
135 | 0 | break; |
136 | 4 | } |
137 | 0 | case Value::Kind::Biconditional: { |
138 | 0 | auto &VV = cast<BiconditionalValue>(V); |
139 | 0 | JOS.attributeObject("lhs", [&] { dump(VV.getLeftSubValue()); }); |
140 | 0 | JOS.attributeObject("rhs", [&] { dump(VV.getRightSubValue()); }); |
141 | 0 | break; |
142 | 4 | } |
143 | 6 | } |
144 | | |
145 | 6 | for (const auto& Prop : V.properties()) |
146 | 0 | JOS.attributeObject(("p:" + Prop.first()).str(), |
147 | 0 | [&] { dump(*Prop.second); }); |
148 | | |
149 | | // Running the SAT solver is expensive, but knowing which booleans are |
150 | | // guaranteed true/false here is valuable and hard to determine by hand. |
151 | 6 | if (auto *B = llvm::dyn_cast<BoolValue>(&V)) { |
152 | 2 | JOS.attribute("truth", Env.flowConditionImplies(*B) ? "true"0 |
153 | 2 | : Env.flowConditionImplies(Env.makeNot(*B)) |
154 | 2 | ? "false"0 |
155 | 2 | : "unknown"); |
156 | 2 | } |
157 | 6 | } |
158 | 6 | void dump(const StorageLocation &L) { |
159 | 6 | JOS.attribute("location", llvm::to_string(&L)); |
160 | 6 | if (!Visited.insert(&L).second) |
161 | 0 | return; |
162 | | |
163 | 6 | JOS.attribute("type", L.getType().getAsString()); |
164 | 6 | if (auto *V = Env.getValue(L)) |
165 | 6 | dump(*V); |
166 | 6 | } |
167 | | |
168 | | llvm::DenseSet<const void*> Visited; |
169 | | llvm::json::OStream &JOS; |
170 | | const Environment &Env; |
171 | | }; |
172 | | |
173 | | class HTMLLogger : public Logger { |
174 | | StreamFactory Streams; |
175 | | std::unique_ptr<llvm::raw_ostream> OS; |
176 | | std::optional<llvm::json::OStream> JOS; |
177 | | |
178 | | const ControlFlowContext *CFG; |
179 | | // Timeline of iterations of CFG block visitation. |
180 | | std::vector<std::pair<const CFGBlock *, unsigned>> Iters; |
181 | | // Number of times each CFG block has been seen. |
182 | | llvm::DenseMap<const CFGBlock *, unsigned> BlockIters; |
183 | | // The messages logged in the current context but not yet written. |
184 | | std::string ContextLogs; |
185 | | // The number of elements we have visited within the current CFG block. |
186 | | unsigned ElementIndex; |
187 | | |
188 | | public: |
189 | 1 | explicit HTMLLogger(StreamFactory Streams) : Streams(std::move(Streams)) {} |
190 | | void beginAnalysis(const ControlFlowContext &CFG, |
191 | 1 | TypeErasedDataflowAnalysis &A) override { |
192 | 1 | OS = Streams(); |
193 | 1 | this->CFG = &CFG; |
194 | 1 | *OS << llvm::StringRef(HTMLLogger_html).split("<?INJECT?>").first; |
195 | | |
196 | 1 | if (const auto *D = CFG.getDecl()) { |
197 | 1 | const auto &SM = A.getASTContext().getSourceManager(); |
198 | 1 | *OS << "<title>"; |
199 | 1 | if (const auto *ND = dyn_cast<NamedDecl>(D)) |
200 | 1 | *OS << ND->getNameAsString() << " at "; |
201 | 1 | *OS << SM.getFilename(D->getLocation()) << ":" |
202 | 1 | << SM.getSpellingLineNumber(D->getLocation()); |
203 | 1 | *OS << "</title>\n"; |
204 | 1 | }; |
205 | | |
206 | 1 | *OS << "<style>" << HTMLLogger_css << "</style>\n"; |
207 | 1 | *OS << "<script>" << HTMLLogger_js << "</script>\n"; |
208 | | |
209 | 1 | writeCode(); |
210 | 1 | writeCFG(); |
211 | | |
212 | 1 | *OS << "<script>var HTMLLoggerData = \n"; |
213 | 1 | JOS.emplace(*OS, /*Indent=*/2); |
214 | 1 | JOS->objectBegin(); |
215 | 1 | JOS->attributeBegin("states"); |
216 | 1 | JOS->objectBegin(); |
217 | 1 | } |
218 | | // Between beginAnalysis() and endAnalysis() we write all the states for |
219 | | // particular analysis points into the `timeline` array. |
220 | 1 | void endAnalysis() override { |
221 | 1 | JOS->objectEnd(); |
222 | 1 | JOS->attributeEnd(); |
223 | | |
224 | 1 | JOS->attributeArray("timeline", [&] { |
225 | 5 | for (const auto &E : Iters) { |
226 | 5 | JOS->object([&] { |
227 | 5 | JOS->attribute("block", blockID(E.first->getBlockID())); |
228 | 5 | JOS->attribute("iter", E.second); |
229 | 5 | }); |
230 | 5 | } |
231 | 1 | }); |
232 | 1 | JOS->attributeObject("cfg", [&] { |
233 | 1 | for (const auto &E : BlockIters) |
234 | 5 | writeBlock(*E.first, E.second); |
235 | 1 | }); |
236 | | |
237 | 1 | JOS->objectEnd(); |
238 | 1 | JOS.reset(); |
239 | 1 | *OS << ";\n</script>\n"; |
240 | 1 | *OS << llvm::StringRef(HTMLLogger_html).split("<?INJECT?>").second; |
241 | 1 | } |
242 | | |
243 | 5 | void enterBlock(const CFGBlock &B) override { |
244 | 5 | Iters.emplace_back(&B, ++BlockIters[&B]); |
245 | 5 | ElementIndex = 0; |
246 | 5 | } |
247 | 7 | void enterElement(const CFGElement &E) override { |
248 | 7 | ++ElementIndex; |
249 | 7 | } |
250 | | |
251 | 58 | static std::string blockID(unsigned Block) { |
252 | 58 | return llvm::formatv("B{0}", Block); |
253 | 58 | } |
254 | 17 | static std::string eltID(unsigned Block, unsigned Element) { |
255 | 17 | return llvm::formatv("B{0}.{1}", Block, Element); |
256 | 17 | } |
257 | 0 | static std::string iterID(unsigned Block, unsigned Iter) { |
258 | 0 | return llvm::formatv("B{0}:{1}", Block, Iter); |
259 | 0 | } |
260 | | static std::string elementIterID(unsigned Block, unsigned Iter, |
261 | 12 | unsigned Element) { |
262 | 12 | return llvm::formatv("B{0}:{1}_B{0}.{2}", Block, Iter, Element); |
263 | 12 | } |
264 | | |
265 | | // Write the analysis state associated with a particular analysis point. |
266 | | // FIXME: this dump is fairly opaque. We should show: |
267 | | // - values associated with the current Stmt |
268 | | // - values associated with its children |
269 | | // - meaningful names for values |
270 | | // - which boolean values are implied true/false by the flow condition |
271 | 12 | void recordState(TypeErasedDataflowAnalysisState &State) override { |
272 | 12 | unsigned Block = Iters.back().first->getBlockID(); |
273 | 12 | unsigned Iter = Iters.back().second; |
274 | 12 | JOS->attributeObject(elementIterID(Block, Iter, ElementIndex), [&] { |
275 | 12 | JOS->attribute("block", blockID(Block)); |
276 | 12 | JOS->attribute("iter", Iter); |
277 | 12 | JOS->attribute("element", ElementIndex); |
278 | | |
279 | | // If this state immediately follows an Expr, show its built-in model. |
280 | 12 | if (ElementIndex > 0) { |
281 | 7 | auto S = |
282 | 7 | Iters.back().first->Elements[ElementIndex - 1].getAs<CFGStmt>(); |
283 | 7 | if (const Expr *E = S ? llvm::dyn_cast<Expr>(S->getStmt()) : nullptr) |
284 | 6 | if (auto *Loc = State.Env.getStorageLocation(*E, SkipPast::None)) |
285 | 6 | JOS->attributeObject( |
286 | 6 | "value", [&] { ModelDumper(*JOS, State.Env).dump(*Loc); }); |
287 | 7 | } |
288 | 12 | if (!ContextLogs.empty()) { |
289 | 9 | JOS->attribute("logs", ContextLogs); |
290 | 9 | ContextLogs.clear(); |
291 | 9 | } |
292 | 12 | { |
293 | 12 | std::string BuiltinLattice; |
294 | 12 | llvm::raw_string_ostream BuiltinLatticeS(BuiltinLattice); |
295 | 12 | State.Env.dump(BuiltinLatticeS); |
296 | 12 | JOS->attribute("builtinLattice", BuiltinLattice); |
297 | 12 | } |
298 | 12 | }); |
299 | 12 | } |
300 | 0 | void blockConverged() override { logText("Block converged"); } |
301 | | |
302 | 9 | void logText(llvm::StringRef S) override { |
303 | 9 | ContextLogs.append(S.begin(), S.end()); |
304 | 9 | ContextLogs.push_back('\n'); |
305 | 9 | } |
306 | | |
307 | | private: |
308 | | // Write the CFG block details. |
309 | | // Currently this is just the list of elements in execution order. |
310 | | // FIXME: an AST dump would be a useful view, too. |
311 | 5 | void writeBlock(const CFGBlock &B, unsigned Iters) { |
312 | 5 | JOS->attributeObject(blockID(B.getBlockID()), [&] { |
313 | 5 | JOS->attribute("iters", Iters); |
314 | 5 | JOS->attributeArray("elements", [&] { |
315 | 7 | for (const auto &Elt : B.Elements) { |
316 | 7 | std::string Dump; |
317 | 7 | llvm::raw_string_ostream DumpS(Dump); |
318 | 7 | Elt.dumpToStream(DumpS); |
319 | 7 | JOS->value(Dump); |
320 | 7 | } |
321 | 5 | }); |
322 | 5 | }); |
323 | 5 | } |
324 | | |
325 | | // Write the code of function being examined. |
326 | | // We want to overlay the code with <span>s that mark which BB particular |
327 | | // tokens are associated with, and even which BB element (so that clicking |
328 | | // can select the right element). |
329 | 1 | void writeCode() { |
330 | 1 | if (!CFG->getDecl()) |
331 | 0 | return; |
332 | 1 | const auto &AST = CFG->getDecl()->getASTContext(); |
333 | 1 | bool Invalid = false; |
334 | | |
335 | | // Extract the source code from the original file. |
336 | | // Pretty-printing from the AST would probably be nicer (no macros or |
337 | | // indentation to worry about), but we need the boundaries of particular |
338 | | // AST nodes and the printer doesn't provide this. |
339 | 1 | auto Range = clang::Lexer::makeFileCharRange( |
340 | 1 | CharSourceRange::getTokenRange(CFG->getDecl()->getSourceRange()), |
341 | 1 | AST.getSourceManager(), AST.getLangOpts()); |
342 | 1 | if (Range.isInvalid()) |
343 | 0 | return; |
344 | 1 | llvm::StringRef Code = clang::Lexer::getSourceText( |
345 | 1 | Range, AST.getSourceManager(), AST.getLangOpts(), &Invalid); |
346 | 1 | if (Invalid) |
347 | 0 | return; |
348 | | |
349 | 1 | static constexpr unsigned Missing = -1; |
350 | | // TokenInfo stores the BB and set of elements that a token is part of. |
351 | 1 | struct TokenInfo { |
352 | | // The basic block this is part of. |
353 | | // This is the BB of the stmt with the smallest containing range. |
354 | 1 | unsigned BB = Missing; |
355 | 1 | unsigned BBPriority = 0; |
356 | | // The most specific stmt this is part of (smallest range). |
357 | 1 | unsigned Elt = Missing; |
358 | 1 | unsigned EltPriority = 0; |
359 | | // All stmts this is part of. |
360 | 1 | SmallVector<unsigned> Elts; |
361 | | |
362 | | // Mark this token as being part of BB.Elt. |
363 | | // RangeLen is the character length of the element's range, used to |
364 | | // distinguish inner vs outer statements. |
365 | | // For example in `a==0`, token "a" is part of the stmts "a" and "a==0". |
366 | | // However "a" has a smaller range, so is more specific. Clicking on the |
367 | | // token "a" should select the stmt "a". |
368 | 38 | void assign(unsigned BB, unsigned Elt, unsigned RangeLen) { |
369 | | // A worse BB (larger range) => ignore. |
370 | 38 | if (this->BB != Missing && BB != this->BB22 && BBPriority <= RangeLen3 ) |
371 | 0 | return; |
372 | 38 | if (BB != this->BB) { |
373 | 19 | this->BB = BB; |
374 | 19 | Elts.clear(); |
375 | 19 | BBPriority = RangeLen; |
376 | 19 | } |
377 | 38 | BBPriority = std::min(BBPriority, RangeLen); |
378 | 38 | Elts.push_back(Elt); |
379 | 38 | if (this->Elt == Missing || EltPriority > RangeLen22 ) |
380 | 16 | this->Elt = Elt; |
381 | 38 | } |
382 | 118 | bool operator==(const TokenInfo &Other) const { |
383 | 118 | return std::tie(BB, Elt, Elts) == |
384 | 118 | std::tie(Other.BB, Other.Elt, Other.Elts); |
385 | 118 | } |
386 | | // Write the attributes for the <span> on this token. |
387 | 8 | void write(llvm::raw_ostream &OS) const { |
388 | 8 | OS << "class='c"; |
389 | 8 | if (BB != Missing) |
390 | 6 | OS << " " << blockID(BB); |
391 | 8 | for (unsigned Elt : Elts) |
392 | 11 | OS << " " << eltID(BB, Elt); |
393 | 8 | OS << "'"; |
394 | | |
395 | 8 | if (Elt != Missing) |
396 | 6 | OS << " data-elt='" << eltID(BB, Elt) << "'"; |
397 | 8 | if (BB != Missing) |
398 | 6 | OS << " data-bb='" << blockID(BB) << "'"; |
399 | 8 | } |
400 | 1 | }; |
401 | | |
402 | | // Construct one TokenInfo per character in a flat array. |
403 | | // This is inefficient (chars in a token all have the same info) but simple. |
404 | 1 | std::vector<TokenInfo> State(Code.size()); |
405 | 6 | for (const auto *Block : CFG->getCFG()) { |
406 | 6 | unsigned EltIndex = 0; |
407 | 7 | for (const auto& Elt : *Block) { |
408 | 7 | ++EltIndex; |
409 | 7 | if (const auto S = Elt.getAs<CFGStmt>()) { |
410 | 7 | auto EltRange = clang::Lexer::makeFileCharRange( |
411 | 7 | CharSourceRange::getTokenRange(S->getStmt()->getSourceRange()), |
412 | 7 | AST.getSourceManager(), AST.getLangOpts()); |
413 | 7 | if (EltRange.isInvalid()) |
414 | 0 | continue; |
415 | 7 | if (EltRange.getBegin() < Range.getBegin() || |
416 | 7 | EltRange.getEnd() >= Range.getEnd() || |
417 | 7 | EltRange.getEnd() < Range.getBegin() || |
418 | 7 | EltRange.getEnd() >= Range.getEnd()) |
419 | 0 | continue; |
420 | 7 | unsigned Off = EltRange.getBegin().getRawEncoding() - |
421 | 7 | Range.getBegin().getRawEncoding(); |
422 | 7 | unsigned Len = EltRange.getEnd().getRawEncoding() - |
423 | 7 | EltRange.getBegin().getRawEncoding(); |
424 | 45 | for (unsigned I = 0; I < Len; ++I38 ) |
425 | 38 | State[Off + I].assign(Block->getBlockID(), EltIndex, Len); |
426 | 7 | } |
427 | 7 | } |
428 | 6 | } |
429 | | |
430 | | // Finally, write the code with the correct <span>s. |
431 | 1 | unsigned Line = |
432 | 1 | AST.getSourceManager().getSpellingLineNumber(Range.getBegin()); |
433 | 1 | *OS << "<template data-copy='code'>\n"; |
434 | 1 | *OS << "<code class='filename'>"; |
435 | 1 | llvm::printHTMLEscaped( |
436 | 1 | llvm::sys::path::filename( |
437 | 1 | AST.getSourceManager().getFilename(Range.getBegin())), |
438 | 1 | *OS); |
439 | 1 | *OS << "</code>"; |
440 | 1 | *OS << "<code class='line' data-line='" << Line++ << "'>"; |
441 | 61 | for (unsigned I = 0; I < Code.size(); ++I60 ) { |
442 | | // Don't actually write a <span> around each character, only break spans |
443 | | // when the TokenInfo changes. |
444 | 60 | bool NeedOpen = I == 0 || !(State[I] == State[I-1])59 ; |
445 | 60 | bool NeedClose = I + 1 == Code.size() || !(State[I] == State[I + 1])59 ; |
446 | 60 | if (NeedOpen) { |
447 | 8 | *OS << "<span "; |
448 | 8 | State[I].write(*OS); |
449 | 8 | *OS << ">"; |
450 | 8 | } |
451 | 60 | if (Code[I] == '\n') |
452 | 2 | *OS << "</code>\n<code class='line' data-line='" << Line++ << "'>"; |
453 | 58 | else |
454 | 58 | llvm::printHTMLEscaped(Code.substr(I, 1), *OS); |
455 | 60 | if (NeedClose) *OS << "</span>"8 ; |
456 | 60 | } |
457 | 1 | *OS << "</code>\n"; |
458 | 1 | *OS << "</template>"; |
459 | 1 | } |
460 | | |
461 | | // Write the CFG diagram, a graph of basic blocks. |
462 | | // Laying out graphs is hard, so we construct a graphviz description and shell |
463 | | // out to `dot` to turn it into an SVG. |
464 | 1 | void writeCFG() { |
465 | 1 | *OS << "<template data-copy='cfg'>\n"; |
466 | 1 | if (auto SVG = renderSVG(buildCFGDot(CFG->getCFG()))) |
467 | 0 | *OS << *SVG; |
468 | 1 | else |
469 | 1 | *OS << "Can't draw CFG: " << toString(SVG.takeError()); |
470 | 1 | *OS << "</template>\n"; |
471 | 1 | } |
472 | | |
473 | | // Produce a graphviz description of a CFG. |
474 | 1 | static std::string buildCFGDot(const clang::CFG &CFG) { |
475 | 1 | std::string Graph; |
476 | 1 | llvm::raw_string_ostream GraphS(Graph); |
477 | | // Graphviz likes to add unhelpful tooltips everywhere, " " suppresses. |
478 | 1 | GraphS << R"(digraph { |
479 | 1 | tooltip=" " |
480 | 1 | node[class=bb, shape=square, fontname="sans-serif", tooltip=" "] |
481 | 1 | edge[tooltip = " "] |
482 | 1 | )"; |
483 | 7 | for (unsigned I = 0; I < CFG.getNumBlockIDs(); ++I6 ) |
484 | 6 | GraphS << " " << blockID(I) << " [id=" << blockID(I) << "]\n"; |
485 | 6 | for (const auto *Block : CFG) { |
486 | 6 | for (const auto &Succ : Block->succs()) { |
487 | 6 | GraphS << " " << blockID(Block->getBlockID()) << " -> " |
488 | 6 | << blockID(Succ.getReachableBlock()->getBlockID()) << "\n"; |
489 | 6 | } |
490 | 6 | } |
491 | 1 | GraphS << "}\n"; |
492 | 1 | return Graph; |
493 | 1 | } |
494 | | }; |
495 | | |
496 | | // Nothing interesting here, just subprocess/temp-file plumbing. |
497 | 1 | llvm::Expected<std::string> renderSVG(llvm::StringRef DotGraph) { |
498 | 1 | std::string DotPath; |
499 | 1 | if (const auto *FromEnv = ::getenv("GRAPHVIZ_DOT")) |
500 | 0 | DotPath = FromEnv; |
501 | 1 | else { |
502 | 1 | auto FromPath = llvm::sys::findProgramByName("dot"); |
503 | 1 | if (!FromPath) |
504 | 1 | return llvm::createStringError(FromPath.getError(), |
505 | 1 | "'dot' not found on PATH"); |
506 | 0 | DotPath = FromPath.get(); |
507 | 0 | } |
508 | | |
509 | | // Create input and output files for `dot` subprocess. |
510 | | // (We create the output file as empty, to reserve the temp filename). |
511 | 0 | llvm::SmallString<256> Input, Output; |
512 | 0 | int InputFD; |
513 | 0 | if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".dot", InputFD, |
514 | 0 | Input)) |
515 | 0 | return llvm::createStringError(EC, "failed to create `dot` temp input"); |
516 | 0 | llvm::raw_fd_ostream(InputFD, /*shouldClose=*/true) << DotGraph; |
517 | 0 | auto DeleteInput = |
518 | 0 | llvm::make_scope_exit([&] { llvm::sys::fs::remove(Input); }); |
519 | 0 | if (auto EC = llvm::sys::fs::createTemporaryFile("analysis", ".svg", Output)) |
520 | 0 | return llvm::createStringError(EC, "failed to create `dot` temp output"); |
521 | 0 | auto DeleteOutput = |
522 | 0 | llvm::make_scope_exit([&] { llvm::sys::fs::remove(Output); }); |
523 | |
|
524 | 0 | std::vector<std::optional<llvm::StringRef>> Redirects = { |
525 | 0 | Input, Output, |
526 | 0 | /*stderr=*/std::nullopt}; |
527 | 0 | std::string ErrMsg; |
528 | 0 | int Code = llvm::sys::ExecuteAndWait( |
529 | 0 | DotPath, {"dot", "-Tsvg"}, /*Env=*/std::nullopt, Redirects, |
530 | 0 | /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg); |
531 | 0 | if (!ErrMsg.empty()) |
532 | 0 | return llvm::createStringError(llvm::inconvertibleErrorCode(), |
533 | 0 | "'dot' failed: " + ErrMsg); |
534 | 0 | if (Code != 0) |
535 | 0 | return llvm::createStringError(llvm::inconvertibleErrorCode(), |
536 | 0 | "'dot' failed (" + llvm::Twine(Code) + ")"); |
537 | | |
538 | 0 | auto Buf = llvm::MemoryBuffer::getFile(Output); |
539 | 0 | if (!Buf) |
540 | 0 | return llvm::createStringError(Buf.getError(), "Can't read `dot` output"); |
541 | | |
542 | | // Output has <?xml> prefix we don't want. Skip to <svg> tag. |
543 | 0 | llvm::StringRef Result = Buf.get()->getBuffer(); |
544 | 0 | auto Pos = Result.find("<svg"); |
545 | 0 | if (Pos == llvm::StringRef::npos) |
546 | 0 | return llvm::createStringError(llvm::inconvertibleErrorCode(), |
547 | 0 | "Can't find <svg> tag in `dot` output"); |
548 | 0 | return Result.substr(Pos).str(); |
549 | 0 | } |
550 | | |
551 | | } // namespace |
552 | | |
553 | | std::unique_ptr<Logger> |
554 | 1 | Logger::html(std::function<std::unique_ptr<llvm::raw_ostream>()> Streams) { |
555 | 1 | return std::make_unique<HTMLLogger>(std::move(Streams)); |
556 | 1 | } |
557 | | |
558 | | } // namespace clang::dataflow |