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