• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IncludeFinderPPCallbacks(clang::SourceManager * source_manager,string * main_source_file,set<string> * source_file_paths,const HeaderSearchOptions * header_search_options)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 
FileChanged(clang::SourceLocation,clang::PPCallbacks::FileChangeReason reason,clang::SrcMgr::CharacteristicKind,clang::FileID)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 
AddFile(const string & path)134 void IncludeFinderPPCallbacks::AddFile(const string& path) {
135   source_file_paths_->insert(path);
136 }
137 
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)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 
DoubleSlashSystemHeaders(const string & search_path,const string & relative_path) const187 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 
EndOfMainFile()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:
CompilationIndexerAction()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 
ExecuteAction()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 
Preprocess()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 
EndSourceFileAction()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 
main(int argc,const char * argv[])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   // This clang tool does not actually produce edits, but run_tool.py expects
269   // this. So we just print an empty edit block.
270   llvm::outs() << "==== BEGIN EDITS ====\n";
271   llvm::outs() << "==== END EDITS ====\n";
272   return tool.run(frontend_factory.get());
273 }
274