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 *SuggestedModule, |
94 | bool ModuleImported, |
95 | SrcMgr::CharacteristicKind FileType) override { |
96 | Checker.collectUmbrellaHeaderHeader(HeaderName: File->getName()); |
97 | } |
98 | |
99 | private: |
100 | CoverageChecker &Checker; |
101 | }; |
102 | |
103 | // Frontend action stuff: |
104 | |
105 | // Consumer is responsible for setting up the callbacks. |
106 | class CoverageCheckerConsumer : public ASTConsumer { |
107 | public: |
108 | CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) { |
109 | // PP takes ownership. |
110 | PP.addPPCallbacks(C: std::make_unique<CoverageCheckerCallbacks>(args&: Checker)); |
111 | } |
112 | }; |
113 | |
114 | class CoverageCheckerAction : public SyntaxOnlyAction { |
115 | public: |
116 | CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {} |
117 | |
118 | protected: |
119 | std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, |
120 | StringRef InFile) override { |
121 | return std::make_unique<CoverageCheckerConsumer>(args&: Checker, |
122 | args&: CI.getPreprocessor()); |
123 | } |
124 | |
125 | private: |
126 | CoverageChecker &Checker; |
127 | }; |
128 | |
129 | class CoverageCheckerFrontendActionFactory : public FrontendActionFactory { |
130 | public: |
131 | CoverageCheckerFrontendActionFactory(CoverageChecker &Checker) |
132 | : Checker(Checker) {} |
133 | |
134 | std::unique_ptr<FrontendAction> create() override { |
135 | return std::make_unique<CoverageCheckerAction>(args&: Checker); |
136 | } |
137 | |
138 | private: |
139 | CoverageChecker &Checker; |
140 | }; |
141 | |
142 | // CoverageChecker class implementation. |
143 | |
144 | // Constructor. |
145 | CoverageChecker::CoverageChecker(StringRef ModuleMapPath, |
146 | std::vector<std::string> &IncludePaths, |
147 | ArrayRef<std::string> CommandLine, |
148 | clang::ModuleMap *ModuleMap) |
149 | : ModuleMapPath(ModuleMapPath), IncludePaths(IncludePaths), |
150 | CommandLine(CommandLine), |
151 | ModMap(ModuleMap) {} |
152 | |
153 | // Create instance of CoverageChecker, to simplify setting up |
154 | // subordinate objects. |
155 | std::unique_ptr<CoverageChecker> CoverageChecker::createCoverageChecker( |
156 | StringRef ModuleMapPath, std::vector<std::string> &IncludePaths, |
157 | ArrayRef<std::string> CommandLine, clang::ModuleMap *ModuleMap) { |
158 | |
159 | return std::make_unique<CoverageChecker>(args&: ModuleMapPath, args&: IncludePaths, |
160 | args&: CommandLine, args&: ModuleMap); |
161 | } |
162 | |
163 | // Do checks. |
164 | // Starting from the directory of the module.modulemap file, |
165 | // Find all header files, optionally looking only at files |
166 | // covered by the include path options, and compare against |
167 | // the headers referenced by the module.modulemap file. |
168 | // Display warnings for unaccounted-for header files. |
169 | // Returns error_code of 0 if there were no errors or warnings, 1 if there |
170 | // were warnings, 2 if any other problem, such as if a bad |
171 | // module map path argument was specified. |
172 | std::error_code CoverageChecker::doChecks() { |
173 | std::error_code returnValue; |
174 | |
175 | // Collect the headers referenced in the modules. |
176 | collectModuleHeaders(); |
177 | |
178 | // Collect the file system headers. |
179 | if (!collectFileSystemHeaders()) |
180 | return std::error_code(2, std::generic_category()); |
181 | |
182 | // Do the checks. These save the problematic file names. |
183 | findUnaccountedForHeaders(); |
184 | |
185 | // Check for warnings. |
186 | if (!UnaccountedForHeaders.empty()) |
187 | returnValue = std::error_code(1, std::generic_category()); |
188 | |
189 | return returnValue; |
190 | } |
191 | |
192 | // The following functions are called by doChecks. |
193 | |
194 | // Collect module headers. |
195 | // Walks the modules and collects referenced headers into |
196 | // ModuleMapHeadersSet. |
197 | void CoverageChecker::() { |
198 | for (ModuleMap::module_iterator I = ModMap->module_begin(), |
199 | E = ModMap->module_end(); |
200 | I != E; ++I) { |
201 | collectModuleHeaders(Mod: *I->second); |
202 | } |
203 | } |
204 | |
205 | // Collect referenced headers from one module. |
206 | // Collects the headers referenced in the given module into |
207 | // ModuleMapHeadersSet. |
208 | // FIXME: Doesn't collect files from umbrella header. |
209 | bool CoverageChecker::(const Module &Mod) { |
210 | |
211 | if (std::optional<Module::Header> = |
212 | Mod.getUmbrellaHeaderAsWritten()) { |
213 | // Collect umbrella header. |
214 | ModuleMapHeadersSet.insert( |
215 | key: ModularizeUtilities::getCanonicalPath(FilePath: UmbrellaHeader->Entry.getName())); |
216 | // Preprocess umbrella header and collect the headers it references. |
217 | if (!collectUmbrellaHeaderHeaders(UmbrellaHeaderName: UmbrellaHeader->Entry.getName())) |
218 | return false; |
219 | } else if (std::optional<Module::DirectoryName> UmbrellaDir = |
220 | Mod.getUmbrellaDirAsWritten()) { |
221 | // Collect headers in umbrella directory. |
222 | if (!collectUmbrellaHeaders(UmbrellaDirName: UmbrellaDir->Entry.getName())) |
223 | return false; |
224 | } |
225 | |
226 | for (auto & : Mod.Headers) |
227 | for (auto & : HeaderKind) |
228 | ModuleMapHeadersSet.insert( |
229 | key: ModularizeUtilities::getCanonicalPath(FilePath: Header.Entry.getName())); |
230 | |
231 | for (auto *Submodule : Mod.submodules()) |
232 | collectModuleHeaders(Mod: *Submodule); |
233 | |
234 | return true; |
235 | } |
236 | |
237 | // Collect headers from an umbrella directory. |
238 | bool CoverageChecker::(StringRef UmbrellaDirName) { |
239 | // Initialize directory name. |
240 | SmallString<256> Directory(ModuleMapDirectory); |
241 | if (UmbrellaDirName.size()) |
242 | sys::path::append(path&: Directory, a: UmbrellaDirName); |
243 | if (Directory.size() == 0) |
244 | Directory = "." ; |
245 | // Walk the directory. |
246 | std::error_code EC; |
247 | for (sys::fs::directory_iterator I(Directory.str(), EC), E; I != E; |
248 | I.increment(ec&: EC)) { |
249 | if (EC) |
250 | return false; |
251 | std::string File(I->path()); |
252 | llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status(); |
253 | if (!Status) |
254 | return false; |
255 | sys::fs::file_type Type = Status->type(); |
256 | // If the file is a directory, ignore the name and recurse. |
257 | if (Type == sys::fs::file_type::directory_file) { |
258 | if (!collectUmbrellaHeaders(UmbrellaDirName: File)) |
259 | return false; |
260 | continue; |
261 | } |
262 | // If the file does not have a common header extension, ignore it. |
263 | if (!ModularizeUtilities::isHeader(FileName: File)) |
264 | continue; |
265 | // Save header name. |
266 | ModuleMapHeadersSet.insert(key: ModularizeUtilities::getCanonicalPath(FilePath: File)); |
267 | } |
268 | return true; |
269 | } |
270 | |
271 | // Collect headers referenced from an umbrella file. |
272 | bool |
273 | CoverageChecker::(StringRef ) { |
274 | |
275 | SmallString<256> PathBuf(ModuleMapDirectory); |
276 | |
277 | // If directory is empty, it's the current directory. |
278 | if (ModuleMapDirectory.length() == 0) |
279 | sys::fs::current_path(result&: PathBuf); |
280 | |
281 | // Create the compilation database. |
282 | std::unique_ptr<CompilationDatabase> Compilations; |
283 | Compilations.reset(p: new FixedCompilationDatabase(Twine(PathBuf), CommandLine)); |
284 | |
285 | std::vector<std::string> ; |
286 | HeaderPath.push_back(x: std::string(UmbrellaHeaderName)); |
287 | |
288 | // Create the tool and run the compilation. |
289 | ClangTool Tool(*Compilations, HeaderPath); |
290 | int HadErrors = Tool.run(Action: new CoverageCheckerFrontendActionFactory(*this)); |
291 | |
292 | // If we had errors, exit early. |
293 | return !HadErrors; |
294 | } |
295 | |
296 | // Called from CoverageCheckerCallbacks to track a header included |
297 | // from an umbrella header. |
298 | void CoverageChecker::(StringRef ) { |
299 | |
300 | SmallString<256> PathBuf(ModuleMapDirectory); |
301 | // If directory is empty, it's the current directory. |
302 | if (ModuleMapDirectory.length() == 0) |
303 | sys::fs::current_path(result&: PathBuf); |
304 | // HeaderName will have an absolute path, so if it's the module map |
305 | // directory, we remove it, also skipping trailing separator. |
306 | if (HeaderName.starts_with(Prefix: PathBuf)) |
307 | HeaderName = HeaderName.substr(Start: PathBuf.size() + 1); |
308 | // Save header name. |
309 | ModuleMapHeadersSet.insert(key: ModularizeUtilities::getCanonicalPath(FilePath: HeaderName)); |
310 | } |
311 | |
312 | // Collect file system header files. |
313 | // This function scans the file system for header files, |
314 | // starting at the directory of the module.modulemap file, |
315 | // optionally filtering out all but the files covered by |
316 | // the include path options. |
317 | // Returns true if no errors. |
318 | bool CoverageChecker::() { |
319 | |
320 | // Get directory containing the module.modulemap file. |
321 | // Might be relative to current directory, absolute, or empty. |
322 | ModuleMapDirectory = ModularizeUtilities::getDirectoryFromPath(Path: ModuleMapPath); |
323 | |
324 | // If no include paths specified, we do the whole tree starting |
325 | // at the module.modulemap directory. |
326 | if (IncludePaths.size() == 0) { |
327 | if (!collectFileSystemHeaders(IncludePath: StringRef("" ))) |
328 | return false; |
329 | } |
330 | else { |
331 | // Otherwise we only look at the sub-trees specified by the |
332 | // include paths. |
333 | for (std::vector<std::string>::const_iterator I = IncludePaths.begin(), |
334 | E = IncludePaths.end(); |
335 | I != E; ++I) { |
336 | if (!collectFileSystemHeaders(IncludePath: *I)) |
337 | return false; |
338 | } |
339 | } |
340 | |
341 | // Sort it, because different file systems might order the file differently. |
342 | llvm::sort(C&: FileSystemHeaders); |
343 | |
344 | return true; |
345 | } |
346 | |
347 | // Collect file system header files from the given path. |
348 | // This function scans the file system for header files, |
349 | // starting at the given directory, which is assumed to be |
350 | // relative to the directory of the module.modulemap file. |
351 | // \returns True if no errors. |
352 | bool CoverageChecker::(StringRef IncludePath) { |
353 | |
354 | // Initialize directory name. |
355 | SmallString<256> Directory(ModuleMapDirectory); |
356 | if (IncludePath.size()) |
357 | sys::path::append(path&: Directory, a: IncludePath); |
358 | if (Directory.size() == 0) |
359 | Directory = "." ; |
360 | if (IncludePath.starts_with(Prefix: "/" ) || IncludePath.starts_with(Prefix: "\\" ) || |
361 | ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) { |
362 | llvm::errs() << "error: Include path \"" << IncludePath |
363 | << "\" is not relative to the module map file.\n" ; |
364 | return false; |
365 | } |
366 | |
367 | // Recursively walk the directory tree. |
368 | std::error_code EC; |
369 | int Count = 0; |
370 | for (sys::fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E; |
371 | I.increment(ec&: EC)) { |
372 | if (EC) |
373 | return false; |
374 | //std::string file(I->path()); |
375 | StringRef file(I->path()); |
376 | llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status(); |
377 | if (!Status) |
378 | return false; |
379 | sys::fs::file_type type = Status->type(); |
380 | // If the file is a directory, ignore the name (but still recurses). |
381 | if (type == sys::fs::file_type::directory_file) |
382 | continue; |
383 | // Assume directories or files starting with '.' are private and not to |
384 | // be considered. |
385 | if (file.contains(Other: "\\." ) || file.contains(Other: "/." )) |
386 | continue; |
387 | // If the file does not have a common header extension, ignore it. |
388 | if (!ModularizeUtilities::isHeader(FileName: file)) |
389 | continue; |
390 | // Save header name. |
391 | FileSystemHeaders.push_back(x: ModularizeUtilities::getCanonicalPath(FilePath: file)); |
392 | Count++; |
393 | } |
394 | if (Count == 0) { |
395 | llvm::errs() << "warning: No headers found in include path: \"" |
396 | << IncludePath << "\"\n" ; |
397 | } |
398 | return true; |
399 | } |
400 | |
401 | // Find headers unaccounted-for in module map. |
402 | // This function compares the list of collected header files |
403 | // against those referenced in the module map. Display |
404 | // warnings for unaccounted-for header files. |
405 | // Save unaccounted-for file list for possible. |
406 | // fixing action. |
407 | // FIXME: There probably needs to be some canonalization |
408 | // of file names so that header path can be correctly |
409 | // matched. Also, a map could be used for the headers |
410 | // referenced in the module, but |
411 | void CoverageChecker::() { |
412 | // Walk over file system headers. |
413 | for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(), |
414 | E = FileSystemHeaders.end(); |
415 | I != E; ++I) { |
416 | // Look for header in module map. |
417 | if (ModuleMapHeadersSet.insert(key: *I).second) { |
418 | UnaccountedForHeaders.push_back(x: *I); |
419 | llvm::errs() << "warning: " << ModuleMapPath |
420 | << " does not account for file: " << *I << "\n" ; |
421 | } |
422 | } |
423 | } |
424 | |