clang-tools  4.0.0
CoverageChecker.cpp
Go to the documentation of this file.
1 //===--- extra/module-map-checker/CoverageChecker.cpp -------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file implements a class that validates a module map by checking that
11 // all headers in the corresponding directories are accounted for.
12 //
13 // This class uses a previously loaded module map object.
14 // Starting at the module map file directory, or just the include
15 // paths, if specified, it will collect the names of all the files it
16 // considers headers (no extension, .h, or .inc--if you need more, modify the
17 // ModularizeUtilities::isHeader function).
18 // It then compares the headers against those referenced
19 // in the module map, either explicitly named, or implicitly named via an
20 // umbrella directory or umbrella file, as parsed by the ModuleMap object.
21 // If headers are found which are not referenced or covered by an umbrella
22 // directory or file, warning messages will be produced, and the doChecks
23 // function will return an error code of 1. Other errors result in an error
24 // code of 2. If no problems are found, an error code of 0 is returned.
25 //
26 // Note that in the case of umbrella headers, this tool invokes the compiler
27 // to preprocess the file, and uses a callback to collect the header files
28 // included by the umbrella header or any of its nested includes. If any
29 // front end options are needed for these compiler invocations, these are
30 // to be passed in via the CommandLine parameter.
31 //
32 // Warning message have the form:
33 //
34 // warning: module.modulemap does not account for file: Level3A.h
35 //
36 // Note that for the case of the module map referencing a file that does
37 // not exist, the module map parser in Clang will (at the time of this
38 // writing) display an error message.
39 //
40 // Potential problems with this program:
41 //
42 // 1. Might need a better header matching mechanism, or extensions to the
43 // canonical file format used.
44 //
45 // 2. It might need to support additional header file extensions.
46 //
47 // Future directions:
48 //
49 // 1. Add an option to fix the problems found, writing a new module map.
50 // Include an extra option to add unaccounted-for headers as excluded.
51 //
52 //===----------------------------------------------------------------------===//
53 
54 #include "ModularizeUtilities.h"
55 #include "clang/AST/ASTConsumer.h"
56 #include "CoverageChecker.h"
57 #include "clang/AST/ASTContext.h"
58 #include "clang/AST/RecursiveASTVisitor.h"
59 #include "clang/Basic/SourceManager.h"
60 #include "clang/Driver/Options.h"
61 #include "clang/Frontend/CompilerInstance.h"
62 #include "clang/Frontend/FrontendActions.h"
63 #include "clang/Lex/PPCallbacks.h"
64 #include "clang/Lex/Preprocessor.h"
65 #include "clang/Tooling/CompilationDatabase.h"
66 #include "clang/Tooling/Tooling.h"
67 #include "llvm/Option/Option.h"
68 #include "llvm/Support/CommandLine.h"
69 #include "llvm/Support/FileSystem.h"
70 #include "llvm/Support/Path.h"
71 #include "llvm/Support/raw_ostream.h"
72 
73 using namespace Modularize;
74 using namespace clang;
75 using namespace clang::driver;
76 using namespace clang::driver::options;
77 using namespace clang::tooling;
78 namespace cl = llvm::cl;
79 namespace sys = llvm::sys;
80 
81 // Preprocessor callbacks.
82 // We basically just collect include files.
83 class CoverageCheckerCallbacks : public PPCallbacks {
84 public:
85  CoverageCheckerCallbacks(CoverageChecker &Checker) : Checker(Checker) {}
87 
88  // Include directive callback.
89  void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
90  StringRef FileName, bool IsAngled,
91  CharSourceRange FilenameRange, const FileEntry *File,
92  StringRef SearchPath, StringRef RelativePath,
93  const Module *Imported) override {
94  Checker.collectUmbrellaHeaderHeader(File->getName());
95  }
96 
97 private:
98  CoverageChecker &Checker;
99 };
100 
101 // Frontend action stuff:
102 
103 // Consumer is responsible for setting up the callbacks.
105 public:
106  CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) {
107  // PP takes ownership.
108  PP.addPPCallbacks(llvm::make_unique<CoverageCheckerCallbacks>(Checker));
109  }
110 };
111 
113 public:
114  CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {}
115 
116 protected:
117  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
118  StringRef InFile) override {
119  return llvm::make_unique<CoverageCheckerConsumer>(Checker,
120  CI.getPreprocessor());
121  }
122 
123 private:
124  CoverageChecker &Checker;
125 };
126 
128 public:
130  : Checker(Checker) {}
131 
133  return new CoverageCheckerAction(Checker);
134  }
135 
136 private:
137  CoverageChecker &Checker;
138 };
139 
140 // CoverageChecker class implementation.
141 
142 // Constructor.
144  std::vector<std::string> &IncludePaths,
145  ArrayRef<std::string> CommandLine,
146  clang::ModuleMap *ModuleMap)
147  : ModuleMapPath(ModuleMapPath), IncludePaths(IncludePaths),
148  CommandLine(CommandLine),
149  ModMap(ModuleMap) {}
150 
151 // Create instance of CoverageChecker, to simplify setting up
152 // subordinate objects.
154  StringRef ModuleMapPath, std::vector<std::string> &IncludePaths,
155  ArrayRef<std::string> CommandLine, clang::ModuleMap *ModuleMap) {
156 
157  return new CoverageChecker(ModuleMapPath, IncludePaths, CommandLine,
158  ModuleMap);
159 }
160 
161 // Do checks.
162 // Starting from the directory of the module.modulemap file,
163 // Find all header files, optionally looking only at files
164 // covered by the include path options, and compare against
165 // the headers referenced by the module.modulemap file.
166 // Display warnings for unaccounted-for header files.
167 // Returns error_code of 0 if there were no errors or warnings, 1 if there
168 // were warnings, 2 if any other problem, such as if a bad
169 // module map path argument was specified.
170 std::error_code CoverageChecker::doChecks() {
171  std::error_code returnValue;
172 
173  // Collect the headers referenced in the modules.
175 
176  // Collect the file system headers.
178  return std::error_code(2, std::generic_category());
179 
180  // Do the checks. These save the problematic file names.
182 
183  // Check for warnings.
184  if (!UnaccountedForHeaders.empty())
185  returnValue = std::error_code(1, std::generic_category());
186 
187  return returnValue;
188 }
189 
190 // The following functions are called by doChecks.
191 
192 // Collect module headers.
193 // Walks the modules and collects referenced headers into
194 // ModuleMapHeadersSet.
196  for (ModuleMap::module_iterator I = ModMap->module_begin(),
197  E = ModMap->module_end();
198  I != E; ++I) {
199  collectModuleHeaders(*I->second);
200  }
201 }
202 
203 // Collect referenced headers from one module.
204 // Collects the headers referenced in the given module into
205 // ModuleMapHeadersSet.
206 // FIXME: Doesn't collect files from umbrella header.
207 bool CoverageChecker::collectModuleHeaders(const Module &Mod) {
208 
209  if (const FileEntry *UmbrellaHeader = Mod.getUmbrellaHeader().Entry) {
210  // Collect umbrella header.
211  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(
212  UmbrellaHeader->getName()));
213  // Preprocess umbrella header and collect the headers it references.
214  if (!collectUmbrellaHeaderHeaders(UmbrellaHeader->getName()))
215  return false;
216  }
217  else if (const DirectoryEntry *UmbrellaDir = Mod.getUmbrellaDir().Entry) {
218  // Collect headers in umbrella directory.
219  if (!collectUmbrellaHeaders(UmbrellaDir->getName()))
220  return false;
221  }
222 
223  for (auto &HeaderKind : Mod.Headers)
224  for (auto &Header : HeaderKind)
225  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(
226  Header.Entry->getName()));
227 
228  for (auto MI = Mod.submodule_begin(), MIEnd = Mod.submodule_end();
229  MI != MIEnd; ++MI)
230  collectModuleHeaders(**MI);
231 
232  return true;
233 }
234 
235 // Collect headers from an umbrella directory.
236 bool CoverageChecker::collectUmbrellaHeaders(StringRef UmbrellaDirName) {
237  // Initialize directory name.
238  SmallString<256> Directory(ModuleMapDirectory);
239  if (UmbrellaDirName.size())
240  sys::path::append(Directory, UmbrellaDirName);
241  if (Directory.size() == 0)
242  Directory = ".";
243  // Walk the directory.
244  std::error_code EC;
245  sys::fs::file_status Status;
246  for (sys::fs::directory_iterator I(Directory.str(), EC), E; I != E;
247  I.increment(EC)) {
248  if (EC)
249  return false;
250  std::string File(I->path());
251  I->status(Status);
252  sys::fs::file_type Type = Status.type();
253  // If the file is a directory, ignore the name and recurse.
254  if (Type == sys::fs::file_type::directory_file) {
256  return false;
257  continue;
258  }
259  // If the file does not have a common header extension, ignore it.
261  continue;
262  // Save header name.
263  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(File));
264  }
265  return true;
266 }
267 
268 // Collect headers rferenced from an umbrella file.
269 bool
270 CoverageChecker::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) {
271 
272  SmallString<256> PathBuf(ModuleMapDirectory);
273 
274  // If directory is empty, it's the current directory.
275  if (ModuleMapDirectory.length() == 0)
276  sys::fs::current_path(PathBuf);
277 
278  // Create the compilation database.
279  std::unique_ptr<CompilationDatabase> Compilations;
280  Compilations.reset(new FixedCompilationDatabase(Twine(PathBuf), CommandLine));
281 
282  std::vector<std::string> HeaderPath;
283  HeaderPath.push_back(UmbrellaHeaderName);
284 
285  // Create the tool and run the compilation.
286  ClangTool Tool(*Compilations, HeaderPath);
287  int HadErrors = Tool.run(new CoverageCheckerFrontendActionFactory(*this));
288 
289  // If we had errors, exit early.
290  return !HadErrors;
291 }
292 
293 // Called from CoverageCheckerCallbacks to track a header included
294 // from an umbrella header.
296 
297  SmallString<256> PathBuf(ModuleMapDirectory);
298  // If directory is empty, it's the current directory.
299  if (ModuleMapDirectory.length() == 0)
300  sys::fs::current_path(PathBuf);
301  // HeaderName will have an absolute path, so if it's the module map
302  // directory, we remove it, also skipping trailing separator.
303  if (HeaderName.startswith(PathBuf))
304  HeaderName = HeaderName.substr(PathBuf.size() + 1);
305  // Save header name.
306  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(HeaderName));
307 }
308 
309 // Collect file system header files.
310 // This function scans the file system for header files,
311 // starting at the directory of the module.modulemap file,
312 // optionally filtering out all but the files covered by
313 // the include path options.
314 // Returns true if no errors.
316 
317  // Get directory containing the module.modulemap file.
318  // Might be relative to current directory, absolute, or empty.
319  ModuleMapDirectory = ModularizeUtilities::getDirectoryFromPath(ModuleMapPath);
320 
321  // If no include paths specified, we do the whole tree starting
322  // at the module.modulemap directory.
323  if (IncludePaths.size() == 0) {
324  if (!collectFileSystemHeaders(StringRef("")))
325  return false;
326  }
327  else {
328  // Otherwise we only look at the sub-trees specified by the
329  // include paths.
330  for (std::vector<std::string>::const_iterator I = IncludePaths.begin(),
331  E = IncludePaths.end();
332  I != E; ++I) {
333  if (!collectFileSystemHeaders(*I))
334  return false;
335  }
336  }
337 
338  // Sort it, because different file systems might order the file differently.
339  std::sort(FileSystemHeaders.begin(), FileSystemHeaders.end());
340 
341  return true;
342 }
343 
344 // Collect file system header files from the given path.
345 // This function scans the file system for header files,
346 // starting at the given directory, which is assumed to be
347 // relative to the directory of the module.modulemap file.
348 // \returns True if no errors.
349 bool CoverageChecker::collectFileSystemHeaders(StringRef IncludePath) {
350 
351  // Initialize directory name.
352  SmallString<256> Directory(ModuleMapDirectory);
353  if (IncludePath.size())
354  sys::path::append(Directory, IncludePath);
355  if (Directory.size() == 0)
356  Directory = ".";
357  if (IncludePath.startswith("/") || IncludePath.startswith("\\") ||
358  ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) {
359  llvm::errs() << "error: Include path \"" << IncludePath
360  << "\" is not relative to the module map file.\n";
361  return false;
362  }
363 
364  // Recursively walk the directory tree.
365  std::error_code EC;
366  sys::fs::file_status Status;
367  int Count = 0;
368  for (sys::fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E;
369  I.increment(EC)) {
370  if (EC)
371  return false;
372  //std::string file(I->path());
373  StringRef file(I->path());
374  I->status(Status);
375  sys::fs::file_type type = Status.type();
376  // If the file is a directory, ignore the name (but still recurses).
377  if (type == sys::fs::file_type::directory_file)
378  continue;
379  // Assume directories or files starting with '.' are private and not to
380  // be considered.
381  if ((file.find("\\.") != StringRef::npos) ||
382  (file.find("/.") != StringRef::npos))
383  continue;
384  // If the file does not have a common header extension, ignore it.
386  continue;
387  // Save header name.
388  FileSystemHeaders.push_back(ModularizeUtilities::getCanonicalPath(file));
389  Count++;
390  }
391  if (Count == 0) {
392  llvm::errs() << "warning: No headers found in include path: \""
393  << IncludePath << "\"\n";
394  }
395  return true;
396 }
397 
398 // Find headers unaccounted-for in module map.
399 // This function compares the list of collected header files
400 // against those referenced in the module map. Display
401 // warnings for unaccounted-for header files.
402 // Save unaccounted-for file list for possible.
403 // fixing action.
404 // FIXME: There probably needs to be some canonalization
405 // of file names so that header path can be correctly
406 // matched. Also, a map could be used for the headers
407 // referenced in the module, but
409  // Walk over file system headers.
410  for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(),
411  E = FileSystemHeaders.end();
412  I != E; ++I) {
413  // Look for header in module map.
414  if (ModuleMapHeadersSet.insert(*I).second) {
415  UnaccountedForHeaders.push_back(*I);
416  llvm::errs() << "warning: " << ModuleMapPath
417  << " does not account for file: " << *I << "\n";
418  }
419  }
420 }
bool collectUmbrellaHeaders(llvm::StringRef UmbrellaDirName)
Collect headers from an umbrella directory.
static std::string getCanonicalPath(llvm::StringRef FilePath)
Convert header path to canonical form.
CoverageChecker(llvm::StringRef ModuleMapPath, std::vector< std::string > &IncludePaths, llvm::ArrayRef< std::string > CommandLine, clang::ModuleMap *ModuleMap)
Constructor.
CoverageCheckerCallbacks(CoverageChecker &Checker)
HeaderHandle File
std::unique_ptr< ASTConsumer > CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override
static std::string getDirectoryFromPath(llvm::StringRef Path)
Get directory path component from file path.
void collectModuleHeaders()
Collect module headers.
CoverageCheckerAction(CoverageChecker &Checker)
void collectUmbrellaHeaderHeader(llvm::StringRef HeaderName)
Called from CoverageCheckerCallbacks to track a header included from an umbrella header.
CoverageCheckerFrontendActionFactory(CoverageChecker &Checker)
ModularizeUtilities class definition.
void findUnaccountedForHeaders()
Find headers unaccounted-for in module map.
bool collectUmbrellaHeaderHeaders(llvm::StringRef UmbrellaHeaderName)
Collect headers rferenced from an umbrella file.
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
Definitions for CoverageChecker.
static cl::opt< std::string > ModuleMapPath("module-map-path", cl::init(""), cl::desc("Turn on module map output and specify output path or file name."" If no path is specified and if prefix option is specified,"" use prefix for file path."))
bool IsAngled
true if this was an include with angle brackets
CoverageCheckerAction * create() override
std::string CommandLine
Definition: Modularize.cpp:336
Preprocessor * PP
std::error_code doChecks()
Do checks.
static cl::list< std::string > IncludePaths("I", cl::desc("Include path for coverage check."), cl::ZeroOrMore, cl::value_desc("path"))
bool collectFileSystemHeaders()
Collect file system header files.
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported) override
CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP)
static CoverageChecker * createCoverageChecker(llvm::StringRef ModuleMapPath, std::vector< std::string > &IncludePaths, llvm::ArrayRef< std::string > CommandLine, clang::ModuleMap *ModuleMap)
Create instance of CoverageChecker.
Module map checker class.
static bool isHeader(llvm::StringRef FileName)
Check for header file extension.