258 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- unittests/Lex/HeaderMapTest.cpp - HeaderMap tests ----------===//
 | |
| //
 | |
| // 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
 | |
| //
 | |
| //===--------------------------------------------------------------===//
 | |
| 
 | |
| #include "clang/Basic/CharInfo.h"
 | |
| #include "clang/Lex/HeaderMap.h"
 | |
| #include "clang/Lex/HeaderMapTypes.h"
 | |
| #include "llvm/ADT/SmallString.h"
 | |
| #include "llvm/Support/SwapByteOrder.h"
 | |
| #include "gtest/gtest.h"
 | |
| #include <cassert>
 | |
| #include <type_traits>
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace llvm;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // Lay out a header file for testing.
 | |
| template <unsigned NumBuckets, unsigned NumBytes> 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<const MemoryBuffer> getBuffer() const {
 | |
|     return MemoryBuffer::getMemBuffer(
 | |
|         StringRef(reinterpret_cast<const char *>(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 <class FileTy> 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(
 | |
|       *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(
 | |
|       *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeaderMagic) {
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   File.Header.Magic = 0;
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeaderReserved) {
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   File.Header.Reserved = 1;
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeaderVersion) {
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   ++File.Header.Version;
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeaderValidButEmpty) {
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
|   ASSERT_FALSE(NeedsSwap);
 | |
| 
 | |
|   File.swapBytes();
 | |
|   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
|   ASSERT_TRUE(NeedsSwap);
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeader3Buckets) {
 | |
|   MapFile<3, 1> File;
 | |
|   ASSERT_EQ(3 * sizeof(HMapBucket), sizeof(File.Buckets));
 | |
| 
 | |
|   File.init();
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeader0Buckets) {
 | |
|   // Create with 1 bucket to avoid 0-sized arrays.
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   File.Header.NumBuckets = 0;
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, checkHeaderNotEnoughBuckets) {
 | |
|   MapFile<1, 1> File;
 | |
|   File.init();
 | |
|   File.Header.NumBuckets = 8;
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, lookupFilename) {
 | |
|   typedef MapFile<2, 7> FileTy;
 | |
|   FileTy File;
 | |
|   File.init();
 | |
| 
 | |
|   FileMaker<FileTy> Maker(File);
 | |
|   auto a = Maker.addString("a");
 | |
|   auto b = Maker.addString("b");
 | |
|   auto c = Maker.addString("c");
 | |
|   Maker.addBucket(getHash("a"), a, b, c);
 | |
| 
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
|   ASSERT_FALSE(NeedsSwap);
 | |
|   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
 | |
| 
 | |
|   SmallString<8> DestPath;
 | |
|   ASSERT_EQ("bc", Map.lookupFilename("a", DestPath));
 | |
| }
 | |
| 
 | |
| template <class FileTy, class PaddingTy> struct PaddedFile {
 | |
|   FileTy File;
 | |
|   PaddingTy Padding;
 | |
| };
 | |
| 
 | |
| TEST(HeaderMapTest, lookupFilenameTruncatedSuffix) {
 | |
|   typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy;
 | |
|   static_assert(std::is_standard_layout<FileTy>::value,
 | |
|                 "Expected standard layout");
 | |
|   static_assert(sizeof(FileTy) == 64, "check the math");
 | |
|   PaddedFile<FileTy, uint64_t> P;
 | |
|   auto &File = P.File;
 | |
|   auto &Padding = P.Padding;
 | |
|   File.init();
 | |
| 
 | |
|   FileMaker<FileTy> Maker(File);
 | |
|   auto a = Maker.addString("a");
 | |
|   auto b = Maker.addString("b");
 | |
|   auto c = Maker.addString("c");
 | |
|   Maker.addBucket(getHash("a"), a, b, c);
 | |
| 
 | |
|   // Add 'x' characters to cause an overflow into Padding.
 | |
|   ASSERT_EQ('c', File.Bytes[5]);
 | |
|   for (unsigned I = 6; I < sizeof(File.Bytes); ++I) {
 | |
|     ASSERT_EQ(0, File.Bytes[I]);
 | |
|     File.Bytes[I] = 'x';
 | |
|   }
 | |
|   Padding = 0xffffffff; // Padding won't stop it either.
 | |
| 
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
|   ASSERT_FALSE(NeedsSwap);
 | |
|   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
 | |
| 
 | |
|   // The string for "c" runs to the end of File.  Check that the suffix
 | |
|   // ("cxxxx...") is detected as truncated, and an empty string is returned.
 | |
|   SmallString<24> DestPath;
 | |
|   ASSERT_EQ("", Map.lookupFilename("a", DestPath));
 | |
| }
 | |
| 
 | |
| TEST(HeaderMapTest, lookupFilenameTruncatedPrefix) {
 | |
|   typedef MapFile<2, 64 - sizeof(HMapHeader) - 2 * sizeof(HMapBucket)> FileTy;
 | |
|   static_assert(std::is_standard_layout<FileTy>::value,
 | |
|                 "Expected standard layout");
 | |
|   static_assert(sizeof(FileTy) == 64, "check the math");
 | |
|   PaddedFile<FileTy, uint64_t> P;
 | |
|   auto &File = P.File;
 | |
|   auto &Padding = P.Padding;
 | |
|   File.init();
 | |
| 
 | |
|   FileMaker<FileTy> 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);
 | |
| 
 | |
|   // Add 'x' characters to cause an overflow into Padding.
 | |
|   ASSERT_EQ('b', File.Bytes[5]);
 | |
|   for (unsigned I = 6; I < sizeof(File.Bytes); ++I) {
 | |
|     ASSERT_EQ(0, File.Bytes[I]);
 | |
|     File.Bytes[I] = 'x';
 | |
|   }
 | |
|   Padding = 0xffffffff; // Padding won't stop it either.
 | |
| 
 | |
|   bool NeedsSwap;
 | |
|   ASSERT_TRUE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
 | |
|   ASSERT_FALSE(NeedsSwap);
 | |
|   HeaderMapImpl Map(File.getBuffer(), NeedsSwap);
 | |
| 
 | |
|   // The string for "b" runs to the end of File.  Check that the prefix
 | |
|   // ("bxxxx...") is detected as truncated, and an empty string is returned.
 | |
|   SmallString<24> DestPath;
 | |
|   ASSERT_EQ("", Map.lookupFilename("a", DestPath));
 | |
| }
 | |
| 
 | |
| } // end namespace
 |