xref: /llvm-project/clang/unittests/Lex/HeaderSearchTest.cpp (revision e494e2694a90ac6fb18976b8cb6aab849b6279b4)
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 #include <memory>
23 #include <string>
24 
25 namespace clang {
26 namespace {
27 
28 // The test fixture.
29 class HeaderSearchTest : public ::testing::Test {
30 protected:
31   HeaderSearchTest()
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);
40   }
41 
42   void addSearchDir(llvm::StringRef Dir) {
43     VFS->addFile(
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);
47     assert(DE);
48     auto DL = DirectoryLookup(*DE, SrcMgr::C_User, /*isFramework=*/false);
49     Search.AddSearchPath(DL, /*isAngled=*/false);
50   }
51 
52   void addFrameworkSearchDir(llvm::StringRef Dir, bool IsSystem = true) {
53     VFS->addFile(
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);
57     assert(DE);
58     auto DL = DirectoryLookup(*DE, IsSystem ? SrcMgr::C_System : SrcMgr::C_User,
59                               /*isFramework=*/true);
60     if (IsSystem)
61       Search.AddSystemSearchPath(DL);
62     else
63       Search.AddSearchPath(DL, /*isAngled=*/true);
64   }
65 
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);
73     assert(FE);
74 
75     // Test class supports only one HMap at a time.
76     assert(!HMap);
77     HMap = HeaderMap::Create(*FE, FileMgr);
78     auto DL = DirectoryLookup(HMap.get(), SrcMgr::C_User);
79     Search.AddSearchPath(DL, isAngled);
80   }
81 
82   IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> VFS;
83   FileSystemOptions FileMgrOpts;
84   FileManager FileMgr;
85   IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
86   DiagnosticsEngine Diags;
87   SourceManager SourceMgr;
88   LangOptions LangOpts;
89   std::shared_ptr<TargetOptions> TargetOpts;
90   IntrusiveRefCntPtr<TargetInfo> Target;
91   HeaderSearch Search;
92   std::unique_ptr<HeaderMap> HMap;
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("/x");
104   addSearchDir("/x/y");
105   EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/z", /*WorkingDir=*/"",
106                                                    /*MainFile=*/""),
107             "z");
108   addSearchDir("/a/b/");
109   EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c", /*WorkingDir=*/"",
110                                                    /*MainFile=*/""),
111             "c");
112 }
113 
114 TEST_F(HeaderSearchTest, ShortenWithWorkingDir) {
115   addSearchDir("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("/x/./y/");
124   EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/x/y/./z",
125                                                    /*WorkingDir=*/"",
126                                                    /*MainFile=*/""),
127             "z");
128   addSearchDir("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("..");
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("/x/../y/");
170   EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/y/z",
171                                                    /*WorkingDir=*/"",
172                                                    /*MainFile=*/""),
173             "z");
174 }
175 
176 TEST_F(HeaderSearchTest, BothDotDots) {
177   addSearchDir("/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("/");
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       "/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("/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("/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 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);
239 
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);
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("d.h");
269   auto b = Maker.addString("b/");
270   auto c = Maker.addString("c.h");
271   Maker.addBucket("d.h", a, b, c);
272 
273   addHeaderMap("/x/y/z.hmap", File.getBuffer());
274   addSearchDir("/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 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;
293   }
294 
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);
301 
302   VFS->addFile(
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);
307 
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);
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 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);
332   };
333 
334   class MockExternalHeaderFileInfoSource : public ExternalHeaderFileInfoSource {
335     HeaderFileInfo GetHeaderFileInfo(FileEntryRef FE) {
336       HeaderFileInfo HFI;
337       auto FileName = FE.getName();
338       if (FileName == ModularPath)
339         HFI.mergeModuleMembership(ModuleMap::NormalHeader);
340       else if (FileName == TextualPath)
341         HFI.mergeModuleMembership(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(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);
368 
369   // textual -> modular should update the HFI, but modular -> textual should be
370   // a no-op.
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);
383 
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);
391 }
392 
393 } // namespace
394 } // namespace clang
395