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/FrontendActions.h" 62 #include "clang/Lex/PPCallbacks.h" 63 #include "clang/Lex/Preprocessor.h" 64 #include "clang/Tooling/CompilationDatabase.h" 65 #include "clang/Tooling/Tooling.h" 66 #include "llvm/Option/Option.h" 67 #include "llvm/Support/CommandLine.h" 68 #include "llvm/Support/FileSystem.h" 69 #include "llvm/Support/Path.h" 70 #include "llvm/Support/raw_ostream.h" 71 72 using namespace Modularize; 73 using namespace clang; 74 using namespace clang::driver; 75 using namespace clang::driver::options; 76 using namespace clang::tooling; 77 namespace cl = llvm::cl; 78 namespace sys = llvm::sys; 79 80 // Preprocessor callbacks. 81 // We basically just collect include files. 82 class CoverageCheckerCallbacks : public PPCallbacks { 83 public: 84 CoverageCheckerCallbacks(CoverageChecker &Checker) : Checker(Checker) {} 85 ~CoverageCheckerCallbacks() override {} 86 87 // Include directive callback. 88 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, 89 StringRef FileName, bool IsAngled, 90 CharSourceRange FilenameRange, const FileEntry *File, 91 StringRef SearchPath, StringRef RelativePath, 92 const Module *Imported, 93 SrcMgr::CharacteristicKind FileType) override { 94 Checker.collectUmbrellaHeaderHeader(File->getName()); 95 } 96 97 private: 98 CoverageChecker &Checker; 99 }; 100 101 // Frontend action stuff: 102 103 // Consumer is responsible for setting up the callbacks. 104 class CoverageCheckerConsumer : public ASTConsumer { 105 public: 106 CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) { 107 // PP takes ownership. 108 PP.addPPCallbacks(llvm::make_unique<CoverageCheckerCallbacks>(Checker)); 109 } 110 }; 111 112 class CoverageCheckerAction : public SyntaxOnlyAction { 113 public: 114 CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {} 115 116 protected: 117 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 118 StringRef InFile) override { 119 return llvm::make_unique<CoverageCheckerConsumer>(Checker, 120 CI.getPreprocessor()); 121 } 122 123 private: 124 CoverageChecker &Checker; 125 }; 126 127 class CoverageCheckerFrontendActionFactory : public FrontendActionFactory { 128 public: 129 CoverageCheckerFrontendActionFactory(CoverageChecker &Checker) 130 : Checker(Checker) {} 131 132 CoverageCheckerAction *create() override { 133 return new CoverageCheckerAction(Checker); 134 } 135 136 private: 137 CoverageChecker &Checker; 138 }; 139 140 // CoverageChecker class implementation. 141 142 // Constructor. 143 CoverageChecker::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. 153 std::unique_ptr<CoverageChecker> CoverageChecker::createCoverageChecker( 154 StringRef ModuleMapPath, std::vector<std::string> &IncludePaths, 155 ArrayRef<std::string> CommandLine, clang::ModuleMap *ModuleMap) { 156 157 return llvm::make_unique<CoverageChecker>(ModuleMapPath, IncludePaths, 158 CommandLine, 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. 170 std::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. 195 void 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. 207 bool CoverageChecker::collectModuleHeaders(const Module &Mod) { 208 209 if (const FileEntry *UmbrellaHeader = Mod.getUmbrellaHeader().Entry) { 210 // 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 } 217 else if (const DirectoryEntry *UmbrellaDir = Mod.getUmbrellaDir().Entry) { 218 // Collect headers in umbrella directory. 219 if (!collectUmbrellaHeaders(UmbrellaDir->getName())) 220 return false; 221 } 222 223 for (auto &HeaderKind : Mod.Headers) 224 for (auto &Header : HeaderKind) 225 ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath( 226 Header.Entry->getName())); 227 228 for (auto MI = Mod.submodule_begin(), MIEnd = Mod.submodule_end(); 229 MI != MIEnd; ++MI) 230 collectModuleHeaders(**MI); 231 232 return true; 233 } 234 235 // Collect headers from an umbrella directory. 236 bool 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 for (sys::fs::directory_iterator I(Directory.str(), EC), E; I != E; 246 I.increment(EC)) { 247 if (EC) 248 return false; 249 std::string File(I->path()); 250 llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status(); 251 if (!Status) 252 return false; 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. 270 bool 271 CoverageChecker::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. 291 return !HadErrors; 292 } 293 294 // Called from CoverageCheckerCallbacks to track a header included 295 // from an umbrella header. 296 void 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. 316 bool 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. 350 bool 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 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; 372 //std::string file(I->path()); 373 StringRef file(I->path()); 374 llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status(); 375 if (!Status) 376 return false; 377 sys::fs::file_type type = Status->type(); 378 // If the file is a directory, ignore the name (but still recurses). 379 if (type == sys::fs::file_type::directory_file) 380 continue; 381 // Assume directories or files starting with '.' are private and not to 382 // be considered. 383 if ((file.find("\\.") != StringRef::npos) || 384 (file.find("/.") != StringRef::npos)) 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