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 | |
23 | namespace clang { |
24 | namespace { |
25 | |
26 | // The test fixture. |
27 | class : public ::testing::Test { |
28 | protected: |
29 | () |
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 (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 = |
77 | DirectoryLookup(HMap.get(), SrcMgr::C_User, /*isFramework=*/false); |
78 | Search.AddSearchPath(dir: DL, isAngled); |
79 | } |
80 | |
81 | IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ; |
82 | FileSystemOptions ; |
83 | FileManager ; |
84 | IntrusiveRefCntPtr<DiagnosticIDs> ; |
85 | DiagnosticsEngine ; |
86 | SourceManager ; |
87 | LangOptions ; |
88 | std::shared_ptr<TargetOptions> ; |
89 | IntrusiveRefCntPtr<TargetInfo> ; |
90 | HeaderSearch ; |
91 | std::unique_ptr<HeaderMap> ; |
92 | }; |
93 | |
94 | TEST_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 | |
101 | TEST_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 | |
113 | TEST_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 | |
121 | TEST_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 |
135 | TEST_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 | |
143 | TEST_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 | |
152 | TEST_F(HeaderSearchTest, DotDotsWithAbsPath) { |
153 | addSearchDir(Dir: "/x/../y/" ); |
154 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z" , |
155 | /*WorkingDir=*/"" , |
156 | /*MainFile=*/"" ), |
157 | "z" ); |
158 | } |
159 | |
160 | TEST_F(HeaderSearchTest, BothDotDots) { |
161 | addSearchDir(Dir: "/x/../y/" ); |
162 | EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/../y/z" , |
163 | /*WorkingDir=*/"" , |
164 | /*MainFile=*/"" ), |
165 | "z" ); |
166 | } |
167 | |
168 | TEST_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 | |
181 | TEST_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 | |
206 | TEST_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 | |
216 | TEST_F(HeaderSearchTest, HeaderFrameworkLookup) { |
217 | std::string = "/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. |
242 | template <class FileTy, class PaddingTy> |
243 | struct NullTerminatedFile : public FileTy { |
244 | PaddingTy Padding = 0; |
245 | }; |
246 | |
247 | TEST_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 | |
267 | TEST_F(HeaderSearchTest, HeaderMapFrameworkLookup) { |
268 | typedef NullTerminatedFile<test::HMapFileMock<4, 128>, char> FileTy; |
269 | FileTy File; |
270 | File.init(); |
271 | |
272 | std::string = "/tmp/Sources/Foo/Headers/" ; |
273 | std::string = "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 | |