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