brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.9 KiB · d80d78c Raw
421 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements a class that validates a module map by checking that10// 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 include14// paths, if specified, it will collect the names of all the files it15// considers headers (no extension, .h, or .inc--if you need more, modify the16// ModularizeUtilities::isHeader function).17//  It then compares the headers against those referenced18// in the module map, either explicitly named, or implicitly named via an19// umbrella directory or umbrella file, as parsed by the ModuleMap object.20// If headers are found which are not referenced or covered by an umbrella21// directory or file, warning messages will be produced, and the doChecks22// function will return an error code of 1.  Other errors result in an error23// 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 compiler26// to preprocess the file, and uses a callback to collect the header files27// included by the umbrella header or any of its nested includes.  If any28// front end options are needed for these compiler invocations, these are29// 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.h34//35// Note that for the case of the module map referencing a file that does36// not exist, the module map parser in Clang will (at the time of this37// 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 the42//    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 "CoverageChecker.h"54#include "ModularizeUtilities.h"55#include "clang/AST/ASTConsumer.h"56#include "clang/AST/ASTContext.h"57#include "clang/AST/RecursiveASTVisitor.h"58#include "clang/Basic/SourceManager.h"59#include "clang/Frontend/CompilerInstance.h"60#include "clang/Frontend/FrontendAction.h"61#include "clang/Frontend/FrontendActions.h"62#include "clang/Lex/PPCallbacks.h"63#include "clang/Lex/Preprocessor.h"64#include "clang/Options/Options.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 73using namespace Modularize;74using namespace clang;75using namespace clang::driver;76using namespace clang::options;77using namespace clang::tooling;78namespace cl = llvm::cl;79namespace sys = llvm::sys;80 81// Preprocessor callbacks.82// We basically just collect include files.83class CoverageCheckerCallbacks : public PPCallbacks {84public: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 99private:100  CoverageChecker &Checker;101};102 103// Frontend action stuff:104 105// Consumer is responsible for setting up the callbacks.106class CoverageCheckerConsumer : public ASTConsumer {107public:108  CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) {109    // PP takes ownership.110    PP.addPPCallbacks(std::make_unique<CoverageCheckerCallbacks>(Checker));111  }112};113 114class CoverageCheckerAction : public SyntaxOnlyAction {115public:116  CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {}117 118protected:119  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,120    StringRef InFile) override {121    return std::make_unique<CoverageCheckerConsumer>(Checker,122      CI.getPreprocessor());123  }124 125private:126  CoverageChecker &Checker;127};128 129class CoverageCheckerFrontendActionFactory : public FrontendActionFactory {130public:131  CoverageCheckerFrontendActionFactory(CoverageChecker &Checker)132    : Checker(Checker) {}133 134  std::unique_ptr<FrontendAction> create() override {135    return std::make_unique<CoverageCheckerAction>(Checker);136  }137 138private:139  CoverageChecker &Checker;140};141 142// CoverageChecker class implementation.143 144// Constructor.145CoverageChecker::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 up154// subordinate objects.155std::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 files166// covered by the include path options, and compare against167// 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 there170//   were warnings, 2 if any other problem, such as if a bad171//   module map path argument was specified.172std::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 into196// ModuleMapHeadersSet.197void 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 into207// ModuleMapHeadersSet.208// FIXME: Doesn't collect files from umbrella header.209bool 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.237bool 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.271bool272CoverageChecker::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 included296// from an umbrella header.297void 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 map304  // 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 by315// the include path options.316// Returns true if no errors.317bool 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 starting324  // 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 the331    // include paths.332    for (const std::string &IncludePath : IncludePaths) {333      if (!collectFileSystemHeaders(IncludePath))334        return false;335    }336  }337 338  // Sort it, because different file systems might order the file differently.339  llvm::sort(FileSystemHeaders);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 be347// relative to the directory of the module.modulemap file.348// \returns True if no errors.349bool 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.starts_with("/") || IncludePath.starts_with("\\") ||358      ((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) {359    llvm::errs() << "error: Include path \"" << IncludePath360      << "\" 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  int Count = 0;367  for (sys::fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E;368    I.increment(EC)) {369    if (EC)370      return false;371    //std::string file(I->path());372    StringRef file(I->path());373    llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status();374    if (!Status)375      return false;376    sys::fs::file_type type = Status->type();377    // If the file is a directory, ignore the name (but still recurses).378    if (type == sys::fs::file_type::directory_file)379      continue;380    // Assume directories or files starting with '.' are private and not to381    // be considered.382    if (file.contains("\\.") || file.contains("/."))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 files400// against those referenced in the module map.  Display401// 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 canonalization405// of file names so that header path can be correctly406// matched.  Also, a map could be used for the headers407// referenced in the module, but408void 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: " << ModuleMapPath417        << " does not account for file: " << *I << "\n";418    }419  }420}421