blob: b3c4227b6238322d3f10c51b8b2a10338b0ce38b [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.
153CoverageChecker *CoverageChecker::createCoverageChecker(
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.
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
228 for (Module::submodule_const_iterator MI = Mod.submodule_begin(),
John Thompson96f55512015-06-04 23:35:19 +0000229 MIEnd = Mod.submodule_end();
230 MI != MIEnd; ++MI)
John Thompson8eb8d932015-02-19 16:47:27 +0000231 collectModuleHeaders(**MI);
232
233 return true;
234}
235
236// Collect headers from an umbrella directory.
237bool 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) {
256 if (!collectUmbrellaHeaders(File))
257 return false;
258 continue;
259 }
260 // If the file does not have a common header extension, ignore it.
261 if (!ModularizeUtilities::isHeader(File))
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.
270bool
271CoverageChecker::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.
David Blaikie4754aa82015-03-23 19:40:59 +0000291 return !HadErrors;
John Thompson8eb8d932015-02-19 16:47:27 +0000292}
293
294// Called from CoverageCheckerCallbacks to track a header included
295// from an umbrella header.
296void CoverageChecker::collectUmbrellaHeaderHeader(StringRef HeaderName) {
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.
316bool CoverageChecker::collectFileSystemHeaders() {
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.
350bool 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 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 // If the file does not have a common header extension, ignore it.
380 if (!ModularizeUtilities::isHeader(file))
381 continue;
382 // Save header name.
383 FileSystemHeaders.push_back(ModularizeUtilities::getCanonicalPath(file));
384 Count++;
385 }
386 if (Count == 0) {
387 llvm::errs() << "warning: No headers found in include path: \""
388 << IncludePath << "\"\n";
389 }
390 return true;
391}
392
393// Find headers unaccounted-for in module map.
394// This function compares the list of collected header files
395// against those referenced in the module map. Display
396// warnings for unaccounted-for header files.
397// Save unaccounted-for file list for possible.
398// fixing action.
399// FIXME: There probably needs to be some canonalization
400// of file names so that header path can be correctly
401// matched. Also, a map could be used for the headers
402// referenced in the module, but
403void CoverageChecker::findUnaccountedForHeaders() {
404 // Walk over file system headers.
405 for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(),
406 E = FileSystemHeaders.end();
407 I != E; ++I) {
408 // Look for header in module map.
409 if (ModuleMapHeadersSet.insert(*I).second) {
410 UnaccountedForHeaders.push_back(*I);
411 llvm::errs() << "warning: " << ModuleMapPath
412 << " does not account for file: " << *I << "\n";
413 }
414 }
415}