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