1//===- GraphWriter.cpp - Implements GraphWriter support routines ----------===//
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 misc. GraphWriter support routines.
10//
11//===----------------------------------------------------------------------===//
12
13#include "llvm/Support/GraphWriter.h"
14#include "llvm/ADT/SmallString.h"
15#include "llvm/ADT/SmallVector.h"
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Config/config.h"
18#include "llvm/Support/CommandLine.h"
19#include "llvm/Support/Compiler.h"
20#include "llvm/Support/ErrorHandling.h"
21#include "llvm/Support/ErrorOr.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/Program.h"
24#include "llvm/Support/raw_ostream.h"
25#include <cassert>
26#include <system_error>
27#include <string>
28#include <vector>
29
30using namespace llvm;
31
32static cl::opt<bool> ViewBackground("view-background", cl::Hidden,
33  cl::desc("Execute graph viewer in the background. Creates tmp file litter."));
34
35std::string llvm::DOT::EscapeString(const std::string &Label) {
36  std::string Str(Label);
37  for (unsigned i = 0; i != Str.length(); ++i)
38  switch (Str[i]) {
39    case '\n':
40      Str.insert(Str.begin()+i, '\\');  // Escape character...
41      ++i;
42      Str[i] = 'n';
43      break;
44    case '\t':
45      Str.insert(Str.begin()+i, ' ');  // Convert to two spaces
46      ++i;
47      Str[i] = ' ';
48      break;
49    case '\\':
50      if (i+1 != Str.length())
51        switch (Str[i+1]) {
52          case 'l': continue; // don't disturb \l
53          case '|': case '{': case '}':
54            Str.erase(Str.begin()+i); continue;
55          default: break;
56        }
57        LLVM_FALLTHROUGH;
58    case '{': case '}':
59    case '<': case '>':
60    case '|': case '"':
61      Str.insert(Str.begin()+i, '\\');  // Escape character...
62      ++i;  // don't infinite loop
63      break;
64  }
65  return Str;
66}
67
68/// Get a color string for this node number. Simply round-robin selects
69/// from a reasonable number of colors.
70StringRef llvm::DOT::getColorString(unsigned ColorNumber) {
71  static const int NumColors = 20;
72  static const char* Colors[NumColors] = {
73    "aaaaaa", "aa0000", "00aa00", "aa5500", "0055ff", "aa00aa", "00aaaa",
74    "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", "55ffff",
75    "ffaaaa", "aaffaa", "ffffaa", "aaaaff", "ffaaff", "aaffff"};
76  return Colors[ColorNumber % NumColors];
77}
78
79std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
80  FD = -1;
81  SmallString<128> Filename;
82  std::error_code EC = sys::fs::createTemporaryFile(Name, "dot", FD, Filename);
83  if (EC) {
84    errs() << "Error: " << EC.message() << "\n";
85    return "";
86  }
87
88  errs() << "Writing '" << Filename << "'... ";
89  return Filename.str();
90}
91
92// Execute the graph viewer. Return true if there were errors.
93static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
94                            StringRef Filename, bool wait,
95                            std::string &ErrMsg) {
96  if (wait) {
97    if (sys::ExecuteAndWait(ExecPath, args, None, {}, 0, 0, &ErrMsg)) {
98      errs() << "Error: " << ErrMsg << "\n";
99      return true;
100    }
101    sys::fs::remove(Filename);
102    errs() << " done. \n";
103  } else {
104    sys::ExecuteNoWait(ExecPath, args, None, {}, 0, &ErrMsg);
105    errs() << "Remember to erase graph file: " << Filename << "\n";
106  }
107  return false;
108}
109
110namespace {
111
112struct GraphSession {
113  std::string LogBuffer;
114
115  bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
116    raw_string_ostream Log(LogBuffer);
117    SmallVector<StringRef, 8> parts;
118    Names.split(parts, '|');
119    for (auto Name : parts) {
120      if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
121        ProgramPath = *P;
122        return true;
123      }
124      Log << "  Tried '" << Name << "'\n";
125    }
126    return false;
127  }
128};
129
130} // end anonymous namespace
131
132static const char *getProgramName(GraphProgram::Name program) {
133  switch (program) {
134  case GraphProgram::DOT:
135    return "dot";
136  case GraphProgram::FDP:
137    return "fdp";
138  case GraphProgram::NEATO:
139    return "neato";
140  case GraphProgram::TWOPI:
141    return "twopi";
142  case GraphProgram::CIRCO:
143    return "circo";
144  }
145  llvm_unreachable("bad kind");
146}
147
148bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
149                        GraphProgram::Name program) {
150  std::string Filename = FilenameRef;
151  std::string ErrMsg;
152  std::string ViewerPath;
153  GraphSession S;
154
155#ifdef __APPLE__
156  wait &= !ViewBackground;
157  if (S.TryFindProgram("open", ViewerPath)) {
158    std::vector<StringRef> args;
159    args.push_back(ViewerPath);
160    if (wait)
161      args.push_back("-W");
162    args.push_back(Filename);
163    errs() << "Trying 'open' program... ";
164    if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
165      return false;
166  }
167#endif
168  if (S.TryFindProgram("xdg-open", ViewerPath)) {
169    std::vector<StringRef> args;
170    args.push_back(ViewerPath);
171    args.push_back(Filename);
172    errs() << "Trying 'xdg-open' program... ";
173    if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
174      return false;
175  }
176
177  // Graphviz
178  if (S.TryFindProgram("Graphviz", ViewerPath)) {
179    std::vector<StringRef> args;
180    args.push_back(ViewerPath);
181    args.push_back(Filename);
182
183    errs() << "Running 'Graphviz' program... ";
184    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
185  }
186
187  // xdot
188  if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
189    std::vector<StringRef> args;
190    args.push_back(ViewerPath);
191    args.push_back(Filename);
192
193    args.push_back("-f");
194    args.push_back(getProgramName(program));
195
196    errs() << "Running 'xdot.py' program... ";
197    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
198  }
199
200  enum ViewerKind {
201    VK_None,
202    VK_OSXOpen,
203    VK_XDGOpen,
204    VK_Ghostview,
205    VK_CmdStart
206  };
207  ViewerKind Viewer = VK_None;
208#ifdef __APPLE__
209  if (!Viewer && S.TryFindProgram("open", ViewerPath))
210    Viewer = VK_OSXOpen;
211#endif
212  if (!Viewer && S.TryFindProgram("gv", ViewerPath))
213    Viewer = VK_Ghostview;
214  if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
215    Viewer = VK_XDGOpen;
216#ifdef _WIN32
217  if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
218    Viewer = VK_CmdStart;
219  }
220#endif
221
222  // PostScript or PDF graph generator + PostScript/PDF viewer
223  std::string GeneratorPath;
224  if (Viewer &&
225      (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
226       S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
227    std::string OutputFilename =
228        Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
229
230    std::vector<StringRef> args;
231    args.push_back(GeneratorPath);
232    if (Viewer == VK_CmdStart)
233      args.push_back("-Tpdf");
234    else
235      args.push_back("-Tps");
236    args.push_back("-Nfontname=Courier");
237    args.push_back("-Gsize=7.5,10");
238    args.push_back(Filename);
239    args.push_back("-o");
240    args.push_back(OutputFilename);
241
242    errs() << "Running '" << GeneratorPath << "' program... ";
243
244    if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
245      return true;
246
247    // The lifetime of StartArg must include the call of ExecGraphViewer
248    // because the args are passed as vector of char*.
249    std::string StartArg;
250
251    args.clear();
252    args.push_back(ViewerPath);
253    switch (Viewer) {
254    case VK_OSXOpen:
255      args.push_back("-W");
256      args.push_back(OutputFilename);
257      break;
258    case VK_XDGOpen:
259      wait = false;
260      args.push_back(OutputFilename);
261      break;
262    case VK_Ghostview:
263      args.push_back("--spartan");
264      args.push_back(OutputFilename);
265      break;
266    case VK_CmdStart:
267      args.push_back("/S");
268      args.push_back("/C");
269      StartArg =
270          (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
271      args.push_back(StartArg);
272      break;
273    case VK_None:
274      llvm_unreachable("Invalid viewer");
275    }
276
277    ErrMsg.clear();
278    return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
279  }
280
281  // dotty
282  if (S.TryFindProgram("dotty", ViewerPath)) {
283    std::vector<StringRef> args;
284    args.push_back(ViewerPath);
285    args.push_back(Filename);
286
287// Dotty spawns another app and doesn't wait until it returns
288#ifdef _WIN32
289    wait = false;
290#endif
291    errs() << "Running 'dotty' program... ";
292    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
293  }
294
295  errs() << "Error: Couldn't find a usable graph viewer program:\n";
296  errs() << S.LogBuffer << "\n";
297  return true;
298}
299