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(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(std::make_unique<CoverageCheckerCallbacks>(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>(Checker, 122 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>(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>(ModuleMapPath, IncludePaths, 160 CommandLine, 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::collectModuleHeaders() { 198 for (ModuleMap::module_iterator I = ModMap->module_begin(), 199 E = ModMap->module_end(); 200 I != E; ++I) { 201 collectModuleHeaders(*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::collectModuleHeaders(const Module &Mod) { 210 211 if (std::optional<Module::Header> UmbrellaHeader = 212 Mod.getUmbrellaHeaderAsWritten()) { 213 // Collect umbrella header. 214 ModuleMapHeadersSet.insert( 215 ModularizeUtilities::getCanonicalPath(UmbrellaHeader->Entry.getName())); 216 // Preprocess umbrella header and collect the headers it references. 217 if (!collectUmbrellaHeaderHeaders(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(UmbrellaDir->Entry.getName())) 223 return false; 224 } 225 226 for (const auto &Header : Mod.getAllHeaders()) 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::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 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::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) { 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 FixedCompilationDatabase Compilations(Twine(PathBuf), CommandLine); 282 283 std::vector<std::string> HeaderPath; 284 HeaderPath.push_back(std::string(UmbrellaHeaderName)); 285 286 // Create the tool and run the compilation. 287 ClangTool Tool(Compilations, HeaderPath); 288 CoverageCheckerFrontendActionFactory ActionFactory(*this); 289 int HadErrors = Tool.run(&ActionFactory); 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::collectUmbrellaHeaderHeader(StringRef HeaderName) { 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.starts_with(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::collectFileSystemHeaders() { 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::collectFileSystemHeaders(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.starts_with("/") || IncludePath.starts_with("\\") || 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::findUnaccountedForHeaders() { 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