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