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