394 lines · cpp
1//===- unittests/Lex/HeaderSearchTest.cpp ------ HeaderSearch tests -------===//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#include "clang/Lex/HeaderSearch.h"10#include "HeaderMapTestUtils.h"11#include "clang/Basic/Diagnostic.h"12#include "clang/Basic/DiagnosticOptions.h"13#include "clang/Basic/FileManager.h"14#include "clang/Basic/LangOptions.h"15#include "clang/Basic/SourceManager.h"16#include "clang/Basic/TargetInfo.h"17#include "clang/Basic/TargetOptions.h"18#include "clang/Lex/HeaderSearchOptions.h"19#include "llvm/Support/MemoryBuffer.h"20#include "gtest/gtest.h"21#include <memory>22#include <string>23 24namespace clang {25namespace {26 27// The test fixture.28class HeaderSearchTest : public ::testing::Test {29protected:30 HeaderSearchTest()31 : VFS(llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>()),32 FileMgr(FileMgrOpts, VFS),33 Diags(DiagnosticIDs::create(), DiagOpts, new IgnoringDiagConsumer()),34 SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions),35 Search(HSOpts, SourceMgr, Diags, LangOpts, Target.get()) {36 TargetOpts->Triple = "x86_64-apple-darwin11.1.0";37 Target = TargetInfo::CreateTargetInfo(Diags, *TargetOpts);38 }39 40 void addSearchDir(llvm::StringRef Dir) {41 VFS->addFile(42 Dir, 0, llvm::MemoryBuffer::getMemBuffer(""), /*User=*/std::nullopt,43 /*Group=*/std::nullopt, llvm::sys::fs::file_type::directory_file);44 auto DE = FileMgr.getOptionalDirectoryRef(Dir);45 assert(DE);46 auto DL = DirectoryLookup(*DE, SrcMgr::C_User, /*isFramework=*/false);47 Search.AddSearchPath(DL, /*isAngled=*/false);48 }49 50 void addFrameworkSearchDir(llvm::StringRef Dir, bool IsSystem = true) {51 VFS->addFile(52 Dir, 0, llvm::MemoryBuffer::getMemBuffer(""), /*User=*/std::nullopt,53 /*Group=*/std::nullopt, llvm::sys::fs::file_type::directory_file);54 auto DE = FileMgr.getOptionalDirectoryRef(Dir);55 assert(DE);56 auto DL = DirectoryLookup(*DE, IsSystem ? SrcMgr::C_System : SrcMgr::C_User,57 /*isFramework=*/true);58 if (IsSystem)59 Search.AddSystemSearchPath(DL);60 else61 Search.AddSearchPath(DL, /*isAngled=*/true);62 }63 64 void addHeaderMap(llvm::StringRef Filename,65 std::unique_ptr<llvm::MemoryBuffer> Buf,66 bool isAngled = false) {67 VFS->addFile(Filename, 0, std::move(Buf), /*User=*/std::nullopt,68 /*Group=*/std::nullopt,69 llvm::sys::fs::file_type::regular_file);70 auto FE = FileMgr.getOptionalFileRef(Filename, true);71 assert(FE);72 73 // Test class supports only one HMap at a time.74 assert(!HMap);75 HMap = HeaderMap::Create(*FE, FileMgr);76 auto DL = DirectoryLookup(HMap.get(), SrcMgr::C_User);77 Search.AddSearchPath(DL, isAngled);78 }79 80 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> VFS;81 FileSystemOptions FileMgrOpts;82 FileManager FileMgr;83 DiagnosticOptions DiagOpts;84 DiagnosticsEngine Diags;85 SourceManager SourceMgr;86 LangOptions LangOpts;87 std::shared_ptr<TargetOptions> TargetOpts;88 IntrusiveRefCntPtr<TargetInfo> Target;89 HeaderSearchOptions HSOpts;90 HeaderSearch Search;91 std::unique_ptr<HeaderMap> HMap;92};93 94TEST_F(HeaderSearchTest, NoSearchDir) {95 EXPECT_EQ(Search.search_dir_size(), 0u);96 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",97 /*MainFile=*/""),98 "/x/y/z");99}100 101TEST_F(HeaderSearchTest, SimpleShorten) {102 addSearchDir("/x");103 addSearchDir("/x/y");104 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",105 /*MainFile=*/""),106 "z");107 addSearchDir("/a/b/");108 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c", /*WorkingDir=*/"",109 /*MainFile=*/""),110 "c");111}112 113TEST_F(HeaderSearchTest, ShortenWithWorkingDir) {114 addSearchDir("x/y");115 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c/x/y/z",116 /*WorkingDir=*/"/a/b/c",117 /*MainFile=*/""),118 "z");119}120 121TEST_F(HeaderSearchTest, Dots) {122 addSearchDir("/x/./y/");123 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/./z",124 /*WorkingDir=*/"",125 /*MainFile=*/""),126 "z");127 addSearchDir("a/.././c/");128 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/m/n/./c/z",129 /*WorkingDir=*/"/m/n/",130 /*MainFile=*/""),131 "z");132}133 134TEST_F(HeaderSearchTest, RelativeDirs) {135 ASSERT_FALSE(VFS->setCurrentWorkingDirectory("/root/some/dir"));136 addSearchDir("..");137 EXPECT_EQ(138 Search.suggestPathToFileForDiagnostics("/root/some/foo.h",139 /*WorkingDir=*/"/root/some/dir",140 /*MainFile=*/""),141 "foo.h");142 EXPECT_EQ(143 Search.suggestPathToFileForDiagnostics("../foo.h",144 /*WorkingDir=*/"/root/some/dir",145 /*MainFile=*/""),146 "foo.h");147}148 149#ifdef _WIN32150TEST_F(HeaderSearchTest, BackSlash) {151 addSearchDir("C:\\x\\y\\");152 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",153 /*WorkingDir=*/"",154 /*MainFile=*/""),155 "z/t");156}157 158TEST_F(HeaderSearchTest, BackSlashWithDotDot) {159 addSearchDir("..\\y");160 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",161 /*WorkingDir=*/"C:/x/y/",162 /*MainFile=*/""),163 "z/t");164}165#endif166 167TEST_F(HeaderSearchTest, DotDotsWithAbsPath) {168 addSearchDir("/x/../y/");169 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z",170 /*WorkingDir=*/"",171 /*MainFile=*/""),172 "z");173}174 175TEST_F(HeaderSearchTest, BothDotDots) {176 addSearchDir("/x/../y/");177 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/../y/z",178 /*WorkingDir=*/"",179 /*MainFile=*/""),180 "z");181}182 183TEST_F(HeaderSearchTest, IncludeFromSameDirectory) {184 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",185 /*WorkingDir=*/"",186 /*MainFile=*/"/y/a.cc"),187 "z/t.h");188 189 addSearchDir("/");190 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",191 /*WorkingDir=*/"",192 /*MainFile=*/"/y/a.cc"),193 "y/z/t.h");194}195 196TEST_F(HeaderSearchTest, SdkFramework) {197 addFrameworkSearchDir(198 "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/Frameworks/");199 bool IsAngled = false;200 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(201 "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"202 "Frameworks/AppKit.framework/Headers/NSView.h",203 /*WorkingDir=*/"",204 /*MainFile=*/"", &IsAngled),205 "AppKit/NSView.h");206 EXPECT_TRUE(IsAngled);207 208 addFrameworkSearchDir("/System/Developer/Library/Framworks/",209 /*IsSystem*/ false);210 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(211 "/System/Developer/Library/Framworks/"212 "Foo.framework/Headers/Foo.h",213 /*WorkingDir=*/"",214 /*MainFile=*/"", &IsAngled),215 "Foo/Foo.h");216 // Expect to be true even though we passed false to IsSystem earlier since217 // all frameworks should be treated as <>.218 EXPECT_TRUE(IsAngled);219}220 221TEST_F(HeaderSearchTest, NestedFramework) {222 addFrameworkSearchDir("/Platforms/MacOSX/Frameworks");223 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(224 "/Platforms/MacOSX/Frameworks/AppKit.framework/Frameworks/"225 "Sub.framework/Headers/Sub.h",226 /*WorkingDir=*/"",227 /*MainFile=*/""),228 "Sub/Sub.h");229}230 231TEST_F(HeaderSearchTest, HeaderFrameworkLookup) {232 std::string HeaderPath = "/tmp/Frameworks/Foo.framework/Headers/Foo.h";233 addFrameworkSearchDir("/tmp/Frameworks");234 VFS->addFile(HeaderPath, 0,235 llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath),236 /*User=*/std::nullopt, /*Group=*/std::nullopt,237 llvm::sys::fs::file_type::regular_file);238 239 bool IsFrameworkFound = false;240 auto FoundFile = Search.LookupFile(241 "Foo/Foo.h", SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,242 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,243 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,244 /*SuggestedModule=*/nullptr, /*IsMapped=*/nullptr, &IsFrameworkFound);245 246 EXPECT_TRUE(FoundFile.has_value());247 EXPECT_TRUE(IsFrameworkFound);248 auto &FE = *FoundFile;249 auto FI = Search.getExistingFileInfo(FE);250 EXPECT_TRUE(FI);251 EXPECT_TRUE(FI->IsValid);252 EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");253}254 255// Helper struct with null terminator character to make MemoryBuffer happy.256template <class FileTy, class PaddingTy>257struct NullTerminatedFile : public FileTy {258 PaddingTy Padding = 0;259};260 261TEST_F(HeaderSearchTest, HeaderMapReverseLookup) {262 typedef NullTerminatedFile<test::HMapFileMock<2, 32>, char> FileTy;263 FileTy File;264 File.init();265 266 test::HMapFileMockMaker<FileTy> Maker(File);267 auto a = Maker.addString("d.h");268 auto b = Maker.addString("b/");269 auto c = Maker.addString("c.h");270 Maker.addBucket("d.h", a, b, c);271 272 addHeaderMap("/x/y/z.hmap", File.getBuffer());273 addSearchDir("/a");274 275 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c.h",276 /*WorkingDir=*/"",277 /*MainFile=*/""),278 "d.h");279}280 281TEST_F(HeaderSearchTest, HeaderMapFrameworkLookup) {282 typedef NullTerminatedFile<test::HMapFileMock<4, 128>, char> FileTy;283 FileTy File;284 File.init();285 286 std::string HeaderDirName = "/tmp/Sources/Foo/Headers/";287 std::string HeaderName = "Foo.h";288 if (is_style_windows(llvm::sys::path::Style::native)) {289 // Force header path to be absolute on windows.290 // As headermap content should represent absolute locations.291 HeaderDirName = "C:" + HeaderDirName;292 }293 294 test::HMapFileMockMaker<FileTy> Maker(File);295 auto a = Maker.addString("Foo/Foo.h");296 auto b = Maker.addString(HeaderDirName);297 auto c = Maker.addString(HeaderName);298 Maker.addBucket("Foo/Foo.h", a, b, c);299 addHeaderMap("product-headers.hmap", File.getBuffer(), /*isAngled=*/true);300 301 VFS->addFile(302 HeaderDirName + HeaderName, 0,303 llvm::MemoryBuffer::getMemBufferCopy("", HeaderDirName + HeaderName),304 /*User=*/std::nullopt, /*Group=*/std::nullopt,305 llvm::sys::fs::file_type::regular_file);306 307 bool IsMapped = false;308 auto FoundFile = Search.LookupFile(309 "Foo/Foo.h", SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,310 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,311 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,312 /*SuggestedModule=*/nullptr, &IsMapped,313 /*IsFrameworkFound=*/nullptr);314 315 EXPECT_TRUE(FoundFile.has_value());316 EXPECT_TRUE(IsMapped);317 auto &FE = *FoundFile;318 auto FI = Search.getExistingFileInfo(FE);319 EXPECT_TRUE(FI);320 EXPECT_TRUE(FI->IsValid);321 EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");322}323 324TEST_F(HeaderSearchTest, HeaderFileInfoMerge) {325 auto AddHeader = [&](std::string HeaderPath) -> FileEntryRef {326 VFS->addFile(HeaderPath, 0,327 llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath),328 /*User=*/std::nullopt, /*Group=*/std::nullopt,329 llvm::sys::fs::file_type::regular_file);330 return *FileMgr.getOptionalFileRef(HeaderPath);331 };332 333 class MockExternalHeaderFileInfoSource : public ExternalHeaderFileInfoSource {334 HeaderFileInfo GetHeaderFileInfo(FileEntryRef FE) {335 HeaderFileInfo HFI;336 auto FileName = FE.getName();337 if (FileName == ModularPath)338 HFI.mergeModuleMembership(ModuleMap::NormalHeader);339 else if (FileName == TextualPath)340 HFI.mergeModuleMembership(ModuleMap::TextualHeader);341 HFI.External = true;342 HFI.IsValid = true;343 return HFI;344 }345 346 public:347 std::string ModularPath = "/modular.h";348 std::string TextualPath = "/textual.h";349 };350 351 auto ExternalSource = std::make_unique<MockExternalHeaderFileInfoSource>();352 Search.SetExternalSource(ExternalSource.get());353 354 // Everything should start out external.355 auto ModularFE = AddHeader(ExternalSource->ModularPath);356 auto TextualFE = AddHeader(ExternalSource->TextualPath);357 EXPECT_TRUE(Search.getExistingFileInfo(ModularFE)->External);358 EXPECT_TRUE(Search.getExistingFileInfo(TextualFE)->External);359 360 // Marking the same role should keep it external361 Search.MarkFileModuleHeader(ModularFE, ModuleMap::NormalHeader,362 /*isCompilingModuleHeader=*/false);363 Search.MarkFileModuleHeader(TextualFE, ModuleMap::TextualHeader,364 /*isCompilingModuleHeader=*/false);365 EXPECT_TRUE(Search.getExistingFileInfo(ModularFE)->External);366 EXPECT_TRUE(Search.getExistingFileInfo(TextualFE)->External);367 368 // textual -> modular should update the HFI, but modular -> textual should be369 // a no-op.370 Search.MarkFileModuleHeader(ModularFE, ModuleMap::TextualHeader,371 /*isCompilingModuleHeader=*/false);372 Search.MarkFileModuleHeader(TextualFE, ModuleMap::NormalHeader,373 /*isCompilingModuleHeader=*/false);374 auto ModularFI = Search.getExistingFileInfo(ModularFE);375 auto TextualFI = Search.getExistingFileInfo(TextualFE);376 EXPECT_TRUE(ModularFI->External);377 EXPECT_TRUE(ModularFI->isModuleHeader);378 EXPECT_FALSE(ModularFI->isTextualModuleHeader);379 EXPECT_FALSE(TextualFI->External);380 EXPECT_TRUE(TextualFI->isModuleHeader);381 EXPECT_FALSE(TextualFI->isTextualModuleHeader);382 383 // Compiling the module should make the HFI local.384 Search.MarkFileModuleHeader(ModularFE, ModuleMap::NormalHeader,385 /*isCompilingModuleHeader=*/true);386 Search.MarkFileModuleHeader(TextualFE, ModuleMap::NormalHeader,387 /*isCompilingModuleHeader=*/true);388 EXPECT_FALSE(Search.getExistingFileInfo(ModularFE)->External);389 EXPECT_FALSE(Search.getExistingFileInfo(TextualFE)->External);390}391 392} // namespace393} // namespace clang394