//===--- extra/module-map-checker/ModuleMapChecker.cpp -------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file implements a tool that validates a module map by checking that // all headers in the corresponding directories are accounted for. // // Usage: module-map-checker [(module-map-checker options)] // (module-map-file) [(front end options)] // // Options: // // -I(include path) Look at headers only in this directory tree. // Must be a path relative to the module.map file. // There can be multiple -I options, for when the // module map covers multiple directories, and // excludes higher or sibling directories not // specified. If this option is omitted, the // directory containing the module-map-file is // the root of the header tree to be searched for // headers. // // -dump-module-map Dump the module map object during the check. // This displays the modules and headers. // // (front end options) In the case of use of an umbrella header, this can // be used to pass options to the compiler front end // preprocessor, such as -D or -I options. // // This program uses the Clang ModuleMap class to read and parse the module // map file. Starting at the module map file directory, or just the include // paths, if specified, it will collect the names of all the files it // considers headers (no extension, .h, or .inc--if you need more, modify the // isHeader function). It then compares the headers against those referenced // in the module map, either explicitly named, or implicitly named via an // umbrella directory or umbrella file, as parsed by the ModuleMap object. // If headers are found which are not referenced or covered by an umbrella // directory or file, warning messages will be produced, and this program // will return an error code of 1. Other errors result in an error code of 2. // If no problems are found, an error code of 0 is returned. // // Note that in the case of umbrella headers, this tool invokes the compiler // to preprocess the file, and uses a callback to collect the header files // included by the umbrella header or any of its nested includes. If any // front end options are needed for these compiler invocations, these // can be included on the command line after the module map file argument. // // Warning message have the form: // // warning: module.map does not account for file: Level3A.h // // Note that for the case of the module map referencing a file that does // not exist, the module map parser in Clang will (at the time of this // writing) display an error message. // // Potential problems with this program: // // 1. Might need a better header matching mechanism, or extensions to the // canonical file format used. // // 2. It might need to support additional header file extensions. // // Future directions: // // 1. Add an option to fix the problems found, writing a new module map. // Include an extra option to add unaccounted-for headers as excluded. // //===----------------------------------------------------------------------===// #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/SourceManager.h" #include "clang/Driver/Options.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "ModuleMapChecker.h" using namespace clang; using namespace clang::driver; using namespace clang::driver::options; using namespace clang::tooling; using namespace llvm; using namespace llvm::opt; using namespace llvm::sys; // Option for include paths. static cl::list IncludePaths("I", cl::desc("Include path." " Must be relative to module.map file."), cl::ZeroOrMore, cl::value_desc("path")); // Option for dumping the parsed module map. static cl::opt DumpModuleMap("dump-module-map", cl::init(false), cl::desc("Dump the parsed module map information.")); // Option for module.map path. static cl::opt ModuleMapPath(cl::Positional, cl::init("module.map"), cl::desc("")); // Collect all other arguments, which will be passed to the front end. static cl::list CC1Arguments(cl::ConsumeAfter, cl::desc("...")); int main(int Argc, const char **Argv) { // Parse command line. cl::ParseCommandLineOptions(Argc, Argv, "module-map-checker.\n"); // Create checker object. OwningPtr Checker(ModuleMapChecker::createModuleMapChecker( ModuleMapPath, IncludePaths, DumpModuleMap, CC1Arguments)); // Do the checks. The return value is the program return code, // 0 for okay, 1 for module map warnings produced, 2 for any other error. error_code ReturnCode = Checker->doChecks(); if (ReturnCode == error_code(1, generic_category())) return 1; // Module map warnings were issued. else if (ReturnCode == error_code(2, generic_category())) return 2; // Some other error occurred. else return 0; // No errors or warnings. } // Preprocessor callbacks. // We basically just collect include files. class ModuleMapCheckerCallbacks : public PPCallbacks { public: ModuleMapCheckerCallbacks(ModuleMapChecker &Checker) : Checker(Checker) {} ~ModuleMapCheckerCallbacks() {} // Include directive callback. void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported) { Checker.collectUmbrellaHeaderHeader(File->getName()); } private: ModuleMapChecker &Checker; }; // Frontend action stuff: // Consumer is responsible for setting up the callbacks. class ModuleMapCheckerConsumer : public ASTConsumer { public: ModuleMapCheckerConsumer(ModuleMapChecker &Checker, Preprocessor &PP) { // PP takes ownership. PP.addPPCallbacks(new ModuleMapCheckerCallbacks(Checker)); } }; class ModuleMapCheckerAction : public SyntaxOnlyAction { public: ModuleMapCheckerAction(ModuleMapChecker &Checker) : Checker(Checker) {} protected: virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { return new ModuleMapCheckerConsumer(Checker, CI.getPreprocessor()); } private: ModuleMapChecker &Checker; }; class ModuleMapCheckerFrontendActionFactory : public FrontendActionFactory { public: ModuleMapCheckerFrontendActionFactory(ModuleMapChecker &Checker) : Checker(Checker) {} virtual ModuleMapCheckerAction *create() { return new ModuleMapCheckerAction(Checker); } private: ModuleMapChecker &Checker; }; // ModuleMapChecker class implementation. // Constructor. ModuleMapChecker::ModuleMapChecker(StringRef ModuleMapPath, std::vector &IncludePaths, bool DumpModuleMap, ArrayRef CommandLine) : ModuleMapPath(ModuleMapPath), IncludePaths(IncludePaths), DumpModuleMap(DumpModuleMap), CommandLine(CommandLine), LangOpts(new LangOptions()), DiagIDs(new DiagnosticIDs()), DiagnosticOpts(new DiagnosticOptions()), DC(errs(), DiagnosticOpts.getPtr()), Diagnostics( new DiagnosticsEngine(DiagIDs, DiagnosticOpts.getPtr(), &DC, false)), TargetOpts(new ModuleMapTargetOptions()), Target(TargetInfo::CreateTargetInfo(*Diagnostics, TargetOpts.getPtr())), FileMgr(new FileManager(FileSystemOpts)), SourceMgr(new SourceManager(*Diagnostics, *FileMgr, false)), HeaderSearchOpts(new HeaderSearchOptions()), HeaderInfo(new HeaderSearch(HeaderSearchOpts, *SourceMgr, *Diagnostics, *LangOpts, Target.getPtr())), ModMap(new ModuleMap(*SourceMgr, *Diagnostics, *LangOpts, Target.getPtr(), *HeaderInfo)) {} // Create instance of ModuleMapChecker, to simplify setting up // subordinate objects. ModuleMapChecker *ModuleMapChecker::createModuleMapChecker( StringRef ModuleMapPath, std::vector &IncludePaths, bool DumpModuleMap, ArrayRef CommandLine) { return new ModuleMapChecker(ModuleMapPath, IncludePaths, DumpModuleMap, CommandLine); } // Do checks. // Starting from the directory of the module.map file, // Find all header files, optionally looking only at files // covered by the include path options, and compare against // the headers referenced by the module.map file. // Display warnings for unaccounted-for header files. // Returns error_code of 0 if there were no errors or warnings, 1 if there // were warnings, 2 if any other problem, such as if a bad // module map path argument was specified. error_code ModuleMapChecker::doChecks() { error_code returnValue; // Load the module map. if (!loadModuleMap()) return error_code(2, generic_category()); // Collect the headers referenced in the modules. collectModuleHeaders(); // Collect the file system headers. if (!collectFileSystemHeaders()) return error_code(2, generic_category()); // Do the checks. These save the problematic file names. findUnaccountedForHeaders(); // Check for warnings. if (UnaccountedForHeaders.size()) returnValue = error_code(1, generic_category()); // Dump module map if requested. if (DumpModuleMap) { errs() << "\nDump of module map:\n\n"; ModMap->dump(); } return returnValue; } // The following functions are called by doChecks. // Load module map. // Returns true if module.map file loaded successfully. bool ModuleMapChecker::loadModuleMap() { // Get file entry for module.map file. const FileEntry *ModuleMapEntry = SourceMgr->getFileManager().getFile(ModuleMapPath); // return error if not found. if (!ModuleMapEntry) { errs() << "error: File \"" << ModuleMapPath << "\" not found.\n"; return false; } // Because the module map parser uses a ForwardingDiagnosticConsumer, // which doesn't forward the BeginSourceFile call, we do it explicitly here. DC.BeginSourceFile(*LangOpts, 0); // Parse module.map file into module map. if (ModMap->parseModuleMapFile(ModuleMapEntry, false)) return false; // Do matching end call. DC.EndSourceFile(); return true; } // Collect module headers. // Walks the modules and collects referenced headers into // ModuleMapHeadersSet. void ModuleMapChecker::collectModuleHeaders() { for (ModuleMap::module_iterator I = ModMap->module_begin(), E = ModMap->module_end(); I != E; ++I) { collectModuleHeaders(*I->second); } } // Collect referenced headers from one module. // Collects the headers referenced in the given module into // ModuleMapHeadersSet. // FIXME: Doesn't collect files from umbrella header. bool ModuleMapChecker::collectModuleHeaders(const Module &Mod) { if (const FileEntry *UmbrellaHeader = Mod.getUmbrellaHeader()) { // Collect umbrella header. ModuleMapHeadersSet.insert(getCanonicalPath(UmbrellaHeader->getName())); // Preprocess umbrella header and collect the headers it references. if (!collectUmbrellaHeaderHeaders(UmbrellaHeader->getName())) return false; } else if (const DirectoryEntry *UmbrellaDir = Mod.getUmbrellaDir()) { // Collect headers in umbrella directory. if (!collectUmbrellaHeaders(UmbrellaDir->getName())) return false; } for (unsigned I = 0, N = Mod.NormalHeaders.size(); I != N; ++I) { ModuleMapHeadersSet.insert( getCanonicalPath(Mod.NormalHeaders[I]->getName())); } for (unsigned I = 0, N = Mod.ExcludedHeaders.size(); I != N; ++I) { ModuleMapHeadersSet.insert( getCanonicalPath(Mod.ExcludedHeaders[I]->getName())); } for (unsigned I = 0, N = Mod.PrivateHeaders.size(); I != N; ++I) { ModuleMapHeadersSet.insert( getCanonicalPath(Mod.PrivateHeaders[I]->getName())); } for (Module::submodule_const_iterator MI = Mod.submodule_begin(), MIEnd = Mod.submodule_end(); MI != MIEnd; ++MI) collectModuleHeaders(**MI); return true; } // Collect headers from an umbrella directory. bool ModuleMapChecker::collectUmbrellaHeaders(StringRef UmbrellaDirName) { // Initialize directory name. SmallString<256> Directory(ModuleMapDirectory); if (UmbrellaDirName.size()) sys::path::append(Directory, UmbrellaDirName); if (Directory.size() == 0) Directory = "."; // Walk the directory. error_code EC; fs::file_status Status; for (fs::directory_iterator I(Directory.str(), EC), E; I != E; I.increment(EC)) { if (EC) return false; std::string File(I->path()); I->status(Status); fs::file_type Type = Status.type(); // If the file is a directory, ignore the name. if (Type == fs::file_type::directory_file) continue; // If the file does not have a common header extension, ignore it. if (!isHeader(File)) continue; // Save header name. ModuleMapHeadersSet.insert(getCanonicalPath(File)); } return true; } // Collect headers rferenced from an umbrella file. bool ModuleMapChecker::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) { SmallString<256> PathBuf(ModuleMapDirectory); // If directory is empty, it's the current directory. if (ModuleMapDirectory.length() == 0) sys::fs::current_path(PathBuf); // Create the compilation database. OwningPtr Compilations; Compilations.reset(new FixedCompilationDatabase(Twine(PathBuf), CommandLine)); std::vector HeaderPath; HeaderPath.push_back(UmbrellaHeaderName); // Create the tool and run the compilation. ClangTool Tool(*Compilations, HeaderPath); int HadErrors = Tool.run(new ModuleMapCheckerFrontendActionFactory(*this)); // If we had errors, exit early. return HadErrors ? false : true; } // Called from ModuleMapCheckerCallbacks to track a header included // from an umbrella header. void ModuleMapChecker::collectUmbrellaHeaderHeader(StringRef HeaderName) { SmallString<256> PathBuf(ModuleMapDirectory); // If directory is empty, it's the current directory. if (ModuleMapDirectory.length() == 0) sys::fs::current_path(PathBuf); // HeaderName will have an absolute path, so if it's the module map // directory, we remove it, also skipping trailing separator. if (HeaderName.startswith(PathBuf)) HeaderName = HeaderName.substr(PathBuf.size() + 1); // Save header name. ModuleMapHeadersSet.insert(getCanonicalPath(HeaderName)); } // Collect file system header files. // This function scans the file system for header files, // starting at the directory of the module.map file, // optionally filtering out all but the files covered by // the include path options. // Returns true if no errors. bool ModuleMapChecker::collectFileSystemHeaders() { // Get directory containing the module.map file. // Might be relative to current directory, absolute, or empty. ModuleMapDirectory = getDirectoryFromPath(ModuleMapPath); // If no include paths specified, we do the whole tree starting // at the module.map directory. if (IncludePaths.size() == 0) { if (!collectFileSystemHeaders(StringRef(""))) return false; } else { // Otherwise we only look at the sub-trees specified by the // include paths. for (std::vector::const_iterator I = IncludePaths.begin(), E = IncludePaths.end(); I != E; ++I) { if (!collectFileSystemHeaders(*I)) return false; } } // Sort it, because different file systems might order the file differently. std::sort(FileSystemHeaders.begin(), FileSystemHeaders.end()); return true; } // Collect file system header files from the given path. // This function scans the file system for header files, // starting at the given directory, which is assumed to be // relative to the directory of the module.map file. // \returns True if no errors. bool ModuleMapChecker::collectFileSystemHeaders(StringRef IncludePath) { // Initialize directory name. SmallString<256> Directory(ModuleMapDirectory); if (IncludePath.size()) sys::path::append(Directory, IncludePath); if (Directory.size() == 0) Directory = "."; if (IncludePath.startswith("/") || IncludePath.startswith("\\") || ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) { errs() << "error: Include path \"" << IncludePath << "\" is not relative to the module map file.\n"; return false; } // Recursively walk the directory tree. error_code EC; fs::file_status Status; int Count = 0; for (fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E; I.increment(EC)) { if (EC) return false; std::string file(I->path()); I->status(Status); fs::file_type type = Status.type(); // If the file is a directory, ignore the name (but still recurses). if (type == fs::file_type::directory_file) continue; // If the file does not have a common header extension, ignore it. if (!isHeader(file)) continue; // Save header name. FileSystemHeaders.push_back(getCanonicalPath(file)); Count++; } if (Count == 0) { errs() << "warning: No headers found in include path: \"" << IncludePath << "\"\n"; } return true; } // Find headers unaccounted-for in module map. // This function compares the list of collected header files // against those referenced in the module map. Display // warnings for unaccounted-for header files. // Save unaccounted-for file list for possible. // fixing action. // FIXME: There probably needs to be some canonalization // of file names so that header path can be correctly // matched. Also, a map could be used for the headers // referenced in the module, but void ModuleMapChecker::findUnaccountedForHeaders() { // Walk over file system headers. for (std::vector::const_iterator I = FileSystemHeaders.begin(), E = FileSystemHeaders.end(); I != E; ++I) { // Look for header in module map. if (ModuleMapHeadersSet.insert(*I)) { UnaccountedForHeaders.push_back(*I); errs() << "warning: " << ModuleMapPath << " does not account for file: " << *I << "\n"; } } } // Utility functions. // Get directory path component from file path. // \returns the component of the given path, which will be // relative if the given path is relative, absolute if the // given path is absolute, or "." if the path has no leading // path component. std::string ModuleMapChecker::getDirectoryFromPath(StringRef Path) { SmallString<256> Directory(Path); sys::path::remove_filename(Directory); if (Directory.size() == 0) return "."; return Directory.str(); } // Convert header path to canonical form. // The canonical form is basically just use forward slashes, and remove "./". // \param FilePath The file path, relative to the module map directory. // \returns The file path in canonical form. std::string ModuleMapChecker::getCanonicalPath(StringRef FilePath) { std::string Tmp(FilePath); std::replace(Tmp.begin(), Tmp.end(), '\\', '/'); StringRef Result(Tmp); if (Result.startswith("./")) Result = Result.substr(2); return Result; } // Check for header file extension. // If the file extension is .h, .inc, or missing, it's // assumed to be a header. // \param FileName The file name. Must not be a directory. // \returns true if it has a header extension or no extension. bool ModuleMapChecker::isHeader(StringRef FileName) { StringRef Extension = sys::path::extension(FileName); if (Extension.size() == 0) return false; if (Extension.equals_lower(".h")) return true; if (Extension.equals_lower(".inc")) return true; return false; }