Coverage Report

Created: 2023-05-31 04:38

/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