#include "ModularizeUtilities.h"
#include "clang/AST/ASTConsumer.h"
#include "CoverageChecker.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/FrontendAction.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"
using namespace Modularize;
using namespace clang;
using namespace clang::driver;
using namespace clang::driver::options;
using namespace clang::tooling;
namespace cl = llvm::cl;
namespace sys = llvm::sys;
class CoverageCheckerCallbacks : public PPCallbacks {
public:
CoverageCheckerCallbacks(CoverageChecker &Checker) : Checker(Checker) {}
~CoverageCheckerCallbacks() override {}
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange,
OptionalFileEntryRef File, StringRef SearchPath,
StringRef RelativePath, const Module *SuggestedModule,
bool ModuleImported,
SrcMgr::CharacteristicKind FileType) override {
Checker.collectUmbrellaHeaderHeader(File->getName());
}
private:
CoverageChecker &Checker;
};
class CoverageCheckerConsumer : public ASTConsumer {
public:
CoverageCheckerConsumer(CoverageChecker &Checker, Preprocessor &PP) {
PP.addPPCallbacks(std::make_unique<CoverageCheckerCallbacks>(Checker));
}
};
class CoverageCheckerAction : public SyntaxOnlyAction {
public:
CoverageCheckerAction(CoverageChecker &Checker) : Checker(Checker) {}
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override {
return std::make_unique<CoverageCheckerConsumer>(Checker,
CI.getPreprocessor());
}
private:
CoverageChecker &Checker;
};
class CoverageCheckerFrontendActionFactory : public FrontendActionFactory {
public:
CoverageCheckerFrontendActionFactory(CoverageChecker &Checker)
: Checker(Checker) {}
std::unique_ptr<FrontendAction> create() override {
return std::make_unique<CoverageCheckerAction>(Checker);
}
private:
CoverageChecker &Checker;
};
CoverageChecker::CoverageChecker(StringRef ModuleMapPath,
std::vector<std::string> &IncludePaths,
ArrayRef<std::string> CommandLine,
clang::ModuleMap *ModuleMap)
: ModuleMapPath(ModuleMapPath), IncludePaths(IncludePaths),
CommandLine(CommandLine),
ModMap(ModuleMap) {}
std::unique_ptr<CoverageChecker> CoverageChecker::createCoverageChecker(
StringRef ModuleMapPath, std::vector<std::string> &IncludePaths,
ArrayRef<std::string> CommandLine, clang::ModuleMap *ModuleMap) {
return std::make_unique<CoverageChecker>(ModuleMapPath, IncludePaths,
CommandLine, ModuleMap);
}
std::error_code CoverageChecker::doChecks() {
std::error_code returnValue;
collectModuleHeaders();
if (!collectFileSystemHeaders())
return std::error_code(2, std::generic_category());
findUnaccountedForHeaders();
if (!UnaccountedForHeaders.empty())
returnValue = std::error_code(1, std::generic_category());
return returnValue;
}
void CoverageChecker::collectModuleHeaders() {
for (ModuleMap::module_iterator I = ModMap->module_begin(),
E = ModMap->module_end();
I != E; ++I) {
collectModuleHeaders(*I->second);
}
}
bool CoverageChecker::collectModuleHeaders(const Module &Mod) {
if (std::optional<Module::Header> UmbrellaHeader =
Mod.getUmbrellaHeaderAsWritten()) {
ModuleMapHeadersSet.insert(
ModularizeUtilities::getCanonicalPath(UmbrellaHeader->Entry.getName()));
if (!collectUmbrellaHeaderHeaders(UmbrellaHeader->Entry.getName()))
return false;
} else if (std::optional<Module::DirectoryName> UmbrellaDir =
Mod.getUmbrellaDirAsWritten()) {
if (!collectUmbrellaHeaders(UmbrellaDir->Entry.getName()))
return false;
}
for (auto &HeaderKind : Mod.Headers)
for (auto &Header : HeaderKind)
ModuleMapHeadersSet.insert(
ModularizeUtilities::getCanonicalPath(Header.Entry.getName()));
for (auto *Submodule : Mod.submodules())
collectModuleHeaders(*Submodule);
return true;
}
bool CoverageChecker::collectUmbrellaHeaders(StringRef UmbrellaDirName) {
SmallString<256> Directory(ModuleMapDirectory);
if (UmbrellaDirName.size())
sys::path::append(Directory, UmbrellaDirName);
if (Directory.size() == 0)
Directory = ".";
std::error_code EC;
for (sys::fs::directory_iterator I(Directory.str(), EC), E; I != E;
I.increment(EC)) {
if (EC)
return false;
std::string File(I->path());
llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status();
if (!Status)
return false;
sys::fs::file_type Type = Status->type();
if (Type == sys::fs::file_type::directory_file) {
if (!collectUmbrellaHeaders(File))
return false;
continue;
}
if (!ModularizeUtilities::isHeader(File))
continue;
ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(File));
}
return true;
}
bool
CoverageChecker::collectUmbrellaHeaderHeaders(StringRef UmbrellaHeaderName) {
SmallString<256> PathBuf(ModuleMapDirectory);
if (ModuleMapDirectory.length() == 0)
sys::fs::current_path(PathBuf);
std::unique_ptr<CompilationDatabase> Compilations;
Compilations.reset(new FixedCompilationDatabase(Twine(PathBuf), CommandLine));
std::vector<std::string> HeaderPath;
HeaderPath.push_back(std::string(UmbrellaHeaderName));
ClangTool Tool(*Compilations, HeaderPath);
int HadErrors = Tool.run(new CoverageCheckerFrontendActionFactory(*this));
return !HadErrors;
}
void CoverageChecker::collectUmbrellaHeaderHeader(StringRef HeaderName) {
SmallString<256> PathBuf(ModuleMapDirectory);
if (ModuleMapDirectory.length() == 0)
sys::fs::current_path(PathBuf);
if (HeaderName.starts_with(PathBuf))
HeaderName = HeaderName.substr(PathBuf.size() + 1);
ModuleMapHeadersSet.insert(ModularizeUtilities::getCanonicalPath(HeaderName));
}
bool CoverageChecker::collectFileSystemHeaders() {
ModuleMapDirectory = ModularizeUtilities::getDirectoryFromPath(ModuleMapPath);
if (IncludePaths.size() == 0) {
if (!collectFileSystemHeaders(StringRef("")))
return false;
}
else {
for (std::vector<std::string>::const_iterator I = IncludePaths.begin(),
E = IncludePaths.end();
I != E; ++I) {
if (!collectFileSystemHeaders(*I))
return false;
}
}
llvm::sort(FileSystemHeaders);
return true;
}
bool CoverageChecker::collectFileSystemHeaders(StringRef IncludePath) {
SmallString<256> Directory(ModuleMapDirectory);
if (IncludePath.size())
sys::path::append(Directory, IncludePath);
if (Directory.size() == 0)
Directory = ".";
if (IncludePath.starts_with("/") || IncludePath.starts_with("\\") ||
((IncludePath.size() >= 2) && (IncludePath[1] == ':'))) {
llvm::errs() << "error: Include path \"" << IncludePath
<< "\" is not relative to the module map file.\n";
return false;
}
std::error_code EC;
int Count = 0;
for (sys::fs::recursive_directory_iterator I(Directory.str(), EC), E; I != E;
I.increment(EC)) {
if (EC)
return false;
StringRef file(I->path());
llvm::ErrorOr<sys::fs::basic_file_status> Status = I->status();
if (!Status)
return false;
sys::fs::file_type type = Status->type();
if (type == sys::fs::file_type::directory_file)
continue;
if (file.contains("\\.") || file.contains("/."))
continue;
if (!ModularizeUtilities::isHeader(file))
continue;
FileSystemHeaders.push_back(ModularizeUtilities::getCanonicalPath(file));
Count++;
}
if (Count == 0) {
llvm::errs() << "warning: No headers found in include path: \""
<< IncludePath << "\"\n";
}
return true;
}
void CoverageChecker::findUnaccountedForHeaders() {
for (std::vector<std::string>::const_iterator I = FileSystemHeaders.begin(),
E = FileSystemHeaders.end();
I != E; ++I) {
if (ModuleMapHeadersSet.insert(*I).second) {
UnaccountedForHeaders.push_back(*I);
llvm::errs() << "warning: " << ModuleMapPath
<< " does not account for file: " << *I << "\n";
}
}
}