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
79static std::string replaceIllegalFilenameChars(std::string Filename,
80                                               const char ReplacementChar) {
81#ifdef _WIN32
82  std::string IllegalChars = "\\/:?\"<>|";
83#else
84  std::string IllegalChars = "/";
85#endif
86
87  for (char IllegalChar : IllegalChars) {
88    std::replace(Filename.begin(), Filename.end(), IllegalChar,
89                 ReplacementChar);
90  }
91
92  return Filename;
93}
94
95std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
96  FD = -1;
97  SmallString<128> Filename;
98
99  // Windows can't always handle long paths, so limit the length of the name.
100  std::string N = Name.str();
101  N = N.substr(0, std::min<std::size_t>(N.size(), 140));
102
103  // Replace illegal characters in graph Filename with '_' if needed
104  std::string CleansedName = replaceIllegalFilenameChars(N, '_');
105
106  std::error_code EC =
107      sys::fs::createTemporaryFile(CleansedName, "dot", FD, Filename);
108  if (EC) {
109    errs() << "Error: " << EC.message() << "\n";
110    return "";
111  }
112
113  errs() << "Writing '" << Filename << "'... ";
114  return std::string(Filename.str());
115}
116
117// Execute the graph viewer. Return true if there were errors.
118static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
119                            StringRef Filename, bool wait,
120                            std::string &ErrMsg) {
121  if (wait) {
122    if (sys::ExecuteAndWait(ExecPath, args, None, {}, 0, 0, &ErrMsg)) {
123      errs() << "Error: " << ErrMsg << "\n";
124      return true;
125    }
126    sys::fs::remove(Filename);
127    errs() << " done. \n";
128  } else {
129    sys::ExecuteNoWait(ExecPath, args, None, {}, 0, &ErrMsg);
130    errs() << "Remember to erase graph file: " << Filename << "\n";
131  }
132  return false;
133}
134
135namespace {
136
137struct GraphSession {
138  std::string LogBuffer;
139
140  bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
141    raw_string_ostream Log(LogBuffer);
142    SmallVector<StringRef, 8> parts;
143    Names.split(parts, '|');
144    for (auto Name : parts) {
145      if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
146        ProgramPath = *P;
147        return true;
148      }
149      Log << "  Tried '" << Name << "'\n";
150    }
151    return false;
152  }
153};
154
155} // end anonymous namespace
156
157static const char *getProgramName(GraphProgram::Name program) {
158  switch (program) {
159  case GraphProgram::DOT:
160    return "dot";
161  case GraphProgram::FDP:
162    return "fdp";
163  case GraphProgram::NEATO:
164    return "neato";
165  case GraphProgram::TWOPI:
166    return "twopi";
167  case GraphProgram::CIRCO:
168    return "circo";
169  }
170  llvm_unreachable("bad kind");
171}
172
173bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
174                        GraphProgram::Name program) {
175  std::string Filename = std::string(FilenameRef);
176  std::string ErrMsg;
177  std::string ViewerPath;
178  GraphSession S;
179
180#ifdef __APPLE__
181  wait &= !ViewBackground;
182  if (S.TryFindProgram("open", ViewerPath)) {
183    std::vector<StringRef> args;
184    args.push_back(ViewerPath);
185    if (wait)
186      args.push_back("-W");
187    args.push_back(Filename);
188    errs() << "Trying 'open' program... ";
189    if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
190      return false;
191  }
192#endif
193  if (S.TryFindProgram("xdg-open", ViewerPath)) {
194    std::vector<StringRef> args;
195    args.push_back(ViewerPath);
196    args.push_back(Filename);
197    errs() << "Trying 'xdg-open' program... ";
198    if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
199      return false;
200  }
201
202  // Graphviz
203  if (S.TryFindProgram("Graphviz", ViewerPath)) {
204    std::vector<StringRef> args;
205    args.push_back(ViewerPath);
206    args.push_back(Filename);
207
208    errs() << "Running 'Graphviz' program... ";
209    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
210  }
211
212  // xdot
213  if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
214    std::vector<StringRef> args;
215    args.push_back(ViewerPath);
216    args.push_back(Filename);
217
218    args.push_back("-f");
219    args.push_back(getProgramName(program));
220
221    errs() << "Running 'xdot.py' program... ";
222    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
223  }
224
225  enum ViewerKind {
226    VK_None,
227    VK_OSXOpen,
228    VK_XDGOpen,
229    VK_Ghostview,
230    VK_CmdStart
231  };
232  ViewerKind Viewer = VK_None;
233#ifdef __APPLE__
234  if (!Viewer && S.TryFindProgram("open", ViewerPath))
235    Viewer = VK_OSXOpen;
236#endif
237  if (!Viewer && S.TryFindProgram("gv", ViewerPath))
238    Viewer = VK_Ghostview;
239  if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
240    Viewer = VK_XDGOpen;
241#ifdef _WIN32
242  if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
243    Viewer = VK_CmdStart;
244  }
245#endif
246
247  // PostScript or PDF graph generator + PostScript/PDF viewer
248  std::string GeneratorPath;
249  if (Viewer &&
250      (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
251       S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
252    std::string OutputFilename =
253        Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
254
255    std::vector<StringRef> args;
256    args.push_back(GeneratorPath);
257    if (Viewer == VK_CmdStart)
258      args.push_back("-Tpdf");
259    else
260      args.push_back("-Tps");
261    args.push_back("-Nfontname=Courier");
262    args.push_back("-Gsize=7.5,10");
263    args.push_back(Filename);
264    args.push_back("-o");
265    args.push_back(OutputFilename);
266
267    errs() << "Running '" << GeneratorPath << "' program... ";
268
269    if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
270      return true;
271
272    // The lifetime of StartArg must include the call of ExecGraphViewer
273    // because the args are passed as vector of char*.
274    std::string StartArg;
275
276    args.clear();
277    args.push_back(ViewerPath);
278    switch (Viewer) {
279    case VK_OSXOpen:
280      args.push_back("-W");
281      args.push_back(OutputFilename);
282      break;
283    case VK_XDGOpen:
284      wait = false;
285      args.push_back(OutputFilename);
286      break;
287    case VK_Ghostview:
288      args.push_back("--spartan");
289      args.push_back(OutputFilename);
290      break;
291    case VK_CmdStart:
292      args.push_back("/S");
293      args.push_back("/C");
294      StartArg =
295          (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
296      args.push_back(StartArg);
297      break;
298    case VK_None:
299      llvm_unreachable("Invalid viewer");
300    }
301
302    ErrMsg.clear();
303    return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
304  }
305
306  // dotty
307  if (S.TryFindProgram("dotty", ViewerPath)) {
308    std::vector<StringRef> args;
309    args.push_back(ViewerPath);
310    args.push_back(Filename);
311
312// Dotty spawns another app and doesn't wait until it returns
313#ifdef _WIN32
314    wait = false;
315#endif
316    errs() << "Running 'dotty' program... ";
317    return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
318  }
319
320  errs() << "Error: Couldn't find a usable graph viewer program:\n";
321  errs() << S.LogBuffer << "\n";
322  return true;
323}
324