diff --git a/clang/include/clang/Lex/HeaderMap.h b/clang/include/clang/Lex/HeaderMap.h --- a/clang/include/clang/Lex/HeaderMap.h +++ b/clang/include/clang/Lex/HeaderMap.h @@ -16,6 +16,7 @@ #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/MemoryBuffer.h" #include @@ -29,6 +30,7 @@ class HeaderMapImpl { std::unique_ptr FileBuffer; bool NeedsBSwap; + mutable llvm::StringMap ReverseMap; public: HeaderMapImpl(std::unique_ptr File, bool NeedsBSwap) @@ -48,6 +50,9 @@ /// Print the contents of this headermap to stderr. void dump() const; + /// Return key for specifed path. + StringRef reverseLookupFilename(StringRef DestPath) const; + private: unsigned getEndianAdjustedWord(unsigned X) const; const HMapHeader &getHeader() const; @@ -82,6 +87,7 @@ using HeaderMapImpl::lookupFilename; using HeaderMapImpl::getFileName; using HeaderMapImpl::dump; + using HeaderMapImpl::reverseLookupFilename; }; } // end namespace clang. diff --git a/clang/lib/Lex/HeaderMap.cpp b/clang/lib/Lex/HeaderMap.cpp --- a/clang/lib/Lex/HeaderMap.cpp +++ b/clang/lib/Lex/HeaderMap.cpp @@ -240,3 +240,26 @@ return StringRef(DestPath.begin(), DestPath.size()); } } + +StringRef HeaderMapImpl::reverseLookupFilename(StringRef DestPath) const { + if (ReverseMap.empty()) { + const HMapHeader &Hdr = getHeader(); + unsigned NumBuckets = getEndianAdjustedWord(Hdr.NumBuckets); + for (unsigned i = 0; i != NumBuckets; ++i) { + HMapBucket B = getBucket(i); + if (B.Key == HMAP_EmptyBucketKey) + continue; + + Optional Key = getString(B.Key); + Optional Prefix = getString(B.Prefix); + Optional Suffix = getString(B.Suffix); + if (Key && Prefix && Suffix) { + SmallVector Buf; + Buf.append(Prefix->begin(), Prefix->end()); + Buf.append(Suffix->begin(), Suffix->end()); + ReverseMap[StringRef(Buf.begin(), Buf.size())] = *Key; + } + } + } + return ReverseMap.lookup(DestPath); +} diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -1834,7 +1834,7 @@ }; for (unsigned I = 0; I != SearchDirs.size(); ++I) { - // FIXME: Support this search within frameworks and header maps. + // FIXME: Support this search within frameworks. if (!SearchDirs[I].isNormalDir()) continue; @@ -1848,6 +1848,18 @@ if (!BestPrefixLength && CheckDir(path::parent_path(MainFile)) && IsSystem) *IsSystem = false; - - return path::convert_to_slash(File.drop_front(BestPrefixLength)); + // Try resolving resulting filaname via reverse search in header maps, + // key from header name is user prefered name for the include file. + StringRef Filename = File.drop_front(BestPrefixLength); + for (unsigned I = 0; I != SearchDirs.size(); ++I) { + if (SearchDirs[I].isHeaderMap()) { + StringRef SpelledFilename = + SearchDirs[I].getHeaderMap()->reverseLookupFilename(Filename); + if (!SpelledFilename.empty()) { + Filename = SpelledFilename; + break; + } + } + } + return path::convert_to_slash(Filename); } diff --git a/clang/unittests/Lex/HeaderMapTest.cpp b/clang/unittests/Lex/HeaderMapTest.cpp --- a/clang/unittests/Lex/HeaderMapTest.cpp +++ b/clang/unittests/Lex/HeaderMapTest.cpp @@ -6,89 +6,17 @@ // //===--------------------------------------------------------------===// -#include "clang/Basic/CharInfo.h" -#include "clang/Lex/HeaderMap.h" -#include "clang/Lex/HeaderMapTypes.h" +#include "HeaderMapTestUtils.h" #include "llvm/ADT/SmallString.h" -#include "llvm/Support/SwapByteOrder.h" #include "gtest/gtest.h" -#include #include using namespace clang; using namespace llvm; +using namespace clang::test; namespace { -// Lay out a header file for testing. -template struct MapFile { - HMapHeader Header; - HMapBucket Buckets[NumBuckets]; - unsigned char Bytes[NumBytes]; - - void init() { - memset(this, 0, sizeof(MapFile)); - Header.Magic = HMAP_HeaderMagicNumber; - Header.Version = HMAP_HeaderVersion; - Header.NumBuckets = NumBuckets; - Header.StringsOffset = sizeof(Header) + sizeof(Buckets); - } - - void swapBytes() { - using llvm::sys::getSwappedBytes; - Header.Magic = getSwappedBytes(Header.Magic); - Header.Version = getSwappedBytes(Header.Version); - Header.NumBuckets = getSwappedBytes(Header.NumBuckets); - Header.StringsOffset = getSwappedBytes(Header.StringsOffset); - } - - std::unique_ptr getBuffer() const { - return MemoryBuffer::getMemBuffer( - StringRef(reinterpret_cast(this), sizeof(MapFile)), - "header", - /* RequresNullTerminator */ false); - } -}; - -// The header map hash function. -static inline unsigned getHash(StringRef Str) { - unsigned Result = 0; - for (char C : Str) - Result += toLowercase(C) * 13; - return Result; -} - -template struct FileMaker { - FileTy &File; - unsigned SI = 1; - unsigned BI = 0; - FileMaker(FileTy &File) : File(File) {} - - unsigned addString(StringRef S) { - assert(SI + S.size() + 1 <= sizeof(File.Bytes)); - std::copy(S.begin(), S.end(), File.Bytes + SI); - auto OldSI = SI; - SI += S.size() + 1; - return OldSI; - } - void addBucket(unsigned Hash, unsigned Key, unsigned Prefix, unsigned Suffix) { - assert(!(File.Header.NumBuckets & (File.Header.NumBuckets - 1))); - unsigned I = Hash & (File.Header.NumBuckets - 1); - do { - if (!File.Buckets[I].Key) { - File.Buckets[I].Key = Key; - File.Buckets[I].Prefix = Prefix; - File.Buckets[I].Suffix = Suffix; - ++File.Header.NumEntries; - return; - } - ++I; - I &= File.Header.NumBuckets - 1; - } while (I != (Hash & (File.Header.NumBuckets - 1))); - llvm_unreachable("no empty buckets"); - } -}; - TEST(HeaderMapTest, checkHeaderEmpty) { bool NeedsSwap; ASSERT_FALSE(HeaderMapImpl::checkHeader( @@ -98,7 +26,7 @@ } TEST(HeaderMapTest, checkHeaderMagic) { - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); File.Header.Magic = 0; bool NeedsSwap; @@ -106,7 +34,7 @@ } TEST(HeaderMapTest, checkHeaderReserved) { - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); File.Header.Reserved = 1; bool NeedsSwap; @@ -114,7 +42,7 @@ } TEST(HeaderMapTest, checkHeaderVersion) { - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); ++File.Header.Version; bool NeedsSwap; @@ -122,7 +50,7 @@ } TEST(HeaderMapTest, checkHeaderValidButEmpty) { - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); bool NeedsSwap; ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); @@ -134,7 +62,7 @@ } TEST(HeaderMapTest, checkHeader3Buckets) { - MapFile<3, 1> File; + HMapFileMock<3, 1> File; ASSERT_EQ(3 * sizeof(HMapBucket), sizeof(File.Buckets)); File.init(); @@ -144,7 +72,7 @@ TEST(HeaderMapTest, checkHeader0Buckets) { // Create with 1 bucket to avoid 0-sized arrays. - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); File.Header.NumBuckets = 0; bool NeedsSwap; @@ -152,7 +80,7 @@ } TEST(HeaderMapTest, checkHeaderNotEnoughBuckets) { - MapFile<1, 1> File; + HMapFileMock<1, 1> File; File.init(); File.Header.NumBuckets = 8; bool NeedsSwap; @@ -160,15 +88,15 @@ } TEST(HeaderMapTest, lookupFilename) { - typedef MapFile<2, 7> FileTy; + typedef HMapFileMock<2, 7> FileTy; FileTy File; File.init(); - FileMaker Maker(File); + HMapFileMockMaker Maker(File); auto a = Maker.addString("a"); auto b = Maker.addString("b"); auto c = Maker.addString("c"); - Maker.addBucket(getHash("a"), a, b, c); + Maker.addBucket("a", a, b, c); bool NeedsSwap; ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap)); @@ -185,7 +113,8 @@ }; TEST(HeaderMapTest, lookupFilenameTruncatedSuffix) { - typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy; + typedef HMapFileMock<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> + FileTy; static_assert(std::is_standard_layout::value, "Expected standard layout"); static_assert(sizeof(FileTy) == 64, "check the math"); @@ -194,11 +123,11 @@ auto &Padding = P.Padding; File.init(); - FileMaker Maker(File); + HMapFileMockMaker Maker(File); auto a = Maker.addString("a"); auto b = Maker.addString("b"); auto c = Maker.addString("c"); - Maker.addBucket(getHash("a"), a, b, c); + Maker.addBucket("a", a, b, c); // Add 'x' characters to cause an overflow into Padding. ASSERT_EQ('c', File.Bytes[5]); @@ -220,7 +149,8 @@ } TEST(HeaderMapTest, lookupFilenameTruncatedPrefix) { - typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy; + typedef HMapFileMock<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> + FileTy; static_assert(std::is_standard_layout::value, "Expected standard layout"); static_assert(sizeof(FileTy) == 64, "check the math"); @@ -229,11 +159,11 @@ auto &Padding = P.Padding; File.init(); - FileMaker Maker(File); + HMapFileMockMaker Maker(File); auto a = Maker.addString("a"); auto c = Maker.addString("c"); auto b = Maker.addString("b"); // Store the prefix last. - Maker.addBucket(getHash("a"), a, b, c); + Maker.addBucket("a", a, b, c); // Add 'x' characters to cause an overflow into Padding. ASSERT_EQ('b', File.Bytes[5]); diff --git a/clang/unittests/Lex/HeaderMapTestUtils.h b/clang/unittests/Lex/HeaderMapTestUtils.h new file mode 100644 --- /dev/null +++ b/clang/unittests/Lex/HeaderMapTestUtils.h @@ -0,0 +1,100 @@ +//===- unittests/Lex/HeaderMapTestUtils.h - HeaderMap utils -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===--------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_LEX_HEADERMAPTESTUTILS_H +#define LLVM_CLANG_UNITTESTS_LEX_HEADERMAPTESTUTILS_H + +#include "clang/Basic/CharInfo.h" +#include "clang/Lex/HeaderMap.h" +#include "clang/Lex/HeaderMapTypes.h" +#include "llvm/Support/SwapByteOrder.h" +#include + +namespace clang { +namespace test { + +// Lay out a header file for testing. +template struct HMapFileMock { + HMapHeader Header; + HMapBucket Buckets[NumBuckets]; + unsigned char Bytes[NumBytes]; + + void init() { + memset(this, 0, sizeof(HMapFileMock)); + Header.Magic = HMAP_HeaderMagicNumber; + Header.Version = HMAP_HeaderVersion; + Header.NumBuckets = NumBuckets; + Header.StringsOffset = sizeof(Header) + sizeof(Buckets); + } + + void swapBytes() { + using llvm::sys::getSwappedBytes; + Header.Magic = getSwappedBytes(Header.Magic); + Header.Version = getSwappedBytes(Header.Version); + Header.NumBuckets = getSwappedBytes(Header.NumBuckets); + Header.StringsOffset = getSwappedBytes(Header.StringsOffset); + } + + std::unique_ptr getBuffer() { + return llvm::MemoryBuffer::getMemBuffer( + StringRef(reinterpret_cast(this), sizeof(HMapFileMock)), + "header", + /* RequresNullTerminator */ false); + } +}; + +template struct HMapFileMockMaker { + FileTy &File; + unsigned SI = 1; + unsigned BI = 0; + HMapFileMockMaker(FileTy &File) : File(File) {} + + unsigned addString(StringRef S) { + assert(SI + S.size() + 1 <= sizeof(File.Bytes)); + std::copy(S.begin(), S.end(), File.Bytes + SI); + auto OldSI = SI; + SI += S.size() + 1; + return OldSI; + } + + void addBucket(StringRef Str, unsigned Key, unsigned Prefix, + unsigned Suffix) { + addBucket(getHash(Str), Key, Prefix, Suffix); + } + + void addBucket(unsigned Hash, unsigned Key, unsigned Prefix, + unsigned Suffix) { + assert(!(File.Header.NumBuckets & (File.Header.NumBuckets - 1))); + unsigned I = Hash & (File.Header.NumBuckets - 1); + do { + if (!File.Buckets[I].Key) { + File.Buckets[I].Key = Key; + File.Buckets[I].Prefix = Prefix; + File.Buckets[I].Suffix = Suffix; + ++File.Header.NumEntries; + return; + } + ++I; + I &= File.Header.NumBuckets - 1; + } while (I != (Hash & (File.Header.NumBuckets - 1))); + llvm_unreachable("no empty buckets"); + } + + // The header map hash function. + static unsigned getHash(StringRef Str) { + unsigned Result = 0; + for (char C : Str) + Result += toLowercase(C) * 13; + return Result; + } +}; + +} // namespace test +} // namespace clang + +#endif diff --git a/clang/unittests/Lex/HeaderSearchTest.cpp b/clang/unittests/Lex/HeaderSearchTest.cpp --- a/clang/unittests/Lex/HeaderSearchTest.cpp +++ b/clang/unittests/Lex/HeaderSearchTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/Lex/HeaderSearch.h" +#include "HeaderMapTestUtils.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" @@ -45,6 +46,21 @@ Search.AddSearchPath(DL, /*isAngled=*/false); } + void addHeaderMap(llvm::StringRef Filename, + std::unique_ptr Buf) { + VFS->addFile(Filename, 0, std::move(Buf), /*User=*/None, /*Group=*/None, + llvm::sys::fs::file_type::regular_file); + auto FE = FileMgr.getFile(Filename, true); + assert(FE); + + // Test class supports only one HMap at a time. + assert(!HMap); + HMap = HeaderMap::Create(*FE, FileMgr); + auto DL = + DirectoryLookup(HMap.get(), SrcMgr::C_User, /*isFramework=*/false); + Search.AddSearchPath(DL, /*isAngled=*/false); + } + IntrusiveRefCntPtr VFS; FileSystemOptions FileMgrOpts; FileManager FileMgr; @@ -55,6 +71,7 @@ std::shared_ptr TargetOpts; IntrusiveRefCntPtr Target; HeaderSearch Search; + std::unique_ptr HMap; }; TEST_F(HeaderSearchTest, NoSearchDir) { @@ -136,5 +153,31 @@ "y/z/t.h"); } +// Helper struct with null terminator character to make MemoryBuffer happy. +template +struct NullTerminatedFile : public FileTy { + PaddingTy Padding = 0; +}; + +TEST_F(HeaderSearchTest, HeaderMapReverseLookup) { + typedef NullTerminatedFile, char> FileTy; + FileTy File; + File.init(); + + test::HMapFileMockMaker Maker(File); + auto a = Maker.addString("d.h"); + auto b = Maker.addString("b/"); + auto c = Maker.addString("c.h"); + Maker.addBucket("d.h", a, b, c); + + addHeaderMap("/x/y/z.hmap", File.getBuffer()); + addSearchDir("/a"); + + EXPECT_EQ(Search.suggestPathToFileForDiagnostics("/a/b/c.h", + /*WorkingDir=*/"", + /*MainFile=*/""), + "d.h"); +} + } // namespace } // namespace clang