19ab99eecSDuncan P. N. Exon Smith //===- unittests/Lex/HeaderMapTest.cpp - HeaderMap tests ----------===//
29ab99eecSDuncan P. N. Exon Smith //
3*2946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*2946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
5*2946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
69ab99eecSDuncan P. N. Exon Smith //
79ab99eecSDuncan P. N. Exon Smith //===--------------------------------------------------------------===//
89ab99eecSDuncan P. N. Exon Smith 
908fd70faSDuncan P. N. Exon Smith #include "clang/Basic/CharInfo.h"
109ab99eecSDuncan P. N. Exon Smith #include "clang/Lex/HeaderMap.h"
119ab99eecSDuncan P. N. Exon Smith #include "clang/Lex/HeaderMapTypes.h"
1208fd70faSDuncan P. N. Exon Smith #include "llvm/ADT/SmallString.h"
139ab99eecSDuncan P. N. Exon Smith #include "llvm/Support/SwapByteOrder.h"
149ab99eecSDuncan P. N. Exon Smith #include "gtest/gtest.h"
1508fd70faSDuncan P. N. Exon Smith #include <cassert>
1696a01fa0SDuncan P. N. Exon Smith #include <type_traits>
179ab99eecSDuncan P. N. Exon Smith 
189ab99eecSDuncan P. N. Exon Smith using namespace clang;
199ab99eecSDuncan P. N. Exon Smith using namespace llvm;
209ab99eecSDuncan P. N. Exon Smith 
219ab99eecSDuncan P. N. Exon Smith namespace {
229ab99eecSDuncan P. N. Exon Smith 
239ab99eecSDuncan P. N. Exon Smith // Lay out a header file for testing.
249ab99eecSDuncan P. N. Exon Smith template <unsigned NumBuckets, unsigned NumBytes> struct MapFile {
259ab99eecSDuncan P. N. Exon Smith   HMapHeader Header;
269ab99eecSDuncan P. N. Exon Smith   HMapBucket Buckets[NumBuckets];
279ab99eecSDuncan P. N. Exon Smith   unsigned char Bytes[NumBytes];
289ab99eecSDuncan P. N. Exon Smith 
299ab99eecSDuncan P. N. Exon Smith   void init() {
309ab99eecSDuncan P. N. Exon Smith     memset(this, 0, sizeof(MapFile));
319ab99eecSDuncan P. N. Exon Smith     Header.Magic = HMAP_HeaderMagicNumber;
329ab99eecSDuncan P. N. Exon Smith     Header.Version = HMAP_HeaderVersion;
339ab99eecSDuncan P. N. Exon Smith     Header.NumBuckets = NumBuckets;
349ab99eecSDuncan P. N. Exon Smith     Header.StringsOffset = sizeof(Header) + sizeof(Buckets);
359ab99eecSDuncan P. N. Exon Smith   }
369ab99eecSDuncan P. N. Exon Smith 
379ab99eecSDuncan P. N. Exon Smith   void swapBytes() {
389ab99eecSDuncan P. N. Exon Smith     using llvm::sys::getSwappedBytes;
399ab99eecSDuncan P. N. Exon Smith     Header.Magic = getSwappedBytes(Header.Magic);
409ab99eecSDuncan P. N. Exon Smith     Header.Version = getSwappedBytes(Header.Version);
419ab99eecSDuncan P. N. Exon Smith     Header.NumBuckets = getSwappedBytes(Header.NumBuckets);
429ab99eecSDuncan P. N. Exon Smith     Header.StringsOffset = getSwappedBytes(Header.StringsOffset);
439ab99eecSDuncan P. N. Exon Smith   }
449ab99eecSDuncan P. N. Exon Smith 
459ab99eecSDuncan P. N. Exon Smith   std::unique_ptr<const MemoryBuffer> getBuffer() const {
469ab99eecSDuncan P. N. Exon Smith     return MemoryBuffer::getMemBuffer(
479ab99eecSDuncan P. N. Exon Smith         StringRef(reinterpret_cast<const char *>(this), sizeof(MapFile)),
489ab99eecSDuncan P. N. Exon Smith         "header",
499ab99eecSDuncan P. N. Exon Smith         /* RequresNullTerminator */ false);
509ab99eecSDuncan P. N. Exon Smith   }
519ab99eecSDuncan P. N. Exon Smith };
529ab99eecSDuncan P. N. Exon Smith 
5308fd70faSDuncan P. N. Exon Smith // The header map hash function.
5408fd70faSDuncan P. N. Exon Smith static inline unsigned getHash(StringRef Str) {
5508fd70faSDuncan P. N. Exon Smith   unsigned Result = 0;
5608fd70faSDuncan P. N. Exon Smith   for (char C : Str)
5708fd70faSDuncan P. N. Exon Smith     Result += toLowercase(C) * 13;
5808fd70faSDuncan P. N. Exon Smith   return Result;
5908fd70faSDuncan P. N. Exon Smith }
6008fd70faSDuncan P. N. Exon Smith 
6108fd70faSDuncan P. N. Exon Smith template <class FileTy> struct FileMaker {
6208fd70faSDuncan P. N. Exon Smith   FileTy &File;
6308fd70faSDuncan P. N. Exon Smith   unsigned SI = 1;
6408fd70faSDuncan P. N. Exon Smith   unsigned BI = 0;
6508fd70faSDuncan P. N. Exon Smith   FileMaker(FileTy &File) : File(File) {}
6608fd70faSDuncan P. N. Exon Smith 
6708fd70faSDuncan P. N. Exon Smith   unsigned addString(StringRef S) {
6808fd70faSDuncan P. N. Exon Smith     assert(SI + S.size() + 1 <= sizeof(File.Bytes));
6908fd70faSDuncan P. N. Exon Smith     std::copy(S.begin(), S.end(), File.Bytes + SI);
7008fd70faSDuncan P. N. Exon Smith     auto OldSI = SI;
7108fd70faSDuncan P. N. Exon Smith     SI += S.size() + 1;
7208fd70faSDuncan P. N. Exon Smith     return OldSI;
7308fd70faSDuncan P. N. Exon Smith   }
7408fd70faSDuncan P. N. Exon Smith   void addBucket(unsigned Hash, unsigned Key, unsigned Prefix, unsigned Suffix) {
7508fd70faSDuncan P. N. Exon Smith     assert(!(File.Header.NumBuckets & (File.Header.NumBuckets - 1)));
7608fd70faSDuncan P. N. Exon Smith     unsigned I = Hash & (File.Header.NumBuckets - 1);
7708fd70faSDuncan P. N. Exon Smith     do {
7808fd70faSDuncan P. N. Exon Smith       if (!File.Buckets[I].Key) {
7908fd70faSDuncan P. N. Exon Smith         File.Buckets[I].Key = Key;
8008fd70faSDuncan P. N. Exon Smith         File.Buckets[I].Prefix = Prefix;
8108fd70faSDuncan P. N. Exon Smith         File.Buckets[I].Suffix = Suffix;
8208fd70faSDuncan P. N. Exon Smith         ++File.Header.NumEntries;
8308fd70faSDuncan P. N. Exon Smith         return;
8408fd70faSDuncan P. N. Exon Smith       }
8508fd70faSDuncan P. N. Exon Smith       ++I;
8608fd70faSDuncan P. N. Exon Smith       I &= File.Header.NumBuckets - 1;
8708fd70faSDuncan P. N. Exon Smith     } while (I != (Hash & (File.Header.NumBuckets - 1)));
8808fd70faSDuncan P. N. Exon Smith     llvm_unreachable("no empty buckets");
8908fd70faSDuncan P. N. Exon Smith   }
9008fd70faSDuncan P. N. Exon Smith };
9108fd70faSDuncan P. N. Exon Smith 
929ab99eecSDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderEmpty) {
939ab99eecSDuncan P. N. Exon Smith   bool NeedsSwap;
949ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(
959ab99eecSDuncan P. N. Exon Smith       *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
969ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(
979ab99eecSDuncan P. N. Exon Smith       *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
989ab99eecSDuncan P. N. Exon Smith }
999ab99eecSDuncan P. N. Exon Smith 
1009ab99eecSDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderMagic) {
1019ab99eecSDuncan P. N. Exon Smith   MapFile<1, 1> File;
1029ab99eecSDuncan P. N. Exon Smith   File.init();
1039ab99eecSDuncan P. N. Exon Smith   File.Header.Magic = 0;
1049ab99eecSDuncan P. N. Exon Smith   bool NeedsSwap;
1059ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1069ab99eecSDuncan P. N. Exon Smith }
1079ab99eecSDuncan P. N. Exon Smith 
1089ab99eecSDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderReserved) {
1099ab99eecSDuncan P. N. Exon Smith   MapFile<1, 1> File;
1109ab99eecSDuncan P. N. Exon Smith   File.init();
1119ab99eecSDuncan P. N. Exon Smith   File.Header.Reserved = 1;
1129ab99eecSDuncan P. N. Exon Smith   bool NeedsSwap;
1139ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1149ab99eecSDuncan P. N. Exon Smith }
1159ab99eecSDuncan P. N. Exon Smith 
1169ab99eecSDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderVersion) {
1179ab99eecSDuncan P. N. Exon Smith   MapFile<1, 1> File;
1189ab99eecSDuncan P. N. Exon Smith   File.init();
1199ab99eecSDuncan P. N. Exon Smith   ++File.Header.Version;
1209ab99eecSDuncan P. N. Exon Smith   bool NeedsSwap;
1219ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1229ab99eecSDuncan P. N. Exon Smith }
1239ab99eecSDuncan P. N. Exon Smith 
1249ab99eecSDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderValidButEmpty) {
1259ab99eecSDuncan P. N. Exon Smith   MapFile<1, 1> File;
1269ab99eecSDuncan P. N. Exon Smith   File.init();
1279ab99eecSDuncan P. N. Exon Smith   bool NeedsSwap;
1289ab99eecSDuncan P. N. Exon Smith   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1299ab99eecSDuncan P. N. Exon Smith   ASSERT_FALSE(NeedsSwap);
1309ab99eecSDuncan P. N. Exon Smith 
1319ab99eecSDuncan P. N. Exon Smith   File.swapBytes();
1329ab99eecSDuncan P. N. Exon Smith   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1339ab99eecSDuncan P. N. Exon Smith   ASSERT_TRUE(NeedsSwap);
1349ab99eecSDuncan P. N. Exon Smith }
1359ab99eecSDuncan P. N. Exon Smith 
136dfe85305SDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeader3Buckets) {
137dfe85305SDuncan P. N. Exon Smith   MapFile<3, 1> File;
138dfe85305SDuncan P. N. Exon Smith   ASSERT_EQ(3 * sizeof(HMapBucket), sizeof(File.Buckets));
139dfe85305SDuncan P. N. Exon Smith 
140dfe85305SDuncan P. N. Exon Smith   File.init();
141dfe85305SDuncan P. N. Exon Smith   bool NeedsSwap;
142dfe85305SDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
143dfe85305SDuncan P. N. Exon Smith }
144dfe85305SDuncan P. N. Exon Smith 
1453a7def09SDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeader0Buckets) {
1463a7def09SDuncan P. N. Exon Smith   // Create with 1 bucket to avoid 0-sized arrays.
1473a7def09SDuncan P. N. Exon Smith   MapFile<1, 1> File;
1483a7def09SDuncan P. N. Exon Smith   File.init();
1493a7def09SDuncan P. N. Exon Smith   File.Header.NumBuckets = 0;
1503a7def09SDuncan P. N. Exon Smith   bool NeedsSwap;
1513a7def09SDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1523a7def09SDuncan P. N. Exon Smith }
1533a7def09SDuncan P. N. Exon Smith 
1548d6a31c0SDuncan P. N. Exon Smith TEST(HeaderMapTest, checkHeaderNotEnoughBuckets) {
1558d6a31c0SDuncan P. N. Exon Smith   MapFile<1, 1> File;
1568d6a31c0SDuncan P. N. Exon Smith   File.init();
1578d6a31c0SDuncan P. N. Exon Smith   File.Header.NumBuckets = 8;
1588d6a31c0SDuncan P. N. Exon Smith   bool NeedsSwap;
1598d6a31c0SDuncan P. N. Exon Smith   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
1608d6a31c0SDuncan P. N. Exon Smith }
1618d6a31c0SDuncan P. N. Exon Smith 
16208fd70faSDuncan P. N. Exon Smith TEST(HeaderMapTest, lookupFilename) {
16308fd70faSDuncan P. N. Exon Smith   typedef MapFile<2, 7> FileTy;
16408fd70faSDuncan P. N. Exon Smith   FileTy File;
16508fd70faSDuncan P. N. Exon Smith   File.init();
16608fd70faSDuncan P. N. Exon Smith 
16708fd70faSDuncan P. N. Exon Smith   FileMaker<FileTy> Maker(File);
16808fd70faSDuncan P. N. Exon Smith   auto a = Maker.addString("a");
16908fd70faSDuncan P. N. Exon Smith   auto b = Maker.addString("b");
17008fd70faSDuncan P. N. Exon Smith   auto c = Maker.addString("c");
17108fd70faSDuncan P. N. Exon Smith   Maker.addBucket(getHash("a"), a, b, c);
17208fd70faSDuncan P. N. Exon Smith 
17308fd70faSDuncan P. N. Exon Smith   bool NeedsSwap;
17408fd70faSDuncan P. N. Exon Smith   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
17508fd70faSDuncan P. N. Exon Smith   ASSERT_FALSE(NeedsSwap);
17608fd70faSDuncan P. N. Exon Smith   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
17708fd70faSDuncan P. N. Exon Smith 
17808fd70faSDuncan P. N. Exon Smith   SmallString<8> DestPath;
17908fd70faSDuncan P. N. Exon Smith   ASSERT_EQ("bc", Map.lookupFilename("a", DestPath));
18008fd70faSDuncan P. N. Exon Smith }
18108fd70faSDuncan P. N. Exon Smith 
18296a01fa0SDuncan P. N. Exon Smith template <class FileTy, class PaddingTy> struct PaddedFile {
18396a01fa0SDuncan P. N. Exon Smith   FileTy File;
18496a01fa0SDuncan P. N. Exon Smith   PaddingTy Padding;
18596a01fa0SDuncan P. N. Exon Smith };
18696a01fa0SDuncan P. N. Exon Smith 
187b5ce11fcSDuncan P. N. Exon Smith TEST(HeaderMapTest, lookupFilenameTruncatedSuffix) {
18896a01fa0SDuncan P. N. Exon Smith   typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy;
18996a01fa0SDuncan P. N. Exon Smith   static_assert(std::is_standard_layout<FileTy>::value,
19096a01fa0SDuncan P. N. Exon Smith                 "Expected standard layout");
19196a01fa0SDuncan P. N. Exon Smith   static_assert(sizeof(FileTy) == 64, "check the math");
19296a01fa0SDuncan P. N. Exon Smith   PaddedFile<FileTy, uint64_t> P;
19396a01fa0SDuncan P. N. Exon Smith   auto &File = P.File;
19496a01fa0SDuncan P. N. Exon Smith   auto &Padding = P.Padding;
19596a01fa0SDuncan P. N. Exon Smith   File.init();
19696a01fa0SDuncan P. N. Exon Smith 
19796a01fa0SDuncan P. N. Exon Smith   FileMaker<FileTy> Maker(File);
19896a01fa0SDuncan P. N. Exon Smith   auto a = Maker.addString("a");
19996a01fa0SDuncan P. N. Exon Smith   auto b = Maker.addString("b");
20096a01fa0SDuncan P. N. Exon Smith   auto c = Maker.addString("c");
20196a01fa0SDuncan P. N. Exon Smith   Maker.addBucket(getHash("a"), a, b, c);
20296a01fa0SDuncan P. N. Exon Smith 
20396a01fa0SDuncan P. N. Exon Smith   // Add 'x' characters to cause an overflow into Padding.
20496a01fa0SDuncan P. N. Exon Smith   ASSERT_EQ('c', File.Bytes[5]);
20596a01fa0SDuncan P. N. Exon Smith   for (unsigned I = 6; I < sizeof(File.Bytes); ++I) {
20696a01fa0SDuncan P. N. Exon Smith     ASSERT_EQ(0, File.Bytes[I]);
20796a01fa0SDuncan P. N. Exon Smith     File.Bytes[I] = 'x';
20896a01fa0SDuncan P. N. Exon Smith   }
20996a01fa0SDuncan P. N. Exon Smith   Padding = 0xffffffff; // Padding won't stop it either.
21096a01fa0SDuncan P. N. Exon Smith 
21196a01fa0SDuncan P. N. Exon Smith   bool NeedsSwap;
21296a01fa0SDuncan P. N. Exon Smith   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
21396a01fa0SDuncan P. N. Exon Smith   ASSERT_FALSE(NeedsSwap);
21496a01fa0SDuncan P. N. Exon Smith   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
21596a01fa0SDuncan P. N. Exon Smith 
21696a01fa0SDuncan P. N. Exon Smith   // The string for "c" runs to the end of File.  Check that the suffix
217b5ce11fcSDuncan P. N. Exon Smith   // ("cxxxx...") is detected as truncated, and an empty string is returned.
21896a01fa0SDuncan P. N. Exon Smith   SmallString<24> DestPath;
219b5ce11fcSDuncan P. N. Exon Smith   ASSERT_EQ("", Map.lookupFilename("a", DestPath));
220b5ce11fcSDuncan P. N. Exon Smith }
221b5ce11fcSDuncan P. N. Exon Smith 
222b5ce11fcSDuncan P. N. Exon Smith TEST(HeaderMapTest, lookupFilenameTruncatedPrefix) {
223b5ce11fcSDuncan P. N. Exon Smith   typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy;
224b5ce11fcSDuncan P. N. Exon Smith   static_assert(std::is_standard_layout<FileTy>::value,
225b5ce11fcSDuncan P. N. Exon Smith                 "Expected standard layout");
226b5ce11fcSDuncan P. N. Exon Smith   static_assert(sizeof(FileTy) == 64, "check the math");
227b5ce11fcSDuncan P. N. Exon Smith   PaddedFile<FileTy, uint64_t> P;
228b5ce11fcSDuncan P. N. Exon Smith   auto &File = P.File;
229b5ce11fcSDuncan P. N. Exon Smith   auto &Padding = P.Padding;
230b5ce11fcSDuncan P. N. Exon Smith   File.init();
231b5ce11fcSDuncan P. N. Exon Smith 
232b5ce11fcSDuncan P. N. Exon Smith   FileMaker<FileTy> Maker(File);
233b5ce11fcSDuncan P. N. Exon Smith   auto a = Maker.addString("a");
234b5ce11fcSDuncan P. N. Exon Smith   auto c = Maker.addString("c");
235b5ce11fcSDuncan P. N. Exon Smith   auto b = Maker.addString("b"); // Store the prefix last.
236b5ce11fcSDuncan P. N. Exon Smith   Maker.addBucket(getHash("a"), a, b, c);
237b5ce11fcSDuncan P. N. Exon Smith 
238b5ce11fcSDuncan P. N. Exon Smith   // Add 'x' characters to cause an overflow into Padding.
239b5ce11fcSDuncan P. N. Exon Smith   ASSERT_EQ('b', File.Bytes[5]);
240b5ce11fcSDuncan P. N. Exon Smith   for (unsigned I = 6; I < sizeof(File.Bytes); ++I) {
241b5ce11fcSDuncan P. N. Exon Smith     ASSERT_EQ(0, File.Bytes[I]);
242b5ce11fcSDuncan P. N. Exon Smith     File.Bytes[I] = 'x';
243b5ce11fcSDuncan P. N. Exon Smith   }
244b5ce11fcSDuncan P. N. Exon Smith   Padding = 0xffffffff; // Padding won't stop it either.
245b5ce11fcSDuncan P. N. Exon Smith 
246b5ce11fcSDuncan P. N. Exon Smith   bool NeedsSwap;
247b5ce11fcSDuncan P. N. Exon Smith   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
248b5ce11fcSDuncan P. N. Exon Smith   ASSERT_FALSE(NeedsSwap);
249b5ce11fcSDuncan P. N. Exon Smith   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
250b5ce11fcSDuncan P. N. Exon Smith 
251b5ce11fcSDuncan P. N. Exon Smith   // The string for "b" runs to the end of File.  Check that the prefix
252b5ce11fcSDuncan P. N. Exon Smith   // ("bxxxx...") is detected as truncated, and an empty string is returned.
253b5ce11fcSDuncan P. N. Exon Smith   SmallString<24> DestPath;
254b5ce11fcSDuncan P. N. Exon Smith   ASSERT_EQ("", Map.lookupFilename("a", DestPath));
25596a01fa0SDuncan P. N. Exon Smith }
25696a01fa0SDuncan P. N. Exon Smith 
2579ab99eecSDuncan P. N. Exon Smith } // end namespace
258