Macro Hunter



  • Macro Hunter ist ein kleines Program mit dem man C/C++ code nach Macro Definitionen zu Suchen. Dabei wird eine Datei oder gleich ein gesamtes Verzeichniss nach Macros durchforstet und anschließend werden auch noch sämtliche Includes durchforstet.

    Es könnte nützlich sein um die Verursacher von Namenskonflikten zu finden.

    Zum Beispiel, folgende Kommandozeile

    mhunt assert test.cpp -IC:\mingw\include -C
    

    produziert folgende Ausgabe:

    Found a macro definition : { #define assert(x) }
    In file C:\mingw\include\assert.h at line 30
    Included by : C:\test2.h
    Included by : C:\test.h
    Included by : C:\test.cpp
    Found a macro definition : { #define assert(e) }
    In file C:\mingw\include\assert.h at line 43
    Included by : C:\test2.h
    Included by : C:\test.h
    Included by : C:\test.cpp
    

    Oder hier noch ein Beispiel wie man den blöden Header findet der "small" definiert:

    mhunt -I C:\mingw\include -I C:\wxWidgets-2.6.1\include small -CHR
    
    Found a macro definition : { #define small }
    In file C:\mingw\include\rpcndr.h at line 49
    Included by : C:\mingw\include\wtypes.h
    Included by : C:\mingw\include\unknwn.h
    Included by : C:\mingw\include\commdlg.h
    Included by : C:\mingw\include\windows.h
    Included by : C:\wxWidgets-2.6.1\include\wx\msw\wrapwin.h
    Included by : C:\wxWidgets-2.6.1\include\wx\msw\private.h
    Included by : C:\wxWidgets-2.6.1\include\wx\filefn.h
    Included by : C:\wxWidgets-2.6.1\include\wx\utils.h
    Included by : C:\wxWidgets-2.6.1\include\wx\cursor.h
    Included by : C:\wxWidgets-2.6.1\include\wx\event.h
    Included by : C:\wxWidgets-2.6.1\include\wx\window.h
    Included by : C:\wxWidgets-2.6.1\include\wx\toplevel.h
    Included by : C:\wxWidgets-2.6.1\include\wx\dialog.h
    Included by : C:\wxWidgets-2.6.1\include\wx\generic\msgdlgg.h
    Included by : C:\wxWidgets-2.6.1\include\wx\msgdlg.h
    Included by : [...Private ;)...]\app.cpp
    [...noch 3 weitere solcher Treffer...]
    

    Dies Program hab ich haubsächlich geschrieben um mich ein bischen in manche Boost Bibliotheken einzuarbeiten. Da es aber trotzdem einen kleinen Nutzen haben könnte poste ich den Code einfach mal:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <set>
    #include <iterator>
    #include <algorithm>
    #include <boost/filesystem/path.hpp>
    #include <boost/filesystem/operations.hpp>
    #include <boost/filesystem/fstream.hpp>
    #include <boost/filesystem/exception.hpp>
    #include <boost/filesystem/convenience.hpp>
    #include <boost/bind.hpp>
    #include <boost/program_options.hpp>
    #include <boost/regex.hpp>
    
    namespace fs = boost::filesystem;
    namespace po = boost::program_options;
    
    bool suppress_header_not_found_error = false;
    bool show_header_include_chain = false;
    bool show_scanned_files = false;
    bool suppress_recusive_include_error = false;
    bool visit_all = false;
    
    std::vector<fs::path>library_paths;
    std::vector<std::string>file_ext;
    std::vector<std::string>macro;
    std::set<fs::path>visited;
    
    std::vector<fs::path>include_chain;
    
    fs::path lookup_lib_include(fs::path file){
      for(std::vector<fs::path>::const_iterator i=library_paths.begin(), end = library_paths.end();
        i != end; ++i)
        if(fs::exists(*i / file))
          return *i / file;
      return fs::path();
    }
    
    fs::path lookup_local_include(fs::path file){
      if(fs::exists(fs::complete(file)))
        return fs::complete(file);
      else
        return lookup_lib_include(file);
    }
    
    void scan(fs::path file);
    
    std::string kill_backslashes(std::string str){
      std::replace(str.begin(), str.end(), '\\', '/');
      return str;
    }
    
    void follow_global_include(boost::match_results<std::vector<char>::const_iterator>m){
      fs::path file = lookup_lib_include(kill_backslashes(std::string(m[1].first, m[1].second)));
      if(!file.empty())
        scan(file);
      else if(!suppress_header_not_found_error)
        std::cerr<<"Could not follow an include to "<<std::string(m[1].first, m[1].second)<<std::endl;
    }
    
    void follow_local_include(boost::match_results<std::vector<char>::const_iterator>m){
      fs::path file = lookup_local_include(kill_backslashes(std::string(m[1].first, m[1].second)));
      if(!file.empty())
        scan(file);
      else if(!suppress_header_not_found_error)
        std::cerr<<"Could not follow an include to "<<std::string(m[1].first, m[1].second)<<std::endl;
    }
    
    void print_included_by_msg(fs::path file){
      std::cout<<"Included by : "<<file.native_file_string()<<std::endl;
    }
    
    void check_macro_def(const std::vector<unsigned>&line_offset, boost::match_results<std::vector<char>::const_iterator>m){
      if(std::find(macro.begin(), macro.end(), std::string(m[1].first, m[1].second)) != macro.end()){
        std::cout<<"Found a macro definition : { "<<std::string(m[0].first, m[0].second)<<" }"<<std::endl;
    
        std::cout<<"In file "<<include_chain.back().native_file_string()<<" at line "
          <<std::upper_bound(line_offset.begin(), line_offset.end(), static_cast<unsigned>(m.position())) - line_offset.begin() + 1 << std::endl;
        if(show_header_include_chain)
          std::for_each(include_chain.rbegin() + 1, include_chain.rend(), print_included_by_msg);
    
      }
    }
    
    void scan(fs::path file){
      fs::ifstream in(file);
      if(!in)
        std::clog<<"Could not open "<<file.native_file_string()<<std::endl;
      else{
        if(std::find(include_chain.begin(), include_chain.end(), file) != include_chain.end()){
          if(!suppress_recusive_include_error)
            std::clog<<"Not following an include to "<<file.native_file_string()<<" because it is included recursivly"<<std::endl;;
        }else if(visit_all || visited.find(file) == visited.end()){
          include_chain.push_back(file);
    			visited.insert(file);
          if(show_scanned_files)
            std::cout<<"Scanning "<<file.native_file_string()<<std::endl;
    
          // Map the data into memory as we need bidirectional access. std::vector because there is no std::string::puch_back.
          typedef std::vector<char>::const_iterator citer;
          std::vector<char>file_data;
          std::copy(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>(), std::back_inserter(file_data));
    
          // Extract line numbers
          std::vector<unsigned>line_offsets;
          for(citer begin = file_data.begin(), end = file_data.end(), new_line = std::find(begin, end, '\n');
            new_line != end; new_line = std::find(new_line + 1, end, '\n'))
            line_offsets.push_back(new_line - begin);
    
          boost::regex local_include_pattern("\\#[[:space:]]*include[[:space:]]*\"(.*?)\"");
          std::for_each(
            boost::regex_iterator<citer>(file_data.begin(), file_data.end(), local_include_pattern), boost::regex_iterator<citer>(),
            follow_local_include
          );
    
          boost::regex global_include_pattern("\\#[[:space:]]*include[[:space:]]*<(.*?)>");
          std::for_each(
            boost::regex_iterator<citer>(file_data.begin(), file_data.end(), global_include_pattern), boost::regex_iterator<citer>(),
            follow_global_include
          );
    
          boost::regex define_pattern("\\#[[:space:]]*define[[:space:]]*([a-zA-Z0-9]*)(\\([^\\)]*\\))?");
          //boost::regex define_pattern("\\#[[:space:]]*define[[:space:]]*([a-zA-Z0-9]*)");
          std::for_each(
            boost::regex_iterator<citer>(file_data.begin(), file_data.end(), define_pattern), boost::regex_iterator<citer>(),
            boost::bind(check_macro_def, boost::cref(line_offsets), _1)
          );
    
          include_chain.pop_back();
        }
      }
    }
    
    void scan_if_matched_file(fs::path file){
      try{
        if(!file_ext.empty())
          if(std::find(file_ext.begin(), file_ext.end(), fs::extension(file)) == file_ext.end())
            return;
        if(fs::exists(file))
          if(!fs::is_directory(file))
            scan(file);
      }catch(fs::filesystem_error&error){
        std::clog<<"Error with "<<file.native_file_string()<<". Skipping."<<std::endl;
        std::clog<<error.what()<<std::endl;
      }
    }
    
    fs::path parse_native_path(std::string str){
      return fs::path(str, fs::native);
    }
    
    void process_input_file(fs::path file){
      if(!fs::is_directory(file))
    		scan(file);
    	else
    		std::for_each(fs::directory_iterator(file), fs::directory_iterator(), scan_if_matched_file);
    }
    
    void parse_input_file(std::string file_name){
    	fs::path file = lookup_local_include(parse_native_path(file_name));
    	if(file.empty())
    		std::clog<<"Could not find "<<file_name<<std::endl;
    	else
    		process_input_file(file);
    }
    
    std::string prepend_dot(std::string str){
      if(str.empty())
        return ".";
      else
        if(str[0] != '.')
          return "." + str;
        else
          return str;
    }
    
    int main(int argc, char*argv[]){
      try{
        // Commandline description
        po::options_description cmd_options("Available options");
    
        cmd_options.add_options()
          ("help,h", "Produces this help message")
          ("version,v", "Shows informations about the program version.")
          ("include-path,I", po::value< std::vector<std::string> >(), 
            "Adds a path that is searched for header files. They are searched in the way they come on the commandline.")
          ("input-file", po::value< std::vector<std::string> >(), 
            "Specifies either a directory or a file. If it's a file then the search starts in that file regardless of the extension. "
            "If it's a directory then all files directly in that directory having an extension as indicated by --ext are searched.")
          ("ext,E", po::value< std::vector<std::string> >(&file_ext), 
            "Extension of files in input directories to follow. If none is specified then all files regardless of their extensions are followed. "
            "include files are always followed regardless of their extension.")
          ("macro,m", po::value< std::vector<std::string> >(&macro), 
            "The macro name that is searched for. When multiple macros are specified then all are searched for.")
          ("suppress-header-error,H", po::bool_switch(&suppress_header_not_found_error), 
            "Suppresses the error message generated when a header file can not be opened.")
          ("show-include-chain,C", po::bool_switch(&show_header_include_chain), 
            "Shows from were the header was included.")
          ("show-scanned-file,S", po::bool_switch(&show_scanned_files), 
            "Prints a message before a file is being processed.")
          ("suppress-recursive-error,R", po::bool_switch(&suppress_recusive_include_error), 
            "Suppresses the error message generated when a header file includes itself.")
    			("visit-multi,A", po::bool_switch(&visit_all), 
            "Visits a file multiple times when included on different path. The macro definition will be the same but you might find different dependencies. "
    				"This switch is only useful when used with C because otherwise this information is stripped out anyway. "
    				"Warning: This switch can slow down the search drasticaly.")
        ;
    
        po::positional_options_description unnamed_options;
        unnamed_options.add("macro", 1);
        unnamed_options.add("input-file", -1);
    
        // Parsing the commandline
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(cmd_options).positional(unnamed_options).run(), vm);
        po::notify(vm);
    
        // Interpreting the results
        if(vm.count("help") > 0){
          std::cout << "Searches C/C++ files for macro definitions." << std::endl;
          std::cout << "mhunt [options] macro input-file [input-file[...]]" << std::endl;
          std::cout << cmd_options;
          return 0;
        }else if(vm.count("version") > 0){
          std::cout << "This file was build on "__DATE__<<" at "__TIME__ << std::endl;
          return 0;
        }else{
          if(vm["macro"].empty())
            throw std::runtime_error("No macro name was specified.");
    
          std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(), prepend_dot);
    
          if(vm.count("include-path") > 0){
            const std::vector<std::string>&library_path_str = vm["include-path"].as<std::vector<std::string> >();
            std::transform(
              library_path_str.begin(), library_path_str.end(), 
              std::back_inserter(library_paths), 
              &parse_native_path
            );
          }
    
          if(vm["input-file"].empty())
            process_input_file(fs::initial_path());
          else{
            const std::vector<std::string>&input_files = vm["input-file"].as<std::vector<std::string> >();
            std::for_each(input_files.begin(), input_files.end(), &parse_input_file);
          }
        }
    
      }catch(std::exception&error){
        std::cerr<<"Fatal error : "<<error.what()<<std::endl;
        std::cerr<<"Try :"<<std::endl;
        std::cerr<<"mhunt --help"<<std::endl;  
        return 2;
      }
    }
    

Log in to reply