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