Home | History | Annotate | Download | only in translation_unit
      1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 //
      5 // This implements a Clang tool to generate compilation information that is
      6 // sufficient to recompile the code with clang. For each compilation unit, all
      7 // source files which are necessary for compiling it are determined. For each
      8 // compilation unit, a file is created containing a list of all file paths of
      9 // included files.
     10 
     11 #include <assert.h>
     12 #include <unistd.h>
     13 #include <fstream>
     14 #include <iostream>
     15 #include <memory>
     16 #include <set>
     17 #include <stack>
     18 #include <string>
     19 #include <vector>
     20 
     21 #include "clang/Basic/Diagnostic.h"
     22 #include "clang/Basic/FileManager.h"
     23 #include "clang/Basic/SourceManager.h"
     24 #include "clang/Frontend/CompilerInstance.h"
     25 #include "clang/Frontend/FrontendActions.h"
     26 #include "clang/Lex/HeaderSearchOptions.h"
     27 #include "clang/Lex/PPCallbacks.h"
     28 #include "clang/Lex/Preprocessor.h"
     29 #include "clang/Tooling/CommonOptionsParser.h"
     30 #include "clang/Tooling/CompilationDatabase.h"
     31 #include "clang/Tooling/Refactoring.h"
     32 #include "clang/Tooling/Tooling.h"
     33 #include "llvm/Support/CommandLine.h"
     34 #include "llvm/Support/Path.h"
     35 
     36 using clang::HeaderSearchOptions;
     37 using clang::tooling::CommonOptionsParser;
     38 using std::set;
     39 using std::stack;
     40 using std::string;
     41 using std::vector;
     42 
     43 namespace {
     44 // Set of preprocessor callbacks used to record files included.
     45 class IncludeFinderPPCallbacks : public clang::PPCallbacks {
     46  public:
     47   IncludeFinderPPCallbacks(clang::SourceManager* source_manager,
     48                            string* main_source_file,
     49                            set<string>* source_file_paths,
     50                            const HeaderSearchOptions* header_search_options);
     51   void FileChanged(clang::SourceLocation /*loc*/,
     52                    clang::PPCallbacks::FileChangeReason reason,
     53                    clang::SrcMgr::CharacteristicKind /*file_type*/,
     54                    clang::FileID /*prev_fid*/) override;
     55   void AddFile(const string& path);
     56   void InclusionDirective(clang::SourceLocation hash_loc,
     57                           const clang::Token& include_tok,
     58                           llvm::StringRef file_name,
     59                           bool is_angled,
     60                           clang::CharSourceRange range,
     61                           const clang::FileEntry* file,
     62                           llvm::StringRef search_path,
     63                           llvm::StringRef relative_path,
     64                           const clang::Module* imported) override;
     65   void EndOfMainFile() override;
     66 
     67  private:
     68   string DoubleSlashSystemHeaders(const string& search_path,
     69                                   const string& relative_path) const;
     70 
     71   clang::SourceManager* const source_manager_;
     72   string* const main_source_file_;
     73   set<string>* const source_file_paths_;
     74   set<string> system_header_prefixes_;
     75   // The path of the file that was last referenced by an inclusion directive,
     76   // normalized for includes that are relative to a different source file.
     77   string last_inclusion_directive_;
     78   // The stack of currently parsed files. top() gives the current file.
     79   stack<string> current_files_;
     80 };
     81 
     82 IncludeFinderPPCallbacks::IncludeFinderPPCallbacks(
     83     clang::SourceManager* source_manager,
     84     string* main_source_file,
     85     set<string>* source_file_paths,
     86     const HeaderSearchOptions* header_search_options)
     87       : source_manager_(source_manager),
     88         main_source_file_(main_source_file),
     89         source_file_paths_(source_file_paths) {
     90   // In practice this list seems to be empty, but add it anyway just in case.
     91   for (const auto& prefix : header_search_options->SystemHeaderPrefixes) {
     92     system_header_prefixes_.insert(prefix.Prefix);
     93   }
     94 
     95   // This list contains all the include directories of different type.  We add
     96   // all system headers to the set - excluding the Quoted and Angled groups
     97   // which are from -iquote and -I flags.
     98   for (const auto& entry : header_search_options->UserEntries) {
     99     switch (entry.Group) {
    100       case clang::frontend::System:
    101       case clang::frontend::ExternCSystem:
    102       case clang::frontend::CSystem:
    103       case clang::frontend::CXXSystem:
    104       case clang::frontend::ObjCSystem:
    105       case clang::frontend::ObjCXXSystem:
    106       case clang::frontend::After:
    107         system_header_prefixes_.insert(entry.Path);
    108         break;
    109       default:
    110         break;
    111     }
    112   }
    113 }
    114 
    115 void IncludeFinderPPCallbacks::FileChanged(
    116     clang::SourceLocation /*loc*/,
    117     clang::PPCallbacks::FileChangeReason reason,
    118     clang::SrcMgr::CharacteristicKind /*file_type*/,
    119     clang::FileID /*prev_fid*/) {
    120   if (reason == clang::PPCallbacks::EnterFile) {
    121     if (!last_inclusion_directive_.empty()) {
    122       current_files_.push(last_inclusion_directive_);
    123     } else {
    124       current_files_.push(
    125           source_manager_->getFileEntryForID(source_manager_->getMainFileID())
    126               ->getName());
    127     }
    128   } else if (reason == ExitFile) {
    129     current_files_.pop();
    130   }
    131   // Other reasons are not interesting for us.
    132 }
    133 
    134 void IncludeFinderPPCallbacks::AddFile(const string& path) {
    135   source_file_paths_->insert(path);
    136 }
    137 
    138 void IncludeFinderPPCallbacks::InclusionDirective(
    139     clang::SourceLocation hash_loc,
    140     const clang::Token& include_tok,
    141     llvm::StringRef file_name,
    142     bool is_angled,
    143     clang::CharSourceRange range,
    144     const clang::FileEntry* file,
    145     llvm::StringRef search_path,
    146     llvm::StringRef relative_path,
    147     const clang::Module* imported) {
    148   if (!file)
    149     return;
    150 
    151   assert(!current_files_.top().empty());
    152   const clang::DirectoryEntry* const search_path_entry =
    153       source_manager_->getFileManager().getDirectory(search_path);
    154   const clang::DirectoryEntry* const current_file_parent_entry =
    155       source_manager_->getFileManager()
    156           .getFile(current_files_.top().c_str())
    157           ->getDir();
    158 
    159   // If the include file was found relatively to the current file's parent
    160   // directory or a search path, we need to normalize it. This is necessary
    161   // because llvm internalizes the path by which an inode was first accessed,
    162   // and always returns that path afterwards. If we do not normalize this
    163   // we will get an error when we replay the compilation, as the virtual
    164   // file system is not aware of inodes.
    165   if (search_path_entry == current_file_parent_entry) {
    166     string parent =
    167         llvm::sys::path::parent_path(current_files_.top().c_str()).str();
    168 
    169     // If the file is a top level file ("file.cc"), we normalize to a path
    170     // relative to "./".
    171     if (parent.empty() || parent == "/")
    172       parent = ".";
    173 
    174     // Otherwise we take the literal path as we stored it for the current
    175     // file, and append the relative path.
    176     last_inclusion_directive_ =
    177         DoubleSlashSystemHeaders(parent, relative_path.str());
    178   } else if (!search_path.empty()) {
    179     last_inclusion_directive_ =
    180         DoubleSlashSystemHeaders(search_path.str(), relative_path.str());
    181   } else {
    182     last_inclusion_directive_ = file_name.str();
    183   }
    184   AddFile(last_inclusion_directive_);
    185 }
    186 
    187 string IncludeFinderPPCallbacks::DoubleSlashSystemHeaders(
    188     const string& search_path,
    189     const string& relative_path) const {
    190   // We want to be able to extract the search path relative to which the
    191   // include statement is defined. Therefore if search_path is a system header
    192   // we use "//" as a separator between the search path and the relative path.
    193   const bool is_system_header =
    194       system_header_prefixes_.find(search_path) !=
    195       system_header_prefixes_.end();
    196 
    197   return search_path + (is_system_header ? "//" : "/") + relative_path;
    198 }
    199 
    200 void IncludeFinderPPCallbacks::EndOfMainFile() {
    201   const clang::FileEntry* main_file =
    202       source_manager_->getFileEntryForID(source_manager_->getMainFileID());
    203   assert(*main_source_file_ == main_file->getName());
    204   AddFile(main_file->getName());
    205 }
    206 
    207 class CompilationIndexerAction : public clang::PreprocessorFrontendAction {
    208  public:
    209   CompilationIndexerAction() {}
    210   void ExecuteAction() override;
    211 
    212   // Runs the preprocessor over the translation unit.
    213   // This triggers the PPCallbacks we register to intercept all required
    214   // files for the compilation.
    215   void Preprocess();
    216   void EndSourceFileAction() override;
    217 
    218  private:
    219   // Set up the state extracted during the compilation, and run Clang over the
    220   // input.
    221   string main_source_file_;
    222   // Maps file names to their contents as read by Clang's source manager.
    223   set<string> source_file_paths_;
    224 };
    225 
    226 void CompilationIndexerAction::ExecuteAction() {
    227   vector<clang::FrontendInputFile> inputs =
    228       getCompilerInstance().getFrontendOpts().Inputs;
    229   assert(inputs.size() == 1);
    230   main_source_file_ = inputs[0].getFile();
    231 
    232   Preprocess();
    233 }
    234 
    235 void CompilationIndexerAction::Preprocess() {
    236   clang::Preprocessor& preprocessor = getCompilerInstance().getPreprocessor();
    237   preprocessor.addPPCallbacks(llvm::make_unique<IncludeFinderPPCallbacks>(
    238       &getCompilerInstance().getSourceManager(),
    239       &main_source_file_,
    240       &source_file_paths_,
    241       &getCompilerInstance().getHeaderSearchOpts()));
    242   preprocessor.getDiagnostics().setIgnoreAllWarnings(true);
    243   preprocessor.SetSuppressIncludeNotFoundError(true);
    244   preprocessor.EnterMainSourceFile();
    245   clang::Token token;
    246   do {
    247     preprocessor.Lex(token);
    248   } while (token.isNot(clang::tok::eof));
    249 }
    250 
    251 void CompilationIndexerAction::EndSourceFileAction() {
    252   std::ofstream out(main_source_file_ + ".filepaths");
    253   for (const string& path : source_file_paths_) {
    254     out << path << std::endl;
    255   }
    256 }
    257 }  // namespace
    258 
    259 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
    260 
    261 int main(int argc, const char* argv[]) {
    262   llvm::cl::OptionCategory category("TranslationUnitGenerator Tool");
    263   CommonOptionsParser options(argc, argv, category);
    264   std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
    265       clang::tooling::newFrontendActionFactory<CompilationIndexerAction>();
    266   clang::tooling::ClangTool tool(options.getCompilations(),
    267                                  options.getSourcePathList());
    268   return tool.run(frontend_factory.get());
    269 }
    270