Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(modularize) +add_subdirectory(module-map-checker) add_subdirectory(pp-trace) add_subdirectory(remove-cstr-calls) add_subdirectory(tool-template) Index: Makefile =================================================================== --- Makefile +++ Makefile @@ -11,7 +11,8 @@ include $(CLANG_LEVEL)/../../Makefile.config -PARALLEL_DIRS := remove-cstr-calls tool-template modularize pp-trace +PARALLEL_DIRS := remove-cstr-calls tool-template modularize \ + module-map-checker pp-trace DIRS := clang-apply-replacements clang-modernize clang-tidy clang-query \ unittests Index: module-map-checker/CMakeLists.txt =================================================================== --- module-map-checker/CMakeLists.txt +++ module-map-checker/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_executable(module-map-checker + ModuleMapChecker.cpp + ) + +target_link_libraries(module-map-checker + clangTooling + clangBasic + clangRewriteFrontend + ) Index: module-map-checker/Makefile =================================================================== --- module-map-checker/Makefile +++ module-map-checker/Makefile @@ -0,0 +1,24 @@ +##===- tools/module-map-checker/Makefile ------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. + +TOOLNAME = module-map-checker +NO_INSTALL = 0 + +# No plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +LINK_COMPONENTS := mcparser bitreader support mc option TransformUtils +USEDLIBS = clangFrontend.a clangSerialization.a clangDriver.a \ + clangTooling.a clangParse.a clangSema.a clangAnalysis.a \ + clangEdit.a clangAST.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + Index: module-map-checker/ModuleMapChecker.h =================================================================== --- module-map-checker/ModuleMapChecker.h +++ module-map-checker/ModuleMapChecker.h @@ -0,0 +1,223 @@ +//===-- ModuleMapChecker.h - Common defs for module-map-checker -*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===--------------------------------------------------------------------===// +/// +/// \file +/// \brief Common definitions for ModuleMapChecker. +/// +//===--------------------------------------------------------------------===// + +#ifndef MODULEMAPCHECKER_H +#define MODULEMAPCHECKER_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Basic/TargetOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/HeaderSearchOptions.h" +#include "clang/Lex/ModuleMap.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Host.h" +#include +#include + +/// Subclass TargetOptions so we can construct it inline with +/// the minimal option, the triple. +class ModuleMapTargetOptions : public clang::TargetOptions { +public: + ModuleMapTargetOptions() { Triple = llvm::sys::getDefaultTargetTriple(); } +}; + +/// Module map checker class. +/// This is the heart of the checker. +/// The doChecks function does the main work. +/// The data members store the options and internally collected data. +class ModuleMapChecker { + // Checker arguments. + + /// The module.map file path. Can be relative or absolute. + llvm::StringRef ModuleMapPath; + /// The include paths to check for files. + /// (Note that other directories above these paths are ignored. + /// To expect all files to be accounted for from the module.map + /// file directory on down, leave this empty.) + std::vector IncludePaths; + /// Flag to dump the module map information during check. + bool DumpModuleMap; + /// The remaining arguments, to be passed to the front end. + llvm::ArrayRef CommandLine; + + // Supporting objects. + + /// Options controlling the language variant. + llvm::IntrusiveRefCntPtr LangOpts; + /// Diagnostic IDs. + const llvm::IntrusiveRefCntPtr DiagIDs; + /// Options controlling the diagnostic engine. + llvm::IntrusiveRefCntPtr DiagnosticOpts; + /// Diagnostic consumer. + clang::TextDiagnosticPrinter DC; + /// Diagnostic engine. + llvm::IntrusiveRefCntPtr Diagnostics; + /// Options controlling the target. + llvm::IntrusiveRefCntPtr TargetOpts; + /// Target information. + llvm::IntrusiveRefCntPtr Target; + /// Options controlling the file system manager. + clang::FileSystemOptions FileSystemOpts; + /// File system manager. + llvm::IntrusiveRefCntPtr FileMgr; + /// Source manager. + llvm::IntrusiveRefCntPtr SourceMgr; + /// Options controlling the \#include directive. + llvm::IntrusiveRefCntPtr HeaderSearchOpts; + /// Header search manager. + llvm::OwningPtr HeaderInfo; + /// The module map. + llvm::OwningPtr ModMap; + + // Internal data. + + /// Directory containing the module map. + /// Might be relative to the current directory, or absolute. + std::string ModuleMapDirectory; + /// Set of all the headers found in the module map. + llvm::StringSet ModuleMapHeadersSet; + /// All the headers found in the file system starting at the + /// module map, or the union of those from the include paths. + std::vector FileSystemHeaders; + /// Headers found in file system, but not in module map. + std::vector UnaccountedForHeaders; + +public: + /// Constructor. + /// You can use the static createModuleMapChecker to create an instance + /// of this object. + /// \param ModuleMapPath The module.map file path. + /// Can be relative or absolute. + /// \param IncludePaths The include paths to check for files. + /// (Note that other directories above these paths are ignored. + /// To expect all files to be accounted for from the module.map + /// file directory on down, leave this empty.) + /// \param DumpModuleMap Flag to dump the module map information + /// during check. + ModuleMapChecker(llvm::StringRef ModuleMapPath, + std::vector &IncludePaths, bool DumpModuleMap, + llvm::ArrayRef CommandLine); + + /// Create instance of ModuleMapChecker. + /// \param ModuleMapPath The module.map file path. + /// Can be relative or absolute. + /// \param IncludePaths The include paths to check for files. + /// (Note that other directories above these paths are ignored. + /// To expect all files to be accounted for from the module.map + /// file directory on down, leave this empty.) + /// \param DumpModuleMap Flag to dump the module map information + /// during check. + /// \returns Initialized ModuleMapChecker object. + static ModuleMapChecker *createModuleMapChecker( + llvm::StringRef ModuleMapPath, std::vector &IncludePaths, + bool DumpModuleMap, llvm::ArrayRef 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 0 if there were no errors or warnings, 1 if there + /// were warnings, 2 if any other problem, such as a bad + /// module map path argument was specified. + llvm::error_code doChecks(); + + // The following functions are called by doChecks. + + /// Load module map. + /// \returns True if module.map file loaded successfully. + bool loadModuleMap(); + + /// Collect module headers. + /// Walks the modules and collects referenced headers into + /// ModuleMapHeadersSet. + void collectModuleHeaders(); + + /// Collect referenced headers from one module. + /// Collects the headers referenced in the given module into + /// ModuleMapHeadersSet. + /// \param Mod The module reference. + /// \return True if no errors. + bool collectModuleHeaders(const clang::Module &Mod); + + /// Collect headers from an umbrella directory. + /// \param UmbrellaDirName The umbrella directory name. + /// \return True if no errors. + bool collectUmbrellaHeaders(llvm::StringRef UmbrellaDirName); + + /// Collect headers rferenced from an umbrella file. + /// \param UmbrellaHeaderName The umbrella file path. + /// \return True if no errors. + bool collectUmbrellaHeaderHeaders(llvm::StringRef UmbrellaHeaderName); + + /// Called from ModuleMapCheckerCallbacks to track a header included + /// from an umbrella header. + /// \param HeaderName The header file path. + void collectUmbrellaHeaderHeader(llvm::StringRef 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 collectFileSystemHeaders(); + + /// 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 collectFileSystemHeaders(llvm::StringRef IncludePath); + + /// 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. + void findUnaccountedForHeaders(); + + // 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 getDirectoryFromPath(llvm::StringRef Path); + + /// Convert header path to canonical form. + /// The canonical form is basically just use forward slashes, + /// and remove "./". + /// \param FilePath The file path. + /// \returns The file path in canonical form. + std::string getCanonicalPath(llvm::StringRef FilePath); + + /// 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 isHeader(llvm::StringRef FileName); +}; + +#endif // MODULEMAPCHECKER_H Index: module-map-checker/ModuleMapChecker.cpp =================================================================== --- module-map-checker/ModuleMapChecker.cpp +++ module-map-checker/ModuleMapChecker.cpp @@ -0,0 +1,563 @@ +//===--- 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 = "."; + + // Recursively walk the directory tree. + error_code EC; + fs::file_status Status; + 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)); + } + 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; +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -31,6 +31,7 @@ clang-modernize clang-tidy modularize + module-map-checker pp-trace remove-cstr-calls Index: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/lit.cfg @@ -34,7 +34,7 @@ config.test_format = lit.formats.ShTest(execute_external) # suffixes: A list of file extensions to treat as test files. -config.suffixes = ['.c', '.cpp', '.m', '.mm', '.cu', '.ll', '.cl', '.s', '.modularize'] +config.suffixes = ['.c', '.cpp', '.m', '.mm', '.cu', '.ll', '.cl', '.s', '.modularize', '.module-map-checker'] # Test-time dependencies located in directories called 'Inputs' are excluded # from test suites; there won't be any lit tests within them. Index: test/module-map-checker/Inputs/includes-test/Includes1/Level1A.h =================================================================== --- test/module-map-checker/Inputs/includes-test/Includes1/Level1A.h +++ test/module-map-checker/Inputs/includes-test/Includes1/Level1A.h @@ -0,0 +1 @@ +#define MACRO_1A 1 Index: test/module-map-checker/Inputs/includes-test/Includes2/Level2A.h =================================================================== --- test/module-map-checker/Inputs/includes-test/Includes2/Level2A.h +++ test/module-map-checker/Inputs/includes-test/Includes2/Level2A.h @@ -0,0 +1 @@ +#define MACRO_2A 1 Index: test/module-map-checker/Inputs/includes-test/NonIncludes/Level3A.h =================================================================== --- test/module-map-checker/Inputs/includes-test/NonIncludes/Level3A.h +++ test/module-map-checker/Inputs/includes-test/NonIncludes/Level3A.h @@ -0,0 +1 @@ +#define MACRO_3A 1 Index: test/module-map-checker/Inputs/includes-test/module.map =================================================================== --- test/module-map-checker/Inputs/includes-test/module.map +++ test/module-map-checker/Inputs/includes-test/module.map @@ -0,0 +1,10 @@ +// module.map + +module Level1A { + header "Includes1/Level1A.h" + export * +} +module Level2A { + header "Includes2/Level2A.h" + export * +} Index: test/module-map-checker/Inputs/main-test/Level1A.h =================================================================== --- test/module-map-checker/Inputs/main-test/Level1A.h +++ test/module-map-checker/Inputs/main-test/Level1A.h @@ -0,0 +1,2 @@ +#include "Level2A.h" +#define MACRO_1A 1 Index: test/module-map-checker/Inputs/main-test/Level1B.h =================================================================== --- test/module-map-checker/Inputs/main-test/Level1B.h +++ test/module-map-checker/Inputs/main-test/Level1B.h @@ -0,0 +1,2 @@ +#include "Level2B.h" +#define MACRO_1B 1 Index: test/module-map-checker/Inputs/main-test/Level2A.h =================================================================== --- test/module-map-checker/Inputs/main-test/Level2A.h +++ test/module-map-checker/Inputs/main-test/Level2A.h @@ -0,0 +1 @@ +#define MACRO_2A 1 Index: test/module-map-checker/Inputs/main-test/Level2B.h =================================================================== --- test/module-map-checker/Inputs/main-test/Level2B.h +++ test/module-map-checker/Inputs/main-test/Level2B.h @@ -0,0 +1 @@ +#define MACRO_2B 1 Index: test/module-map-checker/Inputs/main-test/Level3A.h =================================================================== --- test/module-map-checker/Inputs/main-test/Level3A.h +++ test/module-map-checker/Inputs/main-test/Level3A.h @@ -0,0 +1,2 @@ +#include "Sub/Level3B.h" +#define MACRO_3A 1 Index: test/module-map-checker/Inputs/main-test/Sub/Level3B.h =================================================================== --- test/module-map-checker/Inputs/main-test/Sub/Level3B.h +++ test/module-map-checker/Inputs/main-test/Sub/Level3B.h @@ -0,0 +1 @@ +#define MACRO_3B 1 Index: test/module-map-checker/Inputs/main-test/UmbrellaFile.h =================================================================== --- test/module-map-checker/Inputs/main-test/UmbrellaFile.h +++ test/module-map-checker/Inputs/main-test/UmbrellaFile.h @@ -0,0 +1,3 @@ +#define UMBRELLA_HEADER 1 +#include "UmbrellaInclude1.h" +#include "UmbrellaInclude2.h" Index: test/module-map-checker/Inputs/main-test/UmbrellaInclude1.h =================================================================== --- test/module-map-checker/Inputs/main-test/UmbrellaInclude1.h +++ test/module-map-checker/Inputs/main-test/UmbrellaInclude1.h @@ -0,0 +1 @@ +#define UMBRELLA_INCLUDE_1 1 Index: test/module-map-checker/Inputs/main-test/UmbrellaInclude2.h =================================================================== --- test/module-map-checker/Inputs/main-test/UmbrellaInclude2.h +++ test/module-map-checker/Inputs/main-test/UmbrellaInclude2.h @@ -0,0 +1 @@ +#define UMBRELLA_INCLUDE_2 1 Index: test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell1.h =================================================================== --- test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell1.h +++ test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell1.h @@ -0,0 +1 @@ +#define UMBRELLA_1 1 Index: test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell2.h =================================================================== --- test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell2.h +++ test/module-map-checker/Inputs/main-test/UmbrellaSub/Umbrell2.h @@ -0,0 +1 @@ +#define UMBRELLA_2 1 Index: test/module-map-checker/Inputs/main-test/module.map =================================================================== --- test/module-map-checker/Inputs/main-test/module.map +++ test/module-map-checker/Inputs/main-test/module.map @@ -0,0 +1,30 @@ +// module.map + +module Level1A { + header "Level1A.h" + export * +} +module Level1B { + header "Level1B.h" + export * + module Level2B { + header "Level2B.h" + export * + } +} +module Level2A { + header "Level2A.h" + export * +} +module UmbrellaDirectoryModule { + umbrella "UmbrellaSub" +} +module UmbrellaHeaderModule { + umbrella header "UmbrellaFile.h" +} +/* +module NoHeader { + header "NoHeader.h" + export * +} +*/ Index: test/module-map-checker/includes.module-map-checker =================================================================== --- test/module-map-checker/includes.module-map-checker +++ test/module-map-checker/includes.module-map-checker @@ -0,0 +1 @@ +# RUN: module-map-checker -I Includes1 -I Includes2 %S/Inputs/includes-test/module.map Index: test/module-map-checker/main-test.module-map-checker =================================================================== --- test/module-map-checker/main-test.module-map-checker +++ test/module-map-checker/main-test.module-map-checker @@ -0,0 +1,4 @@ +# RUN: not module-map-checker %S/Inputs/main-test/module.map 2>&1 | FileCheck %s + +# CHECK: warning: {{.*}}{{[/\\]}}Inputs/main-test/module.map does not account for file: {{.*}}{{[/\\]}}Inputs/main-test/Level3A.h +# CHECK-NEXT: warning: {{.*}}{{[/\\]}}Inputs/main-test/module.map does not account for file: {{.*}}{{[/\\]}}Inputs/main-test/Sub/Level3B.h