blob: 510d062d07960db5f9c70b2a64c098751049e121 [file] [log] [blame]
John Thompson8eb8d932015-02-19 16:47:27 +00001//===--- 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
73using namespace Modularize;
74using namespace clang;
75using namespace clang::driver;
76using namespace clang::driver::options;
77using namespace clang::tooling;
78namespace cl = llvm::cl;
79namespace sys = llvm::sys;
80
81// Preprocessor callbacks.
82// We basically just collect include files.
83class CoverageCheckerCallbacks : public PPCallbacks {
84public:
85 CoverageCheckerCallbacks(CoverageChecker &Checker) : Checker(Checker) {}
David Blaikiee04a3da2015-10-20 21:45:52 +000086 ~CoverageCheckerCallbacks() override {}
John Thompson8eb8d932015-02-19 16:47:27 +000087
88 // Include directive callback.
89 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
Alexander Kornienko87638f62015-04-11 07:59:33 +000090 StringRef FileName, bool IsAngled,
91 CharSourceRange FilenameRange, const FileEntry *File,
92 StringRef SearchPath, StringRef RelativePath,
93 const Module *Imported) override {
John Thompson8eb8d932015-02-19 16:47:27 +000094 Checker.collectUmbrellaHeaderHeader(File->getName());
95 }
96
97private:
98 CoverageChecker &Checker;
99};
100
101// Frontend action stuff:
102
103// Consumer is responsible for setting up the callbacks.
104class CoverageCheckerConsumer : public ASTConsumer {
105public:
106 CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) {
107 // PP takes ownership.
108 PP.addPPCallbacks(llvm::make_unique<CoverageCheckerCallbacks>(Checker));
109 }
110};
111
112class CoverageCheckerAction : public SyntaxOnlyAction {
113public:
114 CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {}
115
116protected:
117 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
118 StringRef InFile) override {
119 return llvm::make_unique<CoverageCheckerConsumer>(Checker,
120 CI.getPreprocessor());
121 }
122
123private:
124 CoverageChecker &Checker;
125};
126
127class CoverageCheckerFrontendActionFactory : public FrontendActionFactory {
128public:
129 CoverageCheckerFrontendActionFactory(CoverageChecker &Checker)
130 : Checker(Checker) {}
131
Alexander Kornienko87638f62015-04-11 07:59:33 +0000132 CoverageCheckerAction *create() override {
John Thompson8eb8d932015-02-19 16:47:27 +0000133 return new CoverageCheckerAction(Checker);
134 }
135
136private:
137 CoverageChecker &Checker;
138};
139
140// CoverageChecker class implementation.
141
142// Constructor.
143CoverageChecker::CoverageChecker(StringRef ModuleMapPath,
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.
David Blaikiea67cf002017-02-11 05:25:21 +0000153std::unique_ptr<CoverageChecker> CoverageChecker::createCoverageChecker(
154 StringRef ModuleMapPath, std::vector<std::string> &IncludePaths,
155 ArrayRef<std::string> CommandLine, clang::ModuleMap *ModuleMap) {
John Thompson8eb8d932015-02-19 16:47:27 +0000156
David Blaikiea67cf002017-02-11 05:25:21 +0000157 return llvm::make_unique<CoverageChecker>(ModuleMapPath, IncludePaths,
158 CommandLine, ModuleMap);
John Thompson8eb8d932015-02-19 16:47:27 +0000159}
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.
170std::error_code CoverageChecker::doChecks() {
171 std::error_code returnValue;
172
173 // Collect the headers referenced in the modules.
174 collectModuleHeaders();
175
176 // Collect the file system headers.
177 if (!collectFileSystemHeaders())
178 return std::error_code(2, std::generic_category());
179
180 // Do the checks. These save the problematic file names.
181 findUnaccountedForHeaders();
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.
195void CoverageChecker::collectModuleHeaders() {
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.
207bool CoverageChecker::collectModuleHeaders(const Module &Mod) {
208
Richard Smith9d5ae212015-05-16 03:10:31 +0000209 if (const FileEntry *UmbrellaHeader = Mod.getUmbrellaHeader().Entry) {
John Thompson8eb8d932015-02-19 16:47:27 +0000210 // 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 }
Richard Smith9d5ae212015-05-16 03:10:31 +0000217 else if (const DirectoryEntry *UmbrellaDir = Mod.getUmbrellaDir().Entry) {
John Thompson8eb8d932015-02-19 16:47:27 +0000218 // Collect headers in umbrella directory.
219 if (!collectUmbrellaHeaders(UmbrellaDir->getName()))
220 return false;
221 }
222
223 for (auto &HeaderKind : Mod.Headers)
John Thompson96f55512015-06-04 23:35:19 +0000224 for (auto &Header : HeaderKind)
225 ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(
226 Header.Entry->getName()));
John Thompson8eb8d932015-02-19 16:47:27 +0000227
Piotr Padlewski08124b12016-12-14 15:29:23 +0000228 for (auto MI = Mod.submodule_begin(), MIEnd = Mod.submodule_end();
229 MI != MIEnd; ++MI)
John Thompson8eb8d932015-02-19 16:47:27 +0000230 collectModuleHeaders(**MI);
231
232 return true;
233}
234
235// Collect headers from an umbrella directory.
236bool 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) {
255 if (!collectUmbrellaHeaders(File))
256 return false;
257 continue;
258 }
259 // If the file does not have a common header extension, ignore it.
260 if (!ModularizeUtilities::isHeader(File))
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.
269bool
270CoverageChecker::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.
David Blaikie4754aa82015-03-23 19:40:59 +0000290 return !HadErrors;
John Thompson8eb8d932015-02-19 16:47:27 +0000291}
292
293// Called from CoverageCheckerCallbacks to track a header included
294// from an umbrella header.
295void CoverageChecker::collectUmbrellaHeaderHeader(StringRef HeaderName) {
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.
315bool CoverageChecker::collectFileSystemHeaders() {
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.
349bool 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;
John Thompsonb3eef012015-12-04 22:42:18 +0000372 //std::string file(I->path());
373 StringRef file(I->path());
John Thompson8eb8d932015-02-19 16:47:27 +0000374 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;
John Thompsonb3eef012015-12-04 22:42:18 +0000379 // Assume directories or files starting with '.' are private and not to
380 // be considered.
John Thompson442f1b22015-12-10 01:33:09 +0000381 if ((file.find("\\.") != StringRef::npos) ||
382 (file.find("/.") != StringRef::npos))
John Thompsonb3eef012015-12-04 22:42:18 +0000383 continue;
John Thompson8eb8d932015-02-19 16:47:27 +0000384 // If the file does not have a common header extension, ignore it.
385 if (!ModularizeUtilities::isHeader(file))
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
408void CoverageChecker::findUnaccountedForHeaders() {
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}