1 //===--- 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 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, const FileEntry *File, 92 StringRef SearchPath, StringRef RelativePath, 93 const Module *Imported) 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 CoverageChecker *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. 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 sys::fs::file_status Status; 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 I->status(Status); 252 sys::fs::file_type Type = Status.type(); 253 // If the file is a directory, ignore the name and recurse. 254 if (Type == sys::fs::file_type::directory_file) { 255 if (!collectUmbrellaHeaders(File)) 256 return false; 257 continue; 258 } 259 // If the file does not have a common header extension, ignore it. 260 if (!ModularizeUtilities::isHeader(File)) 261 continue; 262 // Save header name. 263 ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(File)); 264 } 265 return true; 266 } 267 268 // Collect headers rferenced from an umbrella file. 269 bool 270 CoverageChecker::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) { 271 272 SmallString<256> PathBuf(ModuleMapDirectory); 273 274 // If directory is empty, it's the current directory. 275 if (ModuleMapDirectory.length() == 0) 276 sys::fs::current_path(PathBuf); 277 278 // Create the compilation database. 279 std::unique_ptr<CompilationDatabase> Compilations; 280 Compilations.reset(new FixedCompilationDatabase(Twine(PathBuf), CommandLine)); 281 282 std::vector<std::string> HeaderPath; 283 HeaderPath.push_back(UmbrellaHeaderName); 284 285 // Create the tool and run the compilation. 286 ClangTool Tool(*Compilations, HeaderPath); 287 int HadErrors = Tool.run(new CoverageCheckerFrontendActionFactory(*this)); 288 289 // If we had errors, exit early. 290 return !HadErrors; 291 } 292 293 // Called from CoverageCheckerCallbacks to track a header included 294 // from an umbrella header. 295 void CoverageChecker::collectUmbrellaHeaderHeader(StringRef HeaderName) { 296 297 SmallString<256> PathBuf(ModuleMapDirectory); 298 // If directory is empty, it's the current directory. 299 if (ModuleMapDirectory.length() == 0) 300 sys::fs::current_path(PathBuf); 301 // HeaderName will have an absolute path, so if it's the module map 302 // directory, we remove it, also skipping trailing separator. 303 if (HeaderName.startswith(PathBuf)) 304 HeaderName = HeaderName.substr(PathBuf.size() + 1); 305 // Save header name. 306 ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(HeaderName)); 307 } 308 309 // Collect file system header files. 310 // This function scans the file system for header files, 311 // starting at the directory of the module.modulemap file, 312 // optionally filtering out all but the files covered by 313 // the include path options. 314 // Returns true if no errors. 315 bool CoverageChecker::collectFileSystemHeaders() { 316 317 // Get directory containing the module.modulemap file. 318 // Might be relative to current directory, absolute, or empty. 319 ModuleMapDirectory = ModularizeUtilities::getDirectoryFromPath(ModuleMapPath); 320 321 // If no include paths specified, we do the whole tree starting 322 // at the module.modulemap directory. 323 if (IncludePaths.size() == 0) { 324 if (!collectFileSystemHeaders(StringRef(""))) 325 return false; 326 } 327 else { 328 // Otherwise we only look at the sub-trees specified by the 329 // include paths. 330 for (std::vector<std::string>::const_iterator I = IncludePaths.begin(), 331 E = IncludePaths.end(); 332 I != E; ++I) { 333 if (!collectFileSystemHeaders(*I)) 334 return false; 335 } 336 } 337 338 // Sort it, because different file systems might order the file differently. 339 std::sort(FileSystemHeaders.begin(), FileSystemHeaders.end()); 340 341 return true; 342 } 343 344 // Collect file system header files from the given path. 345 // This function scans the file system for header files, 346 // starting at the given directory, which is assumed to be 347 // relative to the directory of the module.modulemap file. 348 // \returns True if no errors. 349 bool CoverageChecker::collectFileSystemHeaders(StringRef IncludePath) { 350 351 // Initialize directory name. 352 SmallString<256> Directory(ModuleMapDirectory); 353 if (IncludePath.size()) 354 sys::path::append(Directory, IncludePath); 355 if (Directory.size() == 0) 356 Directory = "."; 357 if (IncludePath.startswith("/") || IncludePath.startswith("\\") || 358 ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) { 359 llvm::errs() << "error: Include path \"" << IncludePath 360 << "\" is not relative to the module map file.\n"; 361 return false; 362 } 363 364 // Recursively walk the directory tree. 365 std::error_code EC; 366 sys::fs::file_status Status; 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 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 // Assume directories or files starting with '.' are private and not to 380 // be considered. 381 if ((file.find("\\.") != StringRef::npos) || 382 (file.find("/.") != StringRef::npos)) 383 continue; 384 // If the file does not have a common header extension, ignore it. 385 if (!ModularizeUtilities::isHeader(file)) 386 continue; 387 // Save header name. 388 FileSystemHeaders.push_back(ModularizeUtilities::getCanonicalPath(file)); 389 Count++; 390 } 391 if (Count == 0) { 392 llvm::errs() << "warning: No headers found in include path: \"" 393 << IncludePath << "\"\n"; 394 } 395 return true; 396 } 397 398 // Find headers unaccounted-for in module map. 399 // This function compares the list of collected header files 400 // against those referenced in the module map. Display 401 // warnings for unaccounted-for header files. 402 // Save unaccounted-for file list for possible. 403 // fixing action. 404 // FIXME: There probably needs to be some canonalization 405 // of file names so that header path can be correctly 406 // matched. Also, a map could be used for the headers 407 // referenced in the module, but 408 void CoverageChecker::findUnaccountedForHeaders() { 409 // Walk over file system headers. 410 for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(), 411 E = FileSystemHeaders.end(); 412 I != E; ++I) { 413 // Look for header in module map. 414 if (ModuleMapHeadersSet.insert(*I).second) { 415 UnaccountedForHeaders.push_back(*I); 416 llvm::errs() << "warning: " << ModuleMapPath 417 << " does not account for file: " << *I << "\n"; 418 } 419 } 420 } 421