1 //===- unittests/Lex/HeaderMapTest.cpp - HeaderMap tests ----------===//
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
7 //===--------------------------------------------------------------===//
9 #include "clang/Basic/CharInfo.h"
10 #include "clang/Lex/HeaderMap.h"
11 #include "clang/Lex/HeaderMapTypes.h"
12 #include "llvm/ADT/SmallString.h"
13 #include "llvm/Support/SwapByteOrder.h"
14 #include "gtest/gtest.h"
16 #include <type_traits>
18 using namespace clang
;
23 // Lay out a header file for testing.
24 template <unsigned NumBuckets
, unsigned NumBytes
> struct MapFile
{
26 HMapBucket Buckets
[NumBuckets
];
27 unsigned char Bytes
[NumBytes
];
30 memset(this, 0, sizeof(MapFile
));
31 Header
.Magic
= HMAP_HeaderMagicNumber
;
32 Header
.Version
= HMAP_HeaderVersion
;
33 Header
.NumBuckets
= NumBuckets
;
34 Header
.StringsOffset
= sizeof(Header
) + sizeof(Buckets
);
38 using llvm::sys::getSwappedBytes
;
39 Header
.Magic
= getSwappedBytes(Header
.Magic
);
40 Header
.Version
= getSwappedBytes(Header
.Version
);
41 Header
.NumBuckets
= getSwappedBytes(Header
.NumBuckets
);
42 Header
.StringsOffset
= getSwappedBytes(Header
.StringsOffset
);
45 std::unique_ptr
<const MemoryBuffer
> getBuffer() const {
46 return MemoryBuffer::getMemBuffer(
47 StringRef(reinterpret_cast<const char *>(this), sizeof(MapFile
)),
49 /* RequresNullTerminator */ false);
53 // The header map hash function.
54 static inline unsigned getHash(StringRef Str
) {
57 Result
+= toLowercase(C
) * 13;
61 template <class FileTy
> struct FileMaker
{
65 FileMaker(FileTy
&File
) : File(File
) {}
67 unsigned addString(StringRef S
) {
68 assert(SI
+ S
.size() + 1 <= sizeof(File
.Bytes
));
69 std::copy(S
.begin(), S
.end(), File
.Bytes
+ SI
);
74 void addBucket(unsigned Hash
, unsigned Key
, unsigned Prefix
, unsigned Suffix
) {
75 assert(!(File
.Header
.NumBuckets
& (File
.Header
.NumBuckets
- 1)));
76 unsigned I
= Hash
& (File
.Header
.NumBuckets
- 1);
78 if (!File
.Buckets
[I
].Key
) {
79 File
.Buckets
[I
].Key
= Key
;
80 File
.Buckets
[I
].Prefix
= Prefix
;
81 File
.Buckets
[I
].Suffix
= Suffix
;
82 ++File
.Header
.NumEntries
;
86 I
&= File
.Header
.NumBuckets
- 1;
87 } while (I
!= (Hash
& (File
.Header
.NumBuckets
- 1)));
88 llvm_unreachable("no empty buckets");
92 TEST(HeaderMapTest
, checkHeaderEmpty
) {
94 ASSERT_FALSE(HeaderMapImpl::checkHeader(
95 *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap
));
96 ASSERT_FALSE(HeaderMapImpl::checkHeader(
97 *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap
));
100 TEST(HeaderMapTest
, checkHeaderMagic
) {
103 File
.Header
.Magic
= 0;
105 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
108 TEST(HeaderMapTest
, checkHeaderReserved
) {
111 File
.Header
.Reserved
= 1;
113 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
116 TEST(HeaderMapTest
, checkHeaderVersion
) {
119 ++File
.Header
.Version
;
121 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
124 TEST(HeaderMapTest
, checkHeaderValidButEmpty
) {
128 ASSERT_TRUE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
129 ASSERT_FALSE(NeedsSwap
);
132 ASSERT_TRUE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
133 ASSERT_TRUE(NeedsSwap
);
136 TEST(HeaderMapTest
, checkHeader3Buckets
) {
138 ASSERT_EQ(3 * sizeof(HMapBucket
), sizeof(File
.Buckets
));
142 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
145 TEST(HeaderMapTest
, checkHeader0Buckets
) {
146 // Create with 1 bucket to avoid 0-sized arrays.
149 File
.Header
.NumBuckets
= 0;
151 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
154 TEST(HeaderMapTest
, checkHeaderNotEnoughBuckets
) {
157 File
.Header
.NumBuckets
= 8;
159 ASSERT_FALSE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
162 TEST(HeaderMapTest
, lookupFilename
) {
163 typedef MapFile
<2, 7> FileTy
;
167 FileMaker
<FileTy
> Maker(File
);
168 auto a
= Maker
.addString("a");
169 auto b
= Maker
.addString("b");
170 auto c
= Maker
.addString("c");
171 Maker
.addBucket(getHash("a"), a
, b
, c
);
174 ASSERT_TRUE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
175 ASSERT_FALSE(NeedsSwap
);
176 HeaderMapImpl
Map(File
.getBuffer(), NeedsSwap
);
178 SmallString
<8> DestPath
;
179 ASSERT_EQ("bc", Map
.lookupFilename("a", DestPath
));
182 template <class FileTy
, class PaddingTy
> struct PaddedFile
{
187 TEST(HeaderMapTest
, lookupFilenameTruncatedSuffix
) {
188 typedef MapFile
<2, 64 - sizeof(HMapHeader
) - 2 * sizeof(HMapBucket
)> FileTy
;
189 static_assert(std::is_standard_layout
<FileTy
>::value
,
190 "Expected standard layout");
191 static_assert(sizeof(FileTy
) == 64, "check the math");
192 PaddedFile
<FileTy
, uint64_t> P
;
194 auto &Padding
= P
.Padding
;
197 FileMaker
<FileTy
> Maker(File
);
198 auto a
= Maker
.addString("a");
199 auto b
= Maker
.addString("b");
200 auto c
= Maker
.addString("c");
201 Maker
.addBucket(getHash("a"), a
, b
, c
);
203 // Add 'x' characters to cause an overflow into Padding.
204 ASSERT_EQ('c', File
.Bytes
[5]);
205 for (unsigned I
= 6; I
< sizeof(File
.Bytes
); ++I
) {
206 ASSERT_EQ(0, File
.Bytes
[I
]);
209 Padding
= 0xffffffff; // Padding won't stop it either.
212 ASSERT_TRUE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
213 ASSERT_FALSE(NeedsSwap
);
214 HeaderMapImpl
Map(File
.getBuffer(), NeedsSwap
);
216 // The string for "c" runs to the end of File. Check that the suffix
217 // ("cxxxx...") is detected as truncated, and an empty string is returned.
218 SmallString
<24> DestPath
;
219 ASSERT_EQ("", Map
.lookupFilename("a", DestPath
));
222 TEST(HeaderMapTest
, lookupFilenameTruncatedPrefix
) {
223 typedef MapFile
<2, 64 - sizeof(HMapHeader
) - 2 * sizeof(HMapBucket
)> FileTy
;
224 static_assert(std::is_standard_layout
<FileTy
>::value
,
225 "Expected standard layout");
226 static_assert(sizeof(FileTy
) == 64, "check the math");
227 PaddedFile
<FileTy
, uint64_t> P
;
229 auto &Padding
= P
.Padding
;
232 FileMaker
<FileTy
> Maker(File
);
233 auto a
= Maker
.addString("a");
234 auto c
= Maker
.addString("c");
235 auto b
= Maker
.addString("b"); // Store the prefix last.
236 Maker
.addBucket(getHash("a"), a
, b
, c
);
238 // Add 'x' characters to cause an overflow into Padding.
239 ASSERT_EQ('b', File
.Bytes
[5]);
240 for (unsigned I
= 6; I
< sizeof(File
.Bytes
); ++I
) {
241 ASSERT_EQ(0, File
.Bytes
[I
]);
244 Padding
= 0xffffffff; // Padding won't stop it either.
247 ASSERT_TRUE(HeaderMapImpl::checkHeader(*File
.getBuffer(), NeedsSwap
));
248 ASSERT_FALSE(NeedsSwap
);
249 HeaderMapImpl
Map(File
.getBuffer(), NeedsSwap
);
251 // The string for "b" runs to the end of File. Check that the prefix
252 // ("bxxxx...") is detected as truncated, and an empty string is returned.
253 SmallString
<24> DestPath
;
254 ASSERT_EQ("", Map
.lookupFilename("a", DestPath
));