#include "clang/Lex/HeaderSearch.h"
#include "HeaderMapTestUtils.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Serialization/InMemoryModuleCache.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gtest/gtest.h"
#include <memory>
#include <string>
namespace clang {
namespace {
class HeaderSearchTest : public ::testing::Test {
protected:
HeaderSearchTest()
: VFS(new llvm::vfs::InMemoryFileSystem), FileMgr(FileMgrOpts, VFS),
DiagID(new DiagnosticIDs()),
Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions),
Search(std::make_shared<HeaderSearchOptions>(), SourceMgr, Diags,
LangOpts, Target.get()) {
TargetOpts->Triple = "x86_64-apple-darwin11.1.0";
Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts);
}
void addSearchDir(llvm::StringRef Dir) {
VFS->addFile(
Dir, 0, llvm::MemoryBuffer::getMemBuffer(""), std::nullopt,
std::nullopt, llvm::sys::fs::file_type::directory_file);
auto DE = FileMgr.getOptionalDirectoryRef(Dir);
assert(DE);
auto DL = DirectoryLookup(*DE, SrcMgr::C_User, false);
Search.AddSearchPath(DL, false);
}
void addFrameworkSearchDir(llvm::StringRef Dir, bool IsSystem = true) {
VFS->addFile(
Dir, 0, llvm::MemoryBuffer::getMemBuffer(""), std::nullopt,
std::nullopt, llvm::sys::fs::file_type::directory_file);
auto DE = FileMgr.getOptionalDirectoryRef(Dir);
assert(DE);
auto DL = DirectoryLookup(*DE, IsSystem ? SrcMgr::C_System : SrcMgr::C_User,
true);
if (IsSystem)
Search.AddSystemSearchPath(DL);
else
Search.AddSearchPath(DL, true);
}
void addHeaderMap(llvm::StringRef Filename,
std::unique_ptr<llvm::MemoryBuffer> Buf,
bool isAngled = false) {
VFS->addFile(Filename, 0, std::move(Buf), std::nullopt,
std::nullopt,
llvm::sys::fs::file_type::regular_file);
auto FE = FileMgr.getOptionalFileRef(Filename, true);
assert(FE);
assert(!HMap);
HMap = HeaderMap::Create(*FE, FileMgr);
auto DL =
DirectoryLookup(HMap.get(), SrcMgr::C_User, false);
Search.AddSearchPath(DL, isAngled);
}
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> VFS;
FileSystemOptions FileMgrOpts;
FileManager FileMgr;
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
DiagnosticsEngine Diags;
SourceManager SourceMgr;
LangOptions LangOpts;
std::shared_ptr<TargetOptions> TargetOpts;
IntrusiveRefCntPtr<TargetInfo> Target;
HeaderSearch Search;
std::unique_ptr<HeaderMap> HMap;
};
TEST_F(HeaderSearchTest, NoSearchDir) {
EXPECT_EQ(Search.search_dir_size(), 0u);
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", "",
""),
"/x/y/z");
}
TEST_F(HeaderSearchTest, SimpleShorten) {
addSearchDir("/x");
addSearchDir("/x/y");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", "",
""),
"z");
addSearchDir("/a/b/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c", "",
""),
"c");
}
TEST_F(HeaderSearchTest, ShortenWithWorkingDir) {
addSearchDir("x/y");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c/x/y/z",
"/a/b/c",
""),
"z");
}
TEST_F(HeaderSearchTest, Dots) {
addSearchDir("/x/./y/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/./z",
"",
""),
"z");
addSearchDir("a/.././c/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/m/n/./c/z",
"/m/n/",
""),
"z");
}
TEST_F(HeaderSearchTest, RelativeDirs) {
ASSERT_FALSE(VFS->setCurrentWorkingDirectory("/root/some/dir"));
addSearchDir("..");
EXPECT_EQ(
Search.suggestPathToFileForDiagnostics("/root/some/foo.h",
"/root/some/dir",
""),
"foo.h");
EXPECT_EQ(
Search.suggestPathToFileForDiagnostics("../foo.h",
"/root/some/dir",
""),
"foo.h");
}
#ifdef _WIN32
TEST_F(HeaderSearchTest, BackSlash) {
addSearchDir("C:\\x\\y\\");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
"",
""),
"z/t");
}
TEST_F(HeaderSearchTest, BackSlashWithDotDot) {
addSearchDir("..\\y");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
"C:/x/y/",
""),
"z/t");
}
#endif
TEST_F(HeaderSearchTest, DotDotsWithAbsPath) {
addSearchDir("/x/../y/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z",
"",
""),
"z");
}
TEST_F(HeaderSearchTest, BothDotDots) {
addSearchDir("/x/../y/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/../y/z",
"",
""),
"z");
}
TEST_F(HeaderSearchTest, IncludeFromSameDirectory) {
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",
"",
"/y/a.cc"),
"z/t.h");
addSearchDir("/");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",
"",
"/y/a.cc"),
"y/z/t.h");
}
TEST_F(HeaderSearchTest, SdkFramework) {
addFrameworkSearchDir(
"/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/Frameworks/");
bool IsAngled = false;
EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
"/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
"Frameworks/AppKit.framework/Headers/NSView.h",
"",
"", &IsAngled),
"AppKit/NSView.h");
EXPECT_TRUE(IsAngled);
addFrameworkSearchDir("/System/Developer/Library/Framworks/",
false);
EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
"/System/Developer/Library/Framworks/"
"Foo.framework/Headers/Foo.h",
"",
"", &IsAngled),
"Foo/Foo.h");
EXPECT_TRUE(IsAngled);
}
TEST_F(HeaderSearchTest, NestedFramework) {
addFrameworkSearchDir("/Platforms/MacOSX/Frameworks");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
"/Platforms/MacOSX/Frameworks/AppKit.framework/Frameworks/"
"Sub.framework/Headers/Sub.h",
"",
""),
"Sub/Sub.h");
}
TEST_F(HeaderSearchTest, HeaderFrameworkLookup) {
std::string HeaderPath = "/tmp/Frameworks/Foo.framework/Headers/Foo.h";
addFrameworkSearchDir("/tmp/Frameworks");
VFS->addFile(HeaderPath, 0,
llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath),
std::nullopt, std::nullopt,
llvm::sys::fs::file_type::regular_file);
bool IsFrameworkFound = false;
auto FoundFile = Search.LookupFile(
"Foo/Foo.h", SourceLocation(), true, nullptr,
nullptr, {}, nullptr,
nullptr, nullptr,
nullptr, nullptr, &IsFrameworkFound);
EXPECT_TRUE(FoundFile.has_value());
EXPECT_TRUE(IsFrameworkFound);
auto &FE = *FoundFile;
auto FI = Search.getExistingFileInfo(FE);
EXPECT_TRUE(FI);
EXPECT_TRUE(FI->IsValid);
EXPECT_EQ(FI->Framework.str(), "Foo");
EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");
}
template <class FileTy, class PaddingTy>
struct NullTerminatedFile : public FileTy {
PaddingTy Padding = 0;
};
TEST_F(HeaderSearchTest, HeaderMapReverseLookup) {
typedef NullTerminatedFile<test::HMapFileMock<2, 32>, char> FileTy;
FileTy File;
File.init();
test::HMapFileMockMaker<FileTy> Maker(File);
auto a = Maker.addString("d.h");
auto b = Maker.addString("b/");
auto c = Maker.addString("c.h");
Maker.addBucket("d.h", a, b, c);
addHeaderMap("/x/y/z.hmap", File.getBuffer());
addSearchDir("/a");
EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c.h",
"",
""),
"d.h");
}
TEST_F(HeaderSearchTest, HeaderMapFrameworkLookup) {
typedef NullTerminatedFile<test::HMapFileMock<4, 128>, char> FileTy;
FileTy File;
File.init();
std::string HeaderDirName = "/tmp/Sources/Foo/Headers/";
std::string HeaderName = "Foo.h";
if (is_style_windows(llvm::sys::path::Style::native)) {
HeaderDirName = "C:" + HeaderDirName;
}
test::HMapFileMockMaker<FileTy> Maker(File);
auto a = Maker.addString("Foo/Foo.h");
auto b = Maker.addString(HeaderDirName);
auto c = Maker.addString(HeaderName);
Maker.addBucket("Foo/Foo.h", a, b, c);
addHeaderMap("product-headers.hmap", File.getBuffer(), true);
VFS->addFile(
HeaderDirName + HeaderName, 0,
llvm::MemoryBuffer::getMemBufferCopy("", HeaderDirName + HeaderName),
std::nullopt, std::nullopt,
llvm::sys::fs::file_type::regular_file);
bool IsMapped = false;
auto FoundFile = Search.LookupFile(
"Foo/Foo.h", SourceLocation(), true, nullptr,
nullptr, {}, nullptr,
nullptr, nullptr,
nullptr, &IsMapped,
nullptr);
EXPECT_TRUE(FoundFile.has_value());
EXPECT_TRUE(IsMapped);
auto &FE = *FoundFile;
auto FI = Search.getExistingFileInfo(FE);
EXPECT_TRUE(FI);
EXPECT_TRUE(FI->IsValid);
EXPECT_EQ(FI->Framework.str(), "Foo");
EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");
}
TEST_F(HeaderSearchTest, HeaderFileInfoMerge) {
auto AddHeader = [&](std::string HeaderPath) -> FileEntryRef {
VFS->addFile(HeaderPath, 0,
llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath),
std::nullopt, std::nullopt,
llvm::sys::fs::file_type::regular_file);
return *FileMgr.getOptionalFileRef(HeaderPath);
};
class MockExternalHeaderFileInfoSource : public ExternalHeaderFileInfoSource {
HeaderFileInfo GetHeaderFileInfo(FileEntryRef FE) {
HeaderFileInfo HFI;
auto FileName = FE.getName();
if (FileName == ModularPath)
HFI.mergeModuleMembership(ModuleMap::NormalHeader);
else if (FileName == TextualPath)
HFI.mergeModuleMembership(ModuleMap::TextualHeader);
HFI.External = true;
HFI.IsValid = true;
return HFI;
}
public:
std::string ModularPath = "/modular.h";
std::string TextualPath = "/textual.h";
};
auto ExternalSource = std::make_unique<MockExternalHeaderFileInfoSource>();
Search.SetExternalSource(ExternalSource.get());
auto ModularFE = AddHeader(ExternalSource->ModularPath);
auto TextualFE = AddHeader(ExternalSource->TextualPath);
EXPECT_TRUE(Search.getExistingFileInfo(ModularFE)->External);
EXPECT_TRUE(Search.getExistingFileInfo(TextualFE)->External);
Search.MarkFileModuleHeader(ModularFE, ModuleMap::NormalHeader,
false);
Search.MarkFileModuleHeader(TextualFE, ModuleMap::TextualHeader,
false);
EXPECT_TRUE(Search.getExistingFileInfo(ModularFE)->External);
EXPECT_TRUE(Search.getExistingFileInfo(TextualFE)->External);
Search.MarkFileModuleHeader(ModularFE, ModuleMap::TextualHeader,
false);
Search.MarkFileModuleHeader(TextualFE, ModuleMap::NormalHeader,
false);
auto ModularFI = Search.getExistingFileInfo(ModularFE);
auto TextualFI = Search.getExistingFileInfo(TextualFE);
EXPECT_TRUE(ModularFI->External);
EXPECT_TRUE(ModularFI->isModuleHeader);
EXPECT_FALSE(ModularFI->isTextualModuleHeader);
EXPECT_FALSE(TextualFI->External);
EXPECT_TRUE(TextualFI->isModuleHeader);
EXPECT_FALSE(TextualFI->isTextualModuleHeader);
Search.MarkFileModuleHeader(ModularFE, ModuleMap::NormalHeader,
true);
Search.MarkFileModuleHeader(TextualFE, ModuleMap::NormalHeader,
true);
EXPECT_FALSE(Search.getExistingFileInfo(ModularFE)->External);
EXPECT_FALSE(Search.getExistingFileInfo(TextualFE)->External);
}
}
}