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 "llvm/Support/MemoryBuffer.h" |
20 | #include "gtest/gtest.h" |
21 | #include <memory> |
22 | #include <string> |
23 | |
24 | namespace clang { |
25 | namespace { |
26 | |
27 | // The test fixture. |
28 | class : public ::testing::Test { |
29 | protected: |
30 | () |
31 | : VFS(new llvm::vfs::InMemoryFileSystem), FileMgr(FileMgrOpts, VFS), |
32 | DiagID(new DiagnosticIDs()), |
33 | Diags(DiagID, 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, Opts&: *TargetOpts); |
38 | } |
39 | |
40 | void (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 (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 (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 = DirectoryLookup(HMap.get(), SrcMgr::C_User); |
77 | Search.AddSearchPath(dir: DL, isAngled); |
78 | } |
79 | |
80 | IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ; |
81 | FileSystemOptions ; |
82 | FileManager ; |
83 | IntrusiveRefCntPtr<DiagnosticIDs> ; |
84 | DiagnosticOptions ; |
85 | DiagnosticsEngine ; |
86 | SourceManager ; |
87 | LangOptions ; |
88 | std::shared_ptr<TargetOptions> ; |
89 | IntrusiveRefCntPtr<TargetInfo> ; |
90 | HeaderSearchOptions ; |
91 | HeaderSearch ; |
92 | std::unique_ptr<HeaderMap> ; |
93 | }; |
94 | |
95 | TEST_F(HeaderSearchTest, NoSearchDir) { |
96 | EXPECT_EQ(Search.search_dir_size(), 0u); |
97 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z" , /*WorkingDir=*/"" , |
98 | /*MainFile=*/"" ), |
99 | "/x/y/z" ); |
100 | } |
101 | |
102 | TEST_F(HeaderSearchTest, SimpleShorten) { |
103 | addSearchDir(Dir: "/x" ); |
104 | addSearchDir(Dir: "/x/y" ); |
105 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z" , /*WorkingDir=*/"" , |
106 | /*MainFile=*/"" ), |
107 | "z" ); |
108 | addSearchDir(Dir: "/a/b/" ); |
109 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c" , /*WorkingDir=*/"" , |
110 | /*MainFile=*/"" ), |
111 | "c" ); |
112 | } |
113 | |
114 | TEST_F(HeaderSearchTest, ShortenWithWorkingDir) { |
115 | addSearchDir(Dir: "x/y" ); |
116 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c/x/y/z" , |
117 | /*WorkingDir=*/"/a/b/c" , |
118 | /*MainFile=*/"" ), |
119 | "z" ); |
120 | } |
121 | |
122 | TEST_F(HeaderSearchTest, Dots) { |
123 | addSearchDir(Dir: "/x/./y/" ); |
124 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/./z" , |
125 | /*WorkingDir=*/"" , |
126 | /*MainFile=*/"" ), |
127 | "z" ); |
128 | addSearchDir(Dir: "a/.././c/" ); |
129 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/m/n/./c/z" , |
130 | /*WorkingDir=*/"/m/n/" , |
131 | /*MainFile=*/"" ), |
132 | "z" ); |
133 | } |
134 | |
135 | TEST_F(HeaderSearchTest, RelativeDirs) { |
136 | ASSERT_FALSE(VFS->setCurrentWorkingDirectory("/root/some/dir" )); |
137 | addSearchDir(Dir: ".." ); |
138 | EXPECT_EQ( |
139 | Search.suggestPathToFileForDiagnostics("/root/some/foo.h" , |
140 | /*WorkingDir=*/"/root/some/dir" , |
141 | /*MainFile=*/"" ), |
142 | "foo.h" ); |
143 | EXPECT_EQ( |
144 | Search.suggestPathToFileForDiagnostics("../foo.h" , |
145 | /*WorkingDir=*/"/root/some/dir" , |
146 | /*MainFile=*/"" ), |
147 | "foo.h" ); |
148 | } |
149 | |
150 | #ifdef _WIN32 |
151 | TEST_F(HeaderSearchTest, BackSlash) { |
152 | addSearchDir("C:\\x\\y\\" ); |
153 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t" , |
154 | /*WorkingDir=*/"" , |
155 | /*MainFile=*/"" ), |
156 | "z/t" ); |
157 | } |
158 | |
159 | TEST_F(HeaderSearchTest, BackSlashWithDotDot) { |
160 | addSearchDir("..\\y" ); |
161 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("C:\\x\\y\\z\\t" , |
162 | /*WorkingDir=*/"C:/x/y/" , |
163 | /*MainFile=*/"" ), |
164 | "z/t" ); |
165 | } |
166 | #endif |
167 | |
168 | TEST_F(HeaderSearchTest, DotDotsWithAbsPath) { |
169 | addSearchDir(Dir: "/x/../y/" ); |
170 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z" , |
171 | /*WorkingDir=*/"" , |
172 | /*MainFile=*/"" ), |
173 | "z" ); |
174 | } |
175 | |
176 | TEST_F(HeaderSearchTest, BothDotDots) { |
177 | addSearchDir(Dir: "/x/../y/" ); |
178 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/../y/z" , |
179 | /*WorkingDir=*/"" , |
180 | /*MainFile=*/"" ), |
181 | "z" ); |
182 | } |
183 | |
184 | TEST_F(HeaderSearchTest, IncludeFromSameDirectory) { |
185 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h" , |
186 | /*WorkingDir=*/"" , |
187 | /*MainFile=*/"/y/a.cc" ), |
188 | "z/t.h" ); |
189 | |
190 | addSearchDir(Dir: "/" ); |
191 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z/t.h" , |
192 | /*WorkingDir=*/"" , |
193 | /*MainFile=*/"/y/a.cc" ), |
194 | "y/z/t.h" ); |
195 | } |
196 | |
197 | TEST_F(HeaderSearchTest, SdkFramework) { |
198 | addFrameworkSearchDir( |
199 | Dir: "/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" , |
204 | /*WorkingDir=*/"" , |
205 | /*MainFile=*/"" , &IsAngled), |
206 | "AppKit/NSView.h" ); |
207 | EXPECT_TRUE(IsAngled); |
208 | |
209 | addFrameworkSearchDir(Dir: "/System/Developer/Library/Framworks/" , |
210 | /*IsSystem*/ false); |
211 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics( |
212 | "/System/Developer/Library/Framworks/" |
213 | "Foo.framework/Headers/Foo.h" , |
214 | /*WorkingDir=*/"" , |
215 | /*MainFile=*/"" , &IsAngled), |
216 | "Foo/Foo.h" ); |
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); |
220 | } |
221 | |
222 | TEST_F(HeaderSearchTest, NestedFramework) { |
223 | addFrameworkSearchDir(Dir: "/Platforms/MacOSX/Frameworks" ); |
224 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics( |
225 | "/Platforms/MacOSX/Frameworks/AppKit.framework/Frameworks/" |
226 | "Sub.framework/Headers/Sub.h" , |
227 | /*WorkingDir=*/"" , |
228 | /*MainFile=*/"" ), |
229 | "Sub/Sub.h" ); |
230 | } |
231 | |
232 | TEST_F(HeaderSearchTest, HeaderFrameworkLookup) { |
233 | std::string = "/tmp/Frameworks/Foo.framework/Headers/Foo.h" ; |
234 | addFrameworkSearchDir(Dir: "/tmp/Frameworks" ); |
235 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, |
236 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: "" , BufferName: HeaderPath), |
237 | /*User=*/std::nullopt, /*Group=*/std::nullopt, |
238 | Type: llvm::sys::fs::file_type::regular_file); |
239 | |
240 | bool IsFrameworkFound = false; |
241 | auto FoundFile = Search.LookupFile( |
242 | Filename: "Foo/Foo.h" , IncludeLoc: SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr, |
243 | /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr, |
244 | /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr, |
245 | /*SuggestedModule=*/nullptr, /*IsMapped=*/nullptr, IsFrameworkFound: &IsFrameworkFound); |
246 | |
247 | EXPECT_TRUE(FoundFile.has_value()); |
248 | EXPECT_TRUE(IsFrameworkFound); |
249 | auto &FE = *FoundFile; |
250 | auto FI = Search.getExistingFileInfo(FE); |
251 | EXPECT_TRUE(FI); |
252 | EXPECT_TRUE(FI->IsValid); |
253 | EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h" ); |
254 | } |
255 | |
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; |
260 | }; |
261 | |
262 | TEST_F(HeaderSearchTest, HeaderMapReverseLookup) { |
263 | typedef NullTerminatedFile<test::HMapFileMock<2, 32>, char> FileTy; |
264 | FileTy File; |
265 | File.init(); |
266 | |
267 | test::HMapFileMockMaker<FileTy> Maker(File); |
268 | auto a = Maker.addString(S: "d.h" ); |
269 | auto b = Maker.addString(S: "b/" ); |
270 | auto c = Maker.addString(S: "c.h" ); |
271 | Maker.addBucket(Str: "d.h" , Key: a, Prefix: b, Suffix: c); |
272 | |
273 | addHeaderMap(Filename: "/x/y/z.hmap" , Buf: File.getBuffer()); |
274 | addSearchDir(Dir: "/a" ); |
275 | |
276 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c.h" , |
277 | /*WorkingDir=*/"" , |
278 | /*MainFile=*/"" ), |
279 | "d.h" ); |
280 | } |
281 | |
282 | TEST_F(HeaderSearchTest, HeaderMapFrameworkLookup) { |
283 | typedef NullTerminatedFile<test::HMapFileMock<4, 128>, char> FileTy; |
284 | FileTy File; |
285 | File.init(); |
286 | |
287 | std::string = "/tmp/Sources/Foo/Headers/" ; |
288 | std::string = "Foo.h" ; |
289 | if (is_style_windows(S: 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; |
293 | } |
294 | |
295 | test::HMapFileMockMaker<FileTy> Maker(File); |
296 | auto a = Maker.addString(S: "Foo/Foo.h" ); |
297 | auto b = Maker.addString(S: HeaderDirName); |
298 | auto c = Maker.addString(S: HeaderName); |
299 | Maker.addBucket(Str: "Foo/Foo.h" , Key: a, Prefix: b, Suffix: c); |
300 | addHeaderMap(Filename: "product-headers.hmap" , Buf: File.getBuffer(), /*isAngled=*/true); |
301 | |
302 | VFS->addFile( |
303 | Path: HeaderDirName + HeaderName, ModificationTime: 0, |
304 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: "" , BufferName: HeaderDirName + HeaderName), |
305 | /*User=*/std::nullopt, /*Group=*/std::nullopt, |
306 | Type: llvm::sys::fs::file_type::regular_file); |
307 | |
308 | bool IsMapped = false; |
309 | auto FoundFile = Search.LookupFile( |
310 | Filename: "Foo/Foo.h" , IncludeLoc: SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr, |
311 | /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr, |
312 | /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr, |
313 | /*SuggestedModule=*/nullptr, IsMapped: &IsMapped, |
314 | /*IsFrameworkFound=*/nullptr); |
315 | |
316 | EXPECT_TRUE(FoundFile.has_value()); |
317 | EXPECT_TRUE(IsMapped); |
318 | auto &FE = *FoundFile; |
319 | auto FI = Search.getExistingFileInfo(FE); |
320 | EXPECT_TRUE(FI); |
321 | EXPECT_TRUE(FI->IsValid); |
322 | EXPECT_EQ(Search.getIncludeNameForHeader(FE), "Foo/Foo.h" ); |
323 | } |
324 | |
325 | TEST_F(HeaderSearchTest, HeaderFileInfoMerge) { |
326 | auto = [&](std::string ) -> FileEntryRef { |
327 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, |
328 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: "" , BufferName: HeaderPath), |
329 | /*User=*/std::nullopt, /*Group=*/std::nullopt, |
330 | Type: llvm::sys::fs::file_type::regular_file); |
331 | return *FileMgr.getOptionalFileRef(Filename: HeaderPath); |
332 | }; |
333 | |
334 | class : public ExternalHeaderFileInfoSource { |
335 | HeaderFileInfo (FileEntryRef FE) { |
336 | HeaderFileInfo HFI; |
337 | auto FileName = FE.getName(); |
338 | if (FileName == ModularPath) |
339 | HFI.mergeModuleMembership(Role: ModuleMap::NormalHeader); |
340 | else if (FileName == TextualPath) |
341 | HFI.mergeModuleMembership(Role: ModuleMap::TextualHeader); |
342 | HFI.External = true; |
343 | HFI.IsValid = true; |
344 | return HFI; |
345 | } |
346 | |
347 | public: |
348 | std::string ModularPath = "/modular.h" ; |
349 | std::string TextualPath = "/textual.h" ; |
350 | }; |
351 | |
352 | auto ExternalSource = std::make_unique<MockExternalHeaderFileInfoSource>(); |
353 | Search.SetExternalSource(ExternalSource.get()); |
354 | |
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); |
360 | |
361 | // Marking the same role should keep it external |
362 | Search.MarkFileModuleHeader(FE: ModularFE, Role: ModuleMap::NormalHeader, |
363 | /*isCompilingModuleHeader=*/false); |
364 | Search.MarkFileModuleHeader(FE: TextualFE, Role: ModuleMap::TextualHeader, |
365 | /*isCompilingModuleHeader=*/false); |
366 | EXPECT_TRUE(Search.getExistingFileInfo(ModularFE)->External); |
367 | EXPECT_TRUE(Search.getExistingFileInfo(TextualFE)->External); |
368 | |
369 | // textual -> modular should update the HFI, but modular -> textual should be |
370 | // a no-op. |
371 | Search.MarkFileModuleHeader(FE: ModularFE, Role: ModuleMap::TextualHeader, |
372 | /*isCompilingModuleHeader=*/false); |
373 | Search.MarkFileModuleHeader(FE: TextualFE, Role: ModuleMap::NormalHeader, |
374 | /*isCompilingModuleHeader=*/false); |
375 | auto ModularFI = Search.getExistingFileInfo(FE: ModularFE); |
376 | auto TextualFI = Search.getExistingFileInfo(FE: 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); |
383 | |
384 | // Compiling the module should make the HFI local. |
385 | Search.MarkFileModuleHeader(FE: ModularFE, Role: ModuleMap::NormalHeader, |
386 | /*isCompilingModuleHeader=*/true); |
387 | Search.MarkFileModuleHeader(FE: TextualFE, Role: ModuleMap::NormalHeader, |
388 | /*isCompilingModuleHeader=*/true); |
389 | EXPECT_FALSE(Search.getExistingFileInfo(ModularFE)->External); |
390 | EXPECT_FALSE(Search.getExistingFileInfo(TextualFE)->External); |
391 | } |
392 | |
393 | } // namespace |
394 | } // namespace clang |
395 | |