citrun

watch C/C++ source code execute
Log | Files | Refs | LICENSE

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 }