| 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 | |