1 //===- unittests/Lex/HeaderSearchTest.cpp ------ HeaderSearch tests -------===//
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-exception
7 //===----------------------------------------------------------------------===//
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 "clang/Serialization/InMemoryModuleCache.h"
20 #include "llvm/Support/MemoryBuffer.h"
21 #include "gtest/gtest.h"
29 class HeaderSearchTest
: public ::testing::Test
{
32 : VFS(new llvm::vfs::InMemoryFileSystem
), FileMgr(FileMgrOpts
, VFS
),
33 DiagID(new DiagnosticIDs()),
34 Diags(DiagID
, new DiagnosticOptions
, new IgnoringDiagConsumer()),
35 SourceMgr(Diags
, FileMgr
), TargetOpts(new TargetOptions
),
36 Search(std::make_shared
<HeaderSearchOptions
>(), SourceMgr
, Diags
,
37 LangOpts
, Target
.get()) {
38 TargetOpts
->Triple
= "x86_64-apple-darwin11.1.0";
39 Target
= TargetInfo::CreateTargetInfo(Diags
, TargetOpts
);
42 void addSearchDir(llvm::StringRef Dir
) {
44 Dir
, 0, llvm::MemoryBuffer::getMemBuffer(""), /*User=*/std::nullopt
,
45 /*Group=*/std::nullopt
, llvm::sys::fs::file_type::directory_file
);
46 auto DE
= FileMgr
.getOptionalDirectoryRef(Dir
);
48 auto DL
= DirectoryLookup(*DE
, SrcMgr::C_User
, /*isFramework=*/false);
49 Search
.AddSearchPath(DL
, /*isAngled=*/false);
52 void addFrameworkSearchDir(llvm::StringRef Dir
, bool IsSystem
= true) {
54 Dir
, 0, llvm::MemoryBuffer::getMemBuffer(""), /*User=*/std::nullopt
,
55 /*Group=*/std::nullopt
, llvm::sys::fs::file_type::directory_file
);
56 auto DE
= FileMgr
.getOptionalDirectoryRef(Dir
);
58 auto DL
= DirectoryLookup(*DE
, IsSystem
? SrcMgr::C_System
: SrcMgr::C_User
,
59 /*isFramework=*/true);
61 Search
.AddSystemSearchPath(DL
);
63 Search
.AddSearchPath(DL
, /*isAngled=*/true);
66 void addHeaderMap(llvm::StringRef Filename
,
67 std::unique_ptr
<llvm::MemoryBuffer
> Buf
,
68 bool isAngled
= false) {
69 VFS
->addFile(Filename
, 0, std::move(Buf
), /*User=*/std::nullopt
,
70 /*Group=*/std::nullopt
,
71 llvm::sys::fs::file_type::regular_file
);
72 auto FE
= FileMgr
.getOptionalFileRef(Filename
, true);
75 // Test class supports only one HMap at a time.
77 HMap
= HeaderMap::Create(*FE
, FileMgr
);
78 auto DL
= DirectoryLookup(HMap
.get(), SrcMgr::C_User
);
79 Search
.AddSearchPath(DL
, isAngled
);
82 IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> VFS
;
83 FileSystemOptions FileMgrOpts
;
85 IntrusiveRefCntPtr
<DiagnosticIDs
> DiagID
;
86 DiagnosticsEngine Diags
;
87 SourceManager SourceMgr
;
89 std::shared_ptr
<TargetOptions
> TargetOpts
;
90 IntrusiveRefCntPtr
<TargetInfo
> Target
;
92 std::unique_ptr
<HeaderMap
> HMap
;
95 TEST_F(HeaderSearchTest
, NoSearchDir
) {
96 EXPECT_EQ(Search
.search_dir_size(), 0u);
97 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",
102 TEST_F(HeaderSearchTest
, SimpleShorten
) {
104 addSearchDir("/x/y");
105 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",
108 addSearchDir("/a/b/");
109 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/a/b/c", /*WorkingDir=*/"",
114 TEST_F(HeaderSearchTest
, ShortenWithWorkingDir
) {
116 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/a/b/c/x/y/z",
117 /*WorkingDir=*/"/a/b/c",
122 TEST_F(HeaderSearchTest
, Dots
) {
123 addSearchDir("/x/./y/");
124 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/x/y/./z",
128 addSearchDir("a/.././c/");
129 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/m/n/./c/z",
130 /*WorkingDir=*/"/m/n/",
135 TEST_F(HeaderSearchTest
, RelativeDirs
) {
136 ASSERT_FALSE(VFS
->setCurrentWorkingDirectory("/root/some/dir"));
139 Search
.suggestPathToFileForDiagnostics("/root/some/foo.h",
140 /*WorkingDir=*/"/root/some/dir",
144 Search
.suggestPathToFileForDiagnostics("../foo.h",
145 /*WorkingDir=*/"/root/some/dir",
151 TEST_F(HeaderSearchTest
, BackSlash
) {
152 addSearchDir("C:\\x\\y\\");
153 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
159 TEST_F(HeaderSearchTest
, BackSlashWithDotDot
) {
160 addSearchDir("..\\y");
161 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
162 /*WorkingDir=*/"C:/x/y/",
168 TEST_F(HeaderSearchTest
, DotDotsWithAbsPath
) {
169 addSearchDir("/x/../y/");
170 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/y/z",
176 TEST_F(HeaderSearchTest
, BothDotDots
) {
177 addSearchDir("/x/../y/");
178 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/x/../y/z",
184 TEST_F(HeaderSearchTest
, IncludeFromSameDirectory
) {
185 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/y/z/t.h",
187 /*MainFile=*/"/y/a.cc"),
191 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/y/z/t.h",
193 /*MainFile=*/"/y/a.cc"),
197 TEST_F(HeaderSearchTest
, SdkFramework
) {
198 addFrameworkSearchDir(
199 "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/Frameworks/");
200 bool IsAngled
= false;
201 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics(
202 "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
203 "Frameworks/AppKit.framework/Headers/NSView.h",
205 /*MainFile=*/"", &IsAngled
),
207 EXPECT_TRUE(IsAngled
);
209 addFrameworkSearchDir("/System/Developer/Library/Framworks/",
211 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics(
212 "/System/Developer/Library/Framworks/"
213 "Foo.framework/Headers/Foo.h",
215 /*MainFile=*/"", &IsAngled
),
217 // Expect to be true even though we passed false to IsSystem earlier since
218 // all frameworks should be treated as <>.
219 EXPECT_TRUE(IsAngled
);
222 TEST_F(HeaderSearchTest
, NestedFramework
) {
223 addFrameworkSearchDir("/Platforms/MacOSX/Frameworks");
224 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics(
225 "/Platforms/MacOSX/Frameworks/AppKit.framework/Frameworks/"
226 "Sub.framework/Headers/Sub.h",
232 TEST_F(HeaderSearchTest
, HeaderFrameworkLookup
) {
233 std::string HeaderPath
= "/tmp/Frameworks/Foo.framework/Headers/Foo.h";
234 addFrameworkSearchDir("/tmp/Frameworks");
235 VFS
->addFile(HeaderPath
, 0,
236 llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath
),
237 /*User=*/std::nullopt
, /*Group=*/std::nullopt
,
238 llvm::sys::fs::file_type::regular_file
);
240 bool IsFrameworkFound
= false;
241 auto FoundFile
= Search
.LookupFile(
242 "Foo/Foo.h", SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,
243 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,
244 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,
245 /*SuggestedModule=*/nullptr, /*IsMapped=*/nullptr, &IsFrameworkFound
);
247 EXPECT_TRUE(FoundFile
.has_value());
248 EXPECT_TRUE(IsFrameworkFound
);
249 auto &FE
= *FoundFile
;
250 auto FI
= Search
.getExistingFileInfo(FE
);
252 EXPECT_TRUE(FI
->IsValid
);
253 EXPECT_EQ(Search
.getIncludeNameForHeader(FE
), "Foo/Foo.h");
256 // Helper struct with null terminator character to make MemoryBuffer happy.
257 template <class FileTy
, class PaddingTy
>
258 struct NullTerminatedFile
: public FileTy
{
259 PaddingTy Padding
= 0;
262 TEST_F(HeaderSearchTest
, HeaderMapReverseLookup
) {
263 typedef NullTerminatedFile
<test::HMapFileMock
<2, 32>, char> FileTy
;
267 test::HMapFileMockMaker
<FileTy
> Maker(File
);
268 auto a
= Maker
.addString("d.h");
269 auto b
= Maker
.addString("b/");
270 auto c
= Maker
.addString("c.h");
271 Maker
.addBucket("d.h", a
, b
, c
);
273 addHeaderMap("/x/y/z.hmap", File
.getBuffer());
276 EXPECT_EQ(Search
.suggestPathToFileForDiagnostics("/a/b/c.h",
282 TEST_F(HeaderSearchTest
, HeaderMapFrameworkLookup
) {
283 typedef NullTerminatedFile
<test::HMapFileMock
<4, 128>, char> FileTy
;
287 std::string HeaderDirName
= "/tmp/Sources/Foo/Headers/";
288 std::string HeaderName
= "Foo.h";
289 if (is_style_windows(llvm::sys::path::Style::native
)) {
290 // Force header path to be absolute on windows.
291 // As headermap content should represent absolute locations.
292 HeaderDirName
= "C:" + HeaderDirName
;
295 test::HMapFileMockMaker
<FileTy
> Maker(File
);
296 auto a
= Maker
.addString("Foo/Foo.h");
297 auto b
= Maker
.addString(HeaderDirName
);
298 auto c
= Maker
.addString(HeaderName
);
299 Maker
.addBucket("Foo/Foo.h", a
, b
, c
);
300 addHeaderMap("product-headers.hmap", File
.getBuffer(), /*isAngled=*/true);
303 HeaderDirName
+ HeaderName
, 0,
304 llvm::MemoryBuffer::getMemBufferCopy("", HeaderDirName
+ HeaderName
),
305 /*User=*/std::nullopt
, /*Group=*/std::nullopt
,
306 llvm::sys::fs::file_type::regular_file
);
308 bool IsMapped
= false;
309 auto FoundFile
= Search
.LookupFile(
310 "Foo/Foo.h", SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,
311 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,
312 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,
313 /*SuggestedModule=*/nullptr, &IsMapped
,
314 /*IsFrameworkFound=*/nullptr);
316 EXPECT_TRUE(FoundFile
.has_value());
317 EXPECT_TRUE(IsMapped
);
318 auto &FE
= *FoundFile
;
319 auto FI
= Search
.getExistingFileInfo(FE
);
321 EXPECT_TRUE(FI
->IsValid
);
322 EXPECT_EQ(Search
.getIncludeNameForHeader(FE
), "Foo/Foo.h");
325 TEST_F(HeaderSearchTest
, HeaderFileInfoMerge
) {
326 auto AddHeader
= [&](std::string HeaderPath
) -> FileEntryRef
{
327 VFS
->addFile(HeaderPath
, 0,
328 llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath
),
329 /*User=*/std::nullopt
, /*Group=*/std::nullopt
,
330 llvm::sys::fs::file_type::regular_file
);
331 return *FileMgr
.getOptionalFileRef(HeaderPath
);
334 class MockExternalHeaderFileInfoSource
: public ExternalHeaderFileInfoSource
{
335 HeaderFileInfo
GetHeaderFileInfo(FileEntryRef FE
) {
337 auto FileName
= FE
.getName();
338 if (FileName
== ModularPath
)
339 HFI
.mergeModuleMembership(ModuleMap::NormalHeader
);
340 else if (FileName
== TextualPath
)
341 HFI
.mergeModuleMembership(ModuleMap::TextualHeader
);
348 std::string ModularPath
= "/modular.h";
349 std::string TextualPath
= "/textual.h";
352 auto ExternalSource
= std::make_unique
<MockExternalHeaderFileInfoSource
>();
353 Search
.SetExternalSource(ExternalSource
.get());
355 // Everything should start out external.
356 auto ModularFE
= AddHeader(ExternalSource
->ModularPath
);
357 auto TextualFE
= AddHeader(ExternalSource
->TextualPath
);
358 EXPECT_TRUE(Search
.getExistingFileInfo(ModularFE
)->External
);
359 EXPECT_TRUE(Search
.getExistingFileInfo(TextualFE
)->External
);
361 // Marking the same role should keep it external
362 Search
.MarkFileModuleHeader(ModularFE
, ModuleMap::NormalHeader
,
363 /*isCompilingModuleHeader=*/false);
364 Search
.MarkFileModuleHeader(TextualFE
, ModuleMap::TextualHeader
,
365 /*isCompilingModuleHeader=*/false);
366 EXPECT_TRUE(Search
.getExistingFileInfo(ModularFE
)->External
);
367 EXPECT_TRUE(Search
.getExistingFileInfo(TextualFE
)->External
);
369 // textual -> modular should update the HFI, but modular -> textual should be
371 Search
.MarkFileModuleHeader(ModularFE
, ModuleMap::TextualHeader
,
372 /*isCompilingModuleHeader=*/false);
373 Search
.MarkFileModuleHeader(TextualFE
, ModuleMap::NormalHeader
,
374 /*isCompilingModuleHeader=*/false);
375 auto ModularFI
= Search
.getExistingFileInfo(ModularFE
);
376 auto TextualFI
= Search
.getExistingFileInfo(TextualFE
);
377 EXPECT_TRUE(ModularFI
->External
);
378 EXPECT_TRUE(ModularFI
->isModuleHeader
);
379 EXPECT_FALSE(ModularFI
->isTextualModuleHeader
);
380 EXPECT_FALSE(TextualFI
->External
);
381 EXPECT_TRUE(TextualFI
->isModuleHeader
);
382 EXPECT_FALSE(TextualFI
->isTextualModuleHeader
);
384 // Compiling the module should make the HFI local.
385 Search
.MarkFileModuleHeader(ModularFE
, ModuleMap::NormalHeader
,
386 /*isCompilingModuleHeader=*/true);
387 Search
.MarkFileModuleHeader(TextualFE
, ModuleMap::NormalHeader
,
388 /*isCompilingModuleHeader=*/true);
389 EXPECT_FALSE(Search
.getExistingFileInfo(ModularFE
)->External
);
390 EXPECT_FALSE(Search
.getExistingFileInfo(TextualFE
)->External
);