clang-tools  3.9.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 (Module::submodule_const_iterator MI = Mod.submodule_begin(),
229  MIEnd = Mod.submodule_end();
230  MI != MIEnd; ++MI)
231  collectModuleHeaders(**MI);
232 
233  return true;
234 }
235 
236 // Collect headers from an umbrella directory.
237 bool CoverageChecker::collectUmbrellaHeaders(StringRef UmbrellaDirName) {
238  // Initialize directory name.
239  SmallString<256> Directory(ModuleMapDirectory);
240  if (UmbrellaDirName.size())
241  sys::path::append(Directory, UmbrellaDirName);
242  if (Directory.size() == 0)
243  Directory = ".";
244  // Walk the directory.
245  std::error_code EC;
246  sys::fs::file_status Status;
247  for (sys::fs::directory_iterator I(Directory.str(), EC), E; I != E;
248  I.increment(EC)) {
249  if (EC)
250  return false;
251  std::string File(I->path());
252  I->status(Status);
253  sys::fs::file_type Type = Status.type();
254  // If the file is a directory, ignore the name and recurse.
255  if (Type == sys::fs::file_type::directory_file) {
257  return false;
258  continue;
259  }
260  // If the file does not have a common header extension, ignore it.
262  continue;
263  // Save header name.
264  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(File));
265  }
266  return true;
267 }
268 
269 // Collect headers rferenced from an umbrella file.
270 bool
271 CoverageChecker::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) {
272 
273  SmallString<256> PathBuf(ModuleMapDirectory);
274 
275  // If directory is empty, it's the current directory.
276  if (ModuleMapDirectory.length() == 0)
277  sys::fs::current_path(PathBuf);
278 
279  // Create the compilation database.
280  std::unique_ptr<CompilationDatabase> Compilations;
281  Compilations.reset(new FixedCompilationDatabase(Twine(PathBuf), CommandLine));
282 
283  std::vector<std::string> HeaderPath;
284  HeaderPath.push_back(UmbrellaHeaderName);
285 
286  // Create the tool and run the compilation.
287  ClangTool Tool(*Compilations, HeaderPath);
288  int HadErrors = Tool.run(new CoverageCheckerFrontendActionFactory(*this));
289 
290  // If we had errors, exit early.
291  return !HadErrors;
292 }
293 
294 // Called from CoverageCheckerCallbacks to track a header included
295 // from an umbrella header.
297 
298  SmallString<256> PathBuf(ModuleMapDirectory);
299  // If directory is empty, it's the current directory.
300  if (ModuleMapDirectory.length() == 0)
301  sys::fs::current_path(PathBuf);
302  // HeaderName will have an absolute path, so if it's the module map
303  // directory, we remove it, also skipping trailing separator.
304  if (HeaderName.startswith(PathBuf))
305  HeaderName = HeaderName.substr(PathBuf.size() + 1);
306  // Save header name.
307  ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(HeaderName));
308 }
309 
310 // Collect file system header files.
311 // This function scans the file system for header files,
312 // starting at the directory of the module.modulemap file,
313 // optionally filtering out all but the files covered by
314 // the include path options.
315 // Returns true if no errors.
317 
318  // Get directory containing the module.modulemap file.
319  // Might be relative to current directory, absolute, or empty.
320  ModuleMapDirectory = ModularizeUtilities::getDirectoryFromPath(ModuleMapPath);
321 
322  // If no include paths specified, we do the whole tree starting
323  // at the module.modulemap directory.
324  if (IncludePaths.size() == 0) {
325  if (!collectFileSystemHeaders(StringRef("")))
326  return false;
327  }
328  else {
329  // Otherwise we only look at the sub-trees specified by the
330  // include paths.
331  for (std::vector<std::string>::const_iterator I = IncludePaths.begin(),
332  E = IncludePaths.end();
333  I != E; ++I) {
334  if (!collectFileSystemHeaders(*I))
335  return false;
336  }
337  }
338 
339  // Sort it, because different file systems might order the file differently.
340  std::sort(FileSystemHeaders.begin(), FileSystemHeaders.end());
341 
342  return true;
343 }
344 
345 // Collect file system header files from the given path.
346 // This function scans the file system for header files,
347 // starting at the given directory, which is assumed to be
348 // relative to the directory of the module.modulemap file.
349 // \returns True if no errors.
350 bool CoverageChecker::collectFileSystemHeaders(StringRef IncludePath) {
351 
352  // Initialize directory name.
353  SmallString<256> Directory(ModuleMapDirectory);
354  if (IncludePath.size())
355  sys::path::append(Directory, IncludePath);
356  if (Directory.size() == 0)
357  Directory = ".";
358  if (IncludePath.startswith("/") || IncludePath.startswith("\\") ||
359  ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) {
360  llvm::errs() << "error: Include path \"" << IncludePath
361  << "\" is not relative to the module map file.\n";
362  return false;
363  }
364 
365  // Recursively walk the directory tree.
366  std::error_code EC;
367  sys::fs::file_status Status;
368  int Count = 0;
369  for (sys::fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E;
370  I.increment(EC)) {
371  if (EC)
372  return false;
373  //std::string file(I->path());
374  StringRef file(I->path());
375  I->status(Status);
376  sys::fs::file_type type = Status.type();
377  // If the file is a directory, ignore the name (but still recurses).
378  if (type == sys::fs::file_type::directory_file)
379  continue;
380  // Assume directories or files starting with '.' are private and not to
381  // be considered.
382  if ((file.find("\\.") != StringRef::npos) ||
383  (file.find("/.") != StringRef::npos))
384  continue;
385  // If the file does not have a common header extension, ignore it.
387  continue;
388  // Save header name.
389  FileSystemHeaders.push_back(ModularizeUtilities::getCanonicalPath(file));
390  Count++;
391  }
392  if (Count == 0) {
393  llvm::errs() << "warning: No headers found in include path: \""
394  << IncludePath << "\"\n";
395  }
396  return true;
397 }
398 
399 // Find headers unaccounted-for in module map.
400 // This function compares the list of collected header files
401 // against those referenced in the module map. Display
402 // warnings for unaccounted-for header files.
403 // Save unaccounted-for file list for possible.
404 // fixing action.
405 // FIXME: There probably needs to be some canonalization
406 // of file names so that header path can be correctly
407 // matched. Also, a map could be used for the headers
408 // referenced in the module, but
410  // Walk over file system headers.
411  for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(),
412  E = FileSystemHeaders.end();
413  I != E; ++I) {
414  // Look for header in module map.
415  if (ModuleMapHeadersSet.insert(*I).second) {
416  UnaccountedForHeaders.push_back(*I);
417  llvm::errs() << "warning: " << ModuleMapPath
418  << " does not account for file: " << *I << "\n";
419  }
420  }
421 }
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.