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-exception
6//
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 "clang/Serialization/InMemoryModuleCache.h"
20#include "llvm/Support/MemoryBuffer.h"
21#include "gtest/gtest.h"
22
23namespace clang {
24namespace {
25
26// The test fixture.
27class HeaderSearchTest : public ::testing::Test {
28protected:
29 HeaderSearchTest()
30 : VFS(new llvm::vfs::InMemoryFileSystem), FileMgr(FileMgrOpts, VFS),
31 DiagID(new DiagnosticIDs()),
32 Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
33 SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions),
34 Search(std::make_shared<HeaderSearchOptions>(), SourceMgr, Diags,
35 LangOpts, Target.get()) {
36 TargetOpts->Triple = "x86_64-apple-darwin11.1.0";
37 Target = TargetInfo::CreateTargetInfo(Diags, Opts: TargetOpts);
38 }
39
40 void addSearchDir(llvm::StringRef Dir) {
41 VFS->addFile(
42 Path: Dir, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: ""), /*User=*/std::nullopt,
43 /*Group=*/std::nullopt, Type: llvm::sys::fs::file_type::directory_file);
44 auto DE = FileMgr.getOptionalDirectoryRef(DirName: Dir);
45 assert(DE);
46 auto DL = DirectoryLookup(*DE, SrcMgr::C_User, /*isFramework=*/false);
47 Search.AddSearchPath(dir: DL, /*isAngled=*/false);
48 }
49
50 void addFrameworkSearchDir(llvm::StringRef Dir, bool IsSystem = true) {
51 VFS->addFile(
52 Path: Dir, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: ""), /*User=*/std::nullopt,
53 /*Group=*/std::nullopt, Type: llvm::sys::fs::file_type::directory_file);
54 auto DE = FileMgr.getOptionalDirectoryRef(DirName: 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(dir: DL);
60 else
61 Search.AddSearchPath(dir: 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(Path: Filename, ModificationTime: 0, Buffer: std::move(Buf), /*User=*/std::nullopt,
68 /*Group=*/std::nullopt,
69 Type: llvm::sys::fs::file_type::regular_file);
70 auto FE = FileMgr.getOptionalFileRef(Filename, OpenFile: true);
71 assert(FE);
72
73 // Test class supports only one HMap at a time.
74 assert(!HMap);
75 HMap = HeaderMap::Create(FE: *FE, FM&: FileMgr);
76 auto DL =
77 DirectoryLookup(HMap.get(), SrcMgr::C_User, /*isFramework=*/false);
78 Search.AddSearchPath(dir: DL, isAngled);
79 }
80
81 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> VFS;
82 FileSystemOptions FileMgrOpts;
83 FileManager FileMgr;
84 IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
85 DiagnosticsEngine Diags;
86 SourceManager SourceMgr;
87 LangOptions LangOpts;
88 std::shared_ptr<TargetOptions> TargetOpts;
89 IntrusiveRefCntPtr<TargetInfo> Target;
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(Dir: "/x");
103 addSearchDir(Dir: "/x/y");
104 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",
105 /*MainFile=*/""),
106 "z");
107 addSearchDir(Dir: "/a/b/");
108 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c", /*WorkingDir=*/"",
109 /*MainFile=*/""),
110 "c");
111}
112
113TEST_F(HeaderSearchTest, ShortenWithWorkingDir) {
114 addSearchDir(Dir: "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(Dir: "/x/./y/");
123 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/./z",
124 /*WorkingDir=*/"",
125 /*MainFile=*/""),
126 "z");
127 addSearchDir(Dir: "a/.././c/");
128 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/m/n/./c/z",
129 /*WorkingDir=*/"/m/n/",
130 /*MainFile=*/""),
131 "z");
132}
133
134#ifdef _WIN32
135TEST_F(HeaderSearchTest, BackSlash) {
136 addSearchDir("C:\\x\\y\\");
137 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
138 /*WorkingDir=*/"",
139 /*MainFile=*/""),
140 "z/t");
141}
142
143TEST_F(HeaderSearchTest, BackSlashWithDotDot) {
144 addSearchDir("..\\y");
145 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t",
146 /*WorkingDir=*/"C:/x/y/",
147 /*MainFile=*/""),
148 "z/t");
149}
150#endif
151
152TEST_F(HeaderSearchTest, DotDotsWithAbsPath) {
153 addSearchDir(Dir: "/x/../y/");
154 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z",
155 /*WorkingDir=*/"",
156 /*MainFile=*/""),
157 "z");
158}
159
160TEST_F(HeaderSearchTest, BothDotDots) {
161 addSearchDir(Dir: "/x/../y/");
162 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/../y/z",
163 /*WorkingDir=*/"",
164 /*MainFile=*/""),
165 "z");
166}
167
168TEST_F(HeaderSearchTest, IncludeFromSameDirectory) {
169 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",
170 /*WorkingDir=*/"",
171 /*MainFile=*/"/y/a.cc"),
172 "z/t.h");
173
174 addSearchDir(Dir: "/");
175 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h",
176 /*WorkingDir=*/"",
177 /*MainFile=*/"/y/a.cc"),
178 "y/z/t.h");
179}
180
181TEST_F(HeaderSearchTest, SdkFramework) {
182 addFrameworkSearchDir(
183 Dir: "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/Frameworks/");
184 bool IsAngled = false;
185 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
186 "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
187 "Frameworks/AppKit.framework/Headers/NSView.h",
188 /*WorkingDir=*/"",
189 /*MainFile=*/"", &IsAngled),
190 "AppKit/NSView.h");
191 EXPECT_TRUE(IsAngled);
192
193 addFrameworkSearchDir(Dir: "/System/Developer/Library/Framworks/",
194 /*IsSystem*/ false);
195 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
196 "/System/Developer/Library/Framworks/"
197 "Foo.framework/Headers/Foo.h",
198 /*WorkingDir=*/"",
199 /*MainFile=*/"", &IsAngled),
200 "Foo/Foo.h");
201 // Expect to be true even though we passed false to IsSystem earlier since
202 // all frameworks should be treated as <>.
203 EXPECT_TRUE(IsAngled);
204}
205
206TEST_F(HeaderSearchTest, NestedFramework) {
207 addFrameworkSearchDir(Dir: "/Platforms/MacOSX/Frameworks");
208 EXPECT_EQ(Search.suggestPathToFileForDiagnostics(
209 "/Platforms/MacOSX/Frameworks/AppKit.framework/Frameworks/"
210 "Sub.framework/Headers/Sub.h",
211 /*WorkingDir=*/"",
212 /*MainFile=*/""),
213 "Sub/Sub.h");
214}
215
216TEST_F(HeaderSearchTest, HeaderFrameworkLookup) {
217 std::string HeaderPath = "/tmp/Frameworks/Foo.framework/Headers/Foo.h";
218 addFrameworkSearchDir(Dir: "/tmp/Frameworks");
219 VFS->addFile(Path: HeaderPath, ModificationTime: 0,
220 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: "", BufferName: HeaderPath),
221 /*User=*/std::nullopt, /*Group=*/std::nullopt,
222 Type: llvm::sys::fs::file_type::regular_file);
223
224 bool IsFrameworkFound = false;
225 auto FoundFile = Search.LookupFile(
226 Filename: "Foo/Foo.h", IncludeLoc: SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,
227 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,
228 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,
229 /*SuggestedModule=*/nullptr, /*IsMapped=*/nullptr, IsFrameworkFound: &IsFrameworkFound);
230
231 EXPECT_TRUE(FoundFile.has_value());
232 EXPECT_TRUE(IsFrameworkFound);
233 auto &FE = *FoundFile;
234 auto FI = Search.getExistingFileInfo(FE);
235 EXPECT_TRUE(FI);
236 EXPECT_TRUE(FI->IsValid);
237 EXPECT_EQ(FI->Framework.str(), "Foo");
238 EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");
239}
240
241// Helper struct with null terminator character to make MemoryBuffer happy.
242template <class FileTy, class PaddingTy>
243struct NullTerminatedFile : public FileTy {
244 PaddingTy Padding = 0;
245};
246
247TEST_F(HeaderSearchTest, HeaderMapReverseLookup) {
248 typedef NullTerminatedFile<test::HMapFileMock<2, 32>, char> FileTy;
249 FileTy File;
250 File.init();
251
252 test::HMapFileMockMaker<FileTy> Maker(File);
253 auto a = Maker.addString(S: "d.h");
254 auto b = Maker.addString(S: "b/");
255 auto c = Maker.addString(S: "c.h");
256 Maker.addBucket(Str: "d.h", Key: a, Prefix: b, Suffix: c);
257
258 addHeaderMap(Filename: "/x/y/z.hmap", Buf: File.getBuffer());
259 addSearchDir(Dir: "/a");
260
261 EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c.h",
262 /*WorkingDir=*/"",
263 /*MainFile=*/""),
264 "d.h");
265}
266
267TEST_F(HeaderSearchTest, HeaderMapFrameworkLookup) {
268 typedef NullTerminatedFile<test::HMapFileMock<4, 128>, char> FileTy;
269 FileTy File;
270 File.init();
271
272 std::string HeaderDirName = "/tmp/Sources/Foo/Headers/";
273 std::string HeaderName = "Foo.h";
274 if (is_style_windows(S: llvm::sys::path::Style::native)) {
275 // Force header path to be absolute on windows.
276 // As headermap content should represent absolute locations.
277 HeaderDirName = "C:" + HeaderDirName;
278 }
279
280 test::HMapFileMockMaker<FileTy> Maker(File);
281 auto a = Maker.addString(S: "Foo/Foo.h");
282 auto b = Maker.addString(S: HeaderDirName);
283 auto c = Maker.addString(S: HeaderName);
284 Maker.addBucket(Str: "Foo/Foo.h", Key: a, Prefix: b, Suffix: c);
285 addHeaderMap(Filename: "product-headers.hmap", Buf: File.getBuffer(), /*isAngled=*/true);
286
287 VFS->addFile(
288 Path: HeaderDirName + HeaderName, ModificationTime: 0,
289 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: "", BufferName: HeaderDirName + HeaderName),
290 /*User=*/std::nullopt, /*Group=*/std::nullopt,
291 Type: llvm::sys::fs::file_type::regular_file);
292
293 bool IsMapped = false;
294 auto FoundFile = Search.LookupFile(
295 Filename: "Foo/Foo.h", IncludeLoc: SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr,
296 /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr,
297 /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr,
298 /*SuggestedModule=*/nullptr, IsMapped: &IsMapped,
299 /*IsFrameworkFound=*/nullptr);
300
301 EXPECT_TRUE(FoundFile.has_value());
302 EXPECT_TRUE(IsMapped);
303 auto &FE = *FoundFile;
304 auto FI = Search.getExistingFileInfo(FE);
305 EXPECT_TRUE(FI);
306 EXPECT_TRUE(FI->IsValid);
307 EXPECT_EQ(FI->Framework.str(), "Foo");
308 EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h");
309}
310
311} // namespace
312} // namespace clang
313

source code of clang/unittests/Lex/HeaderSearchTest.cpp