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