fe.cc (8201B)
1 // 2 // Copyright (c) 2016 Kyle Milz <kyle@0x30.net> 3 // 4 // Permission to use, copy, modify, and distribute this software for any 5 // purpose with or without fee is hereby granted, provided that the above 6 // copyright notice and this permission notice appear in all copies. 7 // 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 // 16 #include "action.h" // InstrumentActionFactory 17 #include "fe.h" 18 #include "citrun.h" // citrun_major, citrun_minor 19 20 #include <clang/Basic/Diagnostic.h> // IgnoringDiagConsumer 21 #include <clang/Tooling/CommonOptionsParser.h> 22 #include <clang/Tooling/Tooling.h> 23 #include <llvm/Support/raw_os_ostream.h> 24 25 #include <algorithm> // std::find_if 26 #include <cstdio> // tmpnam 27 #include <cstring> // strcmp 28 #include <iostream> // std::cerr 29 #include <sstream> // std::ostringstream 30 31 32 static llvm::cl::OptionCategory ToolingCategory("citrun_inst options"); 33 34 InstFrontend::InstFrontend(int argc, char *argv[], bool is_citrun_inst) : 35 m_start_time(std::chrono::high_resolution_clock::now()), 36 m_args(argv, argv + argc), 37 m_is_citruninst(is_citrun_inst), 38 m_log(is_citrun_inst) 39 { 40 } 41 42 InstFrontend::~InstFrontend() 43 { 44 } 45 46 void 47 InstFrontend::log_identity() 48 { 49 m_log << ">> citrun_inst v" << citrun_major << "." << citrun_minor; 50 log_os_str(); 51 m_log << " called as " << m_args[0] << std::endl; 52 } 53 54 void 55 InstFrontend::get_paths() 56 { 57 m_compilers_path = PREFIX ; 58 m_compilers_path += dir_sep() ; 59 m_compilers_path += "share" ; 60 // We're not ready for this yet. 61 //m_compilers_path += dir_sep() ; 62 //m_compilers_path += "citrun" ; 63 64 m_lib_path = PREFIX ; 65 m_lib_path += dir_sep(); 66 m_lib_path += lib_name(); 67 68 m_log << "Compilers path = '" << m_compilers_path << "'" << std::endl; 69 } 70 71 // 72 // Tries to remove m_compilers_path from PATH otherwise it exits easily. 73 // 74 void 75 InstFrontend::clean_PATH() 76 { 77 if (m_is_citruninst == true) 78 return; 79 80 char *path; 81 if ((path = std::getenv("PATH")) == NULL) { 82 std::cerr << "Error: PATH is not set." << std::endl; 83 m_log << "Error: PATH is not set." << std::endl; 84 exit(1); 85 } 86 87 m_log << "PATH = '" << path << "'" << std::endl; 88 89 // Filter m_compilers_path out of PATH 90 std::stringstream path_ss(path); 91 std::string component; 92 bool first_component = true; 93 bool found_citrun_path = false; 94 std::ostringstream new_path; 95 96 while (std::getline(path_ss, component, path_sep())) { 97 if (component == m_compilers_path) { 98 found_citrun_path = true; 99 continue; 100 } 101 102 if (first_component == false) 103 new_path << path_sep(); 104 105 // It wasn't m_compilers_path, keep it 106 new_path << component; 107 first_component = false; 108 } 109 110 if (!found_citrun_path) { 111 // 112 // This is a really bad situation to be in. We are currently 113 // executing and can't tell which PATH element we were called 114 // from. If we exec there's a chance we'll get stuck in an 115 // infinite exec loop. 116 // 117 // Error visibly so this can be fixed as soon as possible. 118 // 119 std::stringstream err; 120 err << "Error: '" << m_compilers_path << "' not in PATH."; 121 122 std::cerr << err.str() << std::endl; 123 m_log << err.str() << std::endl; 124 exit(1); 125 } 126 127 set_path(new_path.str()); 128 } 129 130 // 131 // Guess if the argument is a source file. If it is stash a backup of the file 132 // and sync the timestamps. 133 // 134 void 135 InstFrontend::save_if_srcfile(char *arg) 136 { 137 std::array<std::string, 4> exts = {{ ".c", ".cc", ".cxx", ".cpp" }}; 138 if (std::find_if(exts.begin(), exts.end(), ends_with(arg)) == exts.end()) 139 return; 140 141 char *dst_fn; 142 if ((dst_fn = std::tmpnam(NULL)) == NULL) { 143 m_log << "tmpnam failed." << std::endl; 144 return; 145 } 146 147 m_source_files.push_back(arg); 148 m_log << "Found source file '" << arg << "'" << std::endl; 149 150 if (m_is_citruninst) 151 // In this mode the modified source file is written to a 152 // completely different file. 153 return; 154 155 copy_file(dst_fn, arg); 156 m_temp_file_map[arg] = dst_fn; 157 } 158 159 // 160 // Walks the entire command line taking action on important arguments. 161 // 162 void 163 InstFrontend::process_cmdline() 164 { 165 bool object_arg = false; 166 bool compile_arg = false; 167 168 // 169 // Walk every argument one by one looking for preprocessor switches, 170 // compile mode flags and source files. 171 // 172 for (auto &arg : m_args) { 173 if (std::strcmp(arg, "-E") == 0 || std::strcmp(arg, "-MM") == 0) { 174 // I don't know the repercussions of doing otherwise. 175 m_log << "Preprocessor argument " << arg << " found" 176 << std::endl; 177 exec_compiler(); 178 } 179 else if (std::strcmp(arg, "-o") == 0) 180 object_arg = true; 181 else if (std::strcmp(arg, "-c") == 0) 182 compile_arg = true; 183 #ifdef _WIN32 184 else if (std::strcmp(arg, "/c") == 0) 185 compile_arg = true; 186 #endif // _WIN32 187 188 save_if_srcfile(arg); 189 } 190 191 if (is_link(object_arg, compile_arg)) { 192 m_log << "Link detected, adding '"<< m_lib_path 193 << "' to command line." << std::endl; 194 m_args.push_back(const_cast<char *>(m_lib_path.c_str())); 195 } 196 197 m_log << "Command line is '" << m_args[0]; 198 for (unsigned int i = 1; i < m_args.size(); ++i) 199 m_log << " " << m_args[i]; 200 m_log << "'" << std::endl; 201 202 if (m_source_files.size() != 0) 203 return; 204 205 m_log << "No source files found on command line." << std::endl; 206 exec_compiler(); 207 } 208 209 // 210 // Creates and executes InstrumentAction objects for detected source files. 211 // 212 void 213 InstFrontend::instrument() 214 { 215 // 216 // Create a special command line for ClangTool that looks like: 217 // clang++ src1.c src2.c -- clang++ -I. -Isrc -c src1.c src2.c 218 // 219 std::vector<const char *> clang_argv; 220 221 clang_argv.push_back(m_args[0]); 222 for (auto &s : m_source_files) 223 clang_argv.push_back(s.c_str()); 224 clang_argv.push_back("--"); 225 clang_argv.insert(clang_argv.end(), m_args.begin(), m_args.end()); 226 #if defined(__OpenBSD__) 227 clang_argv.push_back("-I/usr/local/lib/clang/3.8.0/include"); 228 m_log << "Added clangtool argument '" << clang_argv.back() << "'" << std::endl; 229 #elif defined(__APPLE__) 230 clang_argv.push_back("-I/opt/local/libexec/llvm-3.8/lib/clang/3.8.1/include"); 231 m_log << "Added clangtool argument '" << clang_argv.back() << "'" << std::endl; 232 #elif defined(WIN32) 233 clang_argv.push_back(R"(-IC:\Clang\lib\clang\3.9.1\include)"); 234 m_log << "Added clangtool argument '" << clang_argv.back() << "'" << std::endl; 235 #endif 236 237 int clang_argc = clang_argv.size(); 238 llvm::Expected<clang::tooling::CommonOptionsParser> op = 239 clang::tooling::CommonOptionsParser::create( 240 clang_argc, &clang_argv[0], ToolingCategory); 241 clang::tooling::ClangTool 242 Tool(op->getCompilations(), op->getSourcePathList()); 243 244 // 245 // Ignore all errors/warnings by default. 246 // This makes Tool.run() always return 0 too. 247 // 248 Tool.setDiagnosticConsumer(new clang::IgnoringDiagConsumer()); 249 250 std::unique_ptr<InstrumentActionFactory> f = 251 std::make_unique<InstrumentActionFactory>(m_log, m_is_citruninst, m_source_files); 252 253 // 254 // Run instrumentation. All source files are processed here. 255 // 256 Tool.run(f.get()); 257 258 // All of the time until now is the overhead citrun_inst adds. 259 std::chrono::high_resolution_clock::time_point now = 260 std::chrono::high_resolution_clock::now(); 261 m_log << std::chrono::duration_cast<std::chrono::milliseconds>(now - m_start_time).count() 262 << " Milliseconds spent rewriting source." << std::endl; 263 264 // This is as far as we go in citrun_inst mode. 265 if (m_is_citruninst) 266 exit(0); 267 } 268 269 // 270 // Restore source files from stashed backups and sync timestamps. 271 // 272 void 273 InstFrontend::restore_original_src() 274 { 275 for (auto &tmp_file : m_temp_file_map) { 276 m_log << "Restored '" << tmp_file.first << "'" << std::endl; 277 278 copy_file(tmp_file.first, tmp_file.second); 279 unlink(tmp_file.second.c_str()); 280 } 281 } 282 283 void 284 InstFrontend::compile_instrumented() 285 { 286 int ret; 287 288 ret = fork_compiler(); 289 m_log << "Rewritten source compile " << (ret ? "failed" : "successful") 290 << std::endl; 291 292 restore_original_src(); 293 294 if (ret) 295 // Rewritten compile failed. Run again without modified src. 296 exec_compiler(); 297 }