#include "clang/Basic/SourceManager.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "CoverageChecker.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "ModularizeUtilities.h"
using namespace clang;
using namespace llvm;
using namespace Modularize;
namespace {
class ModuleMapTargetOptions : public clang::TargetOptions {
public:
ModuleMapTargetOptions() { Triple = llvm::sys::getDefaultTargetTriple(); }
};
}
ModularizeUtilities::ModularizeUtilities(std::vector<std::string> &InputPaths,
llvm::StringRef Prefix,
llvm::StringRef ProblemFilesListPath)
: InputFilePaths(InputPaths), HeaderPrefix(Prefix),
ProblemFilesPath(ProblemFilesListPath), HasModuleMap(false),
MissingHeaderCount(0),
LangOpts(new LangOptions()), DiagIDs(new DiagnosticIDs()),
DiagnosticOpts(new DiagnosticOptions()),
DC(llvm::errs(), DiagnosticOpts.get()),
Diagnostics(
new DiagnosticsEngine(DiagIDs, DiagnosticOpts.get(), &DC, false)),
TargetOpts(new ModuleMapTargetOptions()),
Target(TargetInfo::CreateTargetInfo(*Diagnostics, TargetOpts)),
FileMgr(new FileManager(FileSystemOpts)),
SourceMgr(new SourceManager(*Diagnostics, *FileMgr, false)),
HeaderInfo(new HeaderSearch(std::make_shared<HeaderSearchOptions>(),
*SourceMgr, *Diagnostics, *LangOpts,
Target.get())) {}
ModularizeUtilities *ModularizeUtilities::createModularizeUtilities(
std::vector<std::string> &InputPaths, llvm::StringRef Prefix,
llvm::StringRef ProblemFilesListPath) {
return new ModularizeUtilities(InputPaths, Prefix, ProblemFilesListPath);
}
std::error_code ModularizeUtilities::loadAllHeaderListsAndDependencies() {
for (auto I = InputFilePaths.begin(), E = InputFilePaths.end(); I != E; ++I) {
llvm::StringRef InputPath = *I;
if (InputPath.ends_with(".modulemap")) {
if (std::error_code EC = loadModuleMap(InputPath))
return EC;
} else {
if (std::error_code EC = loadSingleHeaderListsAndDependencies(InputPath)) {
errs() << "modularize: error: Unable to get header list '" << InputPath
<< "': " << EC.message() << '\n';
return EC;
}
}
}
if (ProblemFilesPath.size() != 0) {
if (std::error_code EC = loadProblemHeaderList(ProblemFilesPath)) {
errs() << "modularize: error: Unable to get problem header list '" << ProblemFilesPath
<< "': " << EC.message() << '\n';
return EC;
}
}
return std::error_code();
}
std::error_code ModularizeUtilities::doCoverageCheck(
std::vector<std::string> &IncludePaths,
llvm::ArrayRef<std::string> CommandLine) {
int ModuleMapCount = ModuleMaps.size();
int ModuleMapIndex;
std::error_code EC;
for (ModuleMapIndex = 0; ModuleMapIndex < ModuleMapCount; ++ModuleMapIndex) {
std::unique_ptr<clang::ModuleMap> &ModMap = ModuleMaps[ModuleMapIndex];
auto Checker = CoverageChecker::createCoverageChecker(
InputFilePaths[ModuleMapIndex], IncludePaths, CommandLine,
ModMap.get());
std::error_code LocalEC = Checker->doChecks();
if (LocalEC.value() > 0)
EC = LocalEC;
}
return EC;
}
std::error_code ModularizeUtilities::loadSingleHeaderListsAndDependencies(
llvm::StringRef InputPath) {
SmallString<256> HeaderDirectory(InputPath);
llvm::sys::path::remove_filename(HeaderDirectory);
SmallString<256> CurrentDirectory;
llvm::sys::fs::current_path(CurrentDirectory);
if (HeaderPrefix.size() != 0)
HeaderDirectory = HeaderPrefix;
ErrorOr<std::unique_ptr<MemoryBuffer>> listBuffer =
MemoryBuffer::getFile(InputPath);
if (std::error_code EC = listBuffer.getError())
return EC;
SmallVector<StringRef, 32> Strings;
listBuffer.get()->getBuffer().split(Strings, "\n", -1, false);
for (SmallVectorImpl<StringRef>::iterator I = Strings.begin(),
E = Strings.end();
I != E; ++I) {
StringRef Line = I->trim();
if (Line.empty() || (Line[0] == '#'))
continue;
std::pair<StringRef, StringRef> TargetAndDependents = Line.split(':');
SmallString<256> HeaderFileName;
if (llvm::sys::path::is_absolute(TargetAndDependents.first))
llvm::sys::path::native(TargetAndDependents.first, HeaderFileName);
else {
if (HeaderDirectory.size() != 0)
HeaderFileName = HeaderDirectory;
else
HeaderFileName = CurrentDirectory;
llvm::sys::path::append(HeaderFileName, TargetAndDependents.first);
llvm::sys::path::native(HeaderFileName);
}
DependentsVector Dependents;
SmallVector<StringRef, 4> DependentsList;
TargetAndDependents.second.split(DependentsList, " ", -1, false);
int Count = DependentsList.size();
for (int Index = 0; Index < Count; ++Index) {
SmallString<256> Dependent;
if (llvm::sys::path::is_absolute(DependentsList[Index]))
Dependent = DependentsList[Index];
else {
if (HeaderDirectory.size() != 0)
Dependent = HeaderDirectory;
else
Dependent = CurrentDirectory;
llvm::sys::path::append(Dependent, DependentsList[Index]);
}
llvm::sys::path::native(Dependent);
Dependents.push_back(getCanonicalPath(Dependent.str()));
}
HeaderFileName = getCanonicalPath(HeaderFileName);
HeaderFileNames.push_back(std::string(HeaderFileName));
Dependencies[HeaderFileName.str()] = Dependents;
}
return std::error_code();
}
std::error_code ModularizeUtilities::loadProblemHeaderList(
llvm::StringRef InputPath) {
SmallString<256> HeaderDirectory(InputPath);
llvm::sys::path::remove_filename(HeaderDirectory);
SmallString<256> CurrentDirectory;
llvm::sys::fs::current_path(CurrentDirectory);
if (HeaderPrefix.size() != 0)
HeaderDirectory = HeaderPrefix;
ErrorOr<std::unique_ptr<MemoryBuffer>> listBuffer =
MemoryBuffer::getFile(InputPath);
if (std::error_code EC = listBuffer.getError())
return EC;
SmallVector<StringRef, 32> Strings;
listBuffer.get()->getBuffer().split(Strings, "\n", -1, false);
for (SmallVectorImpl<StringRef>::iterator I = Strings.begin(),
E = Strings.end();
I != E; ++I) {
StringRef Line = I->trim();
if (Line.empty() || (Line[0] == '#'))
continue;
SmallString<256> HeaderFileName;
if (llvm::sys::path::is_absolute(Line))
llvm::sys::path::native(Line, HeaderFileName);
else {
if (HeaderDirectory.size() != 0)
HeaderFileName = HeaderDirectory;
else
HeaderFileName = CurrentDirectory;
llvm::sys::path::append(HeaderFileName, Line);
llvm::sys::path::native(HeaderFileName);
}
HeaderFileName = getCanonicalPath(HeaderFileName);
ProblemFileNames.push_back(std::string(HeaderFileName));
}
return std::error_code();
}
std::error_code ModularizeUtilities::loadModuleMap(
llvm::StringRef InputPath) {
auto ModuleMapEntryOrErr = SourceMgr->getFileManager().getFileRef(InputPath);
if (!ModuleMapEntryOrErr) {
llvm::errs() << "error: File \"" << InputPath << "\" not found.\n";
return errorToErrorCode(ModuleMapEntryOrErr.takeError());
}
FileEntryRef ModuleMapEntry = *ModuleMapEntryOrErr;
DC.BeginSourceFile(*LangOpts, nullptr);
DirectoryEntryRef Dir = ModuleMapEntry.getDir();
StringRef DirName(Dir.getName());
if (llvm::sys::path::filename(DirName) == "Modules") {
DirName = llvm::sys::path::parent_path(DirName);
if (DirName.ends_with(".framework")) {
auto FrameworkDirOrErr = FileMgr->getDirectoryRef(DirName);
if (!FrameworkDirOrErr) {
return errorToErrorCode(FrameworkDirOrErr.takeError());
}
Dir = *FrameworkDirOrErr;
}
}
std::unique_ptr<ModuleMap> ModMap;
ModMap.reset(new ModuleMap(*SourceMgr, *Diagnostics, *LangOpts,
Target.get(), *HeaderInfo));
if (ModMap->parseModuleMapFile(ModuleMapEntry, false, Dir)) {
return std::error_code(1, std::generic_category());
}
DC.EndSourceFile();
MissingHeaderCount = 0;
if (!collectModuleMapHeaders(ModMap.get()))
return std::error_code(1, std::generic_category());
ModuleMaps.push_back(std::move(ModMap));
HasModuleMap = true;
if (MissingHeaderCount)
return std::error_code(1, std::generic_category());
return std::error_code();
}
bool ModularizeUtilities::collectModuleMapHeaders(clang::ModuleMap *ModMap) {
SmallVector<std::pair<StringRef, const clang::Module *>, 0> Vec;
for (auto &M : ModMap->modules())
Vec.emplace_back(M.first(), M.second);
llvm::sort(Vec, llvm::less_first());
for (auto &I : Vec)
if (!collectModuleHeaders(*I.second))
return false;
return true;
}
bool ModularizeUtilities::collectModuleHeaders(const clang::Module &Mod) {
if (Mod.IsExplicit)
return true;
DependentsVector UmbrellaDependents;
for (auto *Submodule : Mod.submodules())
collectModuleHeaders(*Submodule);
if (std::optional<clang::Module::Header> UmbrellaHeader =
Mod.getUmbrellaHeaderAsWritten()) {
std::string HeaderPath = getCanonicalPath(UmbrellaHeader->Entry.getName());
HeaderFileNames.push_back(HeaderPath);
} else if (std::optional<clang::Module::DirectoryName> UmbrellaDir =
Mod.getUmbrellaDirAsWritten()) {
if (Mod.Headers->size() == 0) {
if (!collectUmbrellaHeaders(UmbrellaDir->Entry.getName(),
UmbrellaDependents))
return false;
}
}
int NormalHeaderCount = Mod.Headers[clang::Module::HK_Normal].size();
for (int Index = 0; Index < NormalHeaderCount; ++Index) {
DependentsVector NormalDependents;
const clang::Module::Header &Header(
Mod.Headers[clang::Module::HK_Normal][Index]);
std::string HeaderPath = getCanonicalPath(Header.Entry.getName());
HeaderFileNames.push_back(HeaderPath);
}
int MissingCountThisModule = Mod.MissingHeaders.size();
for (int Index = 0; Index < MissingCountThisModule; ++Index) {
std::string MissingFile = Mod.MissingHeaders[Index].FileName;
SourceLocation Loc = Mod.MissingHeaders[Index].FileNameLoc;
errs() << Loc.printToString(*SourceMgr)
<< ": error : Header not found: " << MissingFile << "\n";
}
MissingHeaderCount += MissingCountThisModule;
return true;
}
bool ModularizeUtilities::collectUmbrellaHeaders(StringRef UmbrellaDirName,
DependentsVector &Dependents) {
SmallString<256> Directory(UmbrellaDirName);
std::error_code EC;
for (llvm::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<llvm::sys::fs::basic_file_status> Status = I->status();
if (!Status)
return false;
llvm::sys::fs::file_type Type = Status->type();
if (Type == llvm::sys::fs::file_type::directory_file) {
if (!collectUmbrellaHeaders(File, Dependents))
return false;
continue;
}
if (!isHeader(File))
continue;
std::string HeaderPath = getCanonicalPath(File);
Dependents.push_back(HeaderPath);
}
return true;
}
static std::string replaceDotDot(StringRef Path) {
SmallString<128> Buffer;
llvm::sys::path::const_iterator B = llvm::sys::path::begin(Path),
E = llvm::sys::path::end(Path);
while (B != E) {
if (*B == "..")
llvm::sys::path::remove_filename(Buffer);
else if (*B != ".")
llvm::sys::path::append(Buffer, *B);
++B;
}
if (Path.ends_with("/") || Path.ends_with("\\"))
Buffer.append(1, Path.back());
return Buffer.c_str();
}
std::string ModularizeUtilities::getCanonicalPath(StringRef FilePath) {
std::string Tmp(replaceDotDot(FilePath));
std::replace(Tmp.begin(), Tmp.end(), '\\', '/');
StringRef Tmp2(Tmp);
if (Tmp2.starts_with("./"))
Tmp = std::string(Tmp2.substr(2));
return Tmp;
}
bool ModularizeUtilities::isHeader(StringRef FileName) {
StringRef Extension = llvm::sys::path::extension(FileName);
if (Extension.size() == 0)
return true;
if (Extension.equals_insensitive(".h"))
return true;
if (Extension.equals_insensitive(".inc"))
return true;
return false;
}
std::string ModularizeUtilities::getDirectoryFromPath(StringRef Path) {
SmallString<256> Directory(Path);
sys::path::remove_filename(Directory);
if (Directory.size() == 0)
return ".";
return std::string(Directory);
}
void ModularizeUtilities::addUniqueProblemFile(std::string FilePath) {
FilePath = getCanonicalPath(FilePath);
for(auto &TestFilePath : ProblemFileNames) {
if (TestFilePath == FilePath)
return;
}
ProblemFileNames.push_back(FilePath);
}
void ModularizeUtilities::addNoCompileErrorsFile(std::string FilePath) {
FilePath = getCanonicalPath(FilePath);
GoodFileNames.push_back(FilePath);
}
void ModularizeUtilities::displayProblemFiles() {
errs() << "\nThese are the files with possible errors:\n\n";
for (auto &ProblemFile : ProblemFileNames) {
errs() << ProblemFile << "\n";
}
}
void ModularizeUtilities::displayGoodFiles() {
errs() << "\nThese are the files with no detected errors:\n\n";
for (auto &GoodFile : HeaderFileNames) {
bool Good = true;
for (auto &ProblemFile : ProblemFileNames) {
if (ProblemFile == GoodFile) {
Good = false;
break;
}
}
if (Good)
errs() << GoodFile << "\n";
}
}
void ModularizeUtilities::displayCombinedFiles() {
errs() <<
"\nThese are the combined files, with problem files preceded by #:\n\n";
for (auto &File : HeaderFileNames) {
bool Good = true;
for (auto &ProblemFile : ProblemFileNames) {
if (ProblemFile == File) {
Good = false;
break;
}
}
errs() << (Good ? "" : "#") << File << "\n";
}
}