HeaderMapTest.cpp 7.61 KB
//===- 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