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