//===- 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 "HeaderMapTestUtils.h"
#include "llvm/ADT/SmallString.h"
#include "gtest/gtest.h"
#include <type_traits>

using namespace clang;
using namespace llvm;
using namespace clang::test;

namespace {

TEST(HeaderMapTest, checkHeaderEmpty) {
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(
      *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
  ASSERT_FALSE(HeaderMapImpl::checkHeader(
      *MemoryBuffer::getMemBufferCopy("", "empty"), NeedsSwap));
}

TEST(HeaderMapTest, checkHeaderMagic) {
  HMapFileMock<1, 1> File;
  File.init();
  File.Header.Magic = 0;
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
}

TEST(HeaderMapTest, checkHeaderReserved) {
  HMapFileMock<1, 1> File;
  File.init();
  File.Header.Reserved = 1;
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
}

TEST(HeaderMapTest, checkHeaderVersion) {
  HMapFileMock<1, 1> File;
  File.init();
  ++File.Header.Version;
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
}

TEST(HeaderMapTest, checkHeaderValidButEmpty) {
  HMapFileMock<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) {
  HMapFileMock<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.
  HMapFileMock<1, 1> File;
  File.init();
  File.Header.NumBuckets = 0;
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
}

TEST(HeaderMapTest, checkHeaderNotEnoughBuckets) {
  HMapFileMock<1, 1> File;
  File.init();
  File.Header.NumBuckets = 8;
  bool NeedsSwap;
  ASSERT_FALSE(HeaderMapImpl::checkHeader(*File.getBuffer(), NeedsSwap));
}

TEST(HeaderMapTest, lookupFilename) {
  typedef HMapFileMock<2, 7> FileTy;
  FileTy File;
  File.init();

  HMapFileMockMaker<FileTy> Maker(File);
  auto a = Maker.addString("a");
  auto b = Maker.addString("b");
  auto c = Maker.addString("c");
  Maker.addBucket("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 HMapFileMock<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();

  HMapFileMockMaker<FileTy> Maker(File);
  auto a = Maker.addString("a");
  auto b = Maker.addString("b");
  auto c = Maker.addString("c");
  Maker.addBucket("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 HMapFileMock<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();

  HMapFileMockMaker<FileTy> Maker(File);
  auto a = Maker.addString("a");
  auto c = Maker.addString("c");
  auto b = Maker.addString("b"); // Store the prefix last.
  Maker.addBucket("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
