//===-- asan_mem_test.cc --------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer, an address sanity checker.
//
//===----------------------------------------------------------------------===//
#include "asan_test_utils.h"

template<typename T>
void MemSetOOBTestTemplate(size_t length) {
  if (length == 0) return;
  size_t size = Ident(sizeof(T) * length);
  T *array = Ident((T*)malloc(size));
  int element = Ident(42);
  int zero = Ident(0);
  void *(*MEMSET)(void *s, int c, size_t n) = Ident(memset);
  // memset interval inside array
  MEMSET(array, element, size);
  MEMSET(array, element, size - 1);
  MEMSET(array + length - 1, element, sizeof(T));
  MEMSET(array, element, 1);

  // memset 0 bytes
  MEMSET(array - 10, element, zero);
  MEMSET(array - 1, element, zero);
  MEMSET(array, element, zero);
  MEMSET(array + length, 0, zero);
  MEMSET(array + length + 1, 0, zero);

  // try to memset bytes to the right of array
  EXPECT_DEATH(MEMSET(array, 0, size + 1),
               RightOOBWriteMessage(0));
  EXPECT_DEATH(MEMSET((char*)(array + length) - 1, element, 6),
               RightOOBWriteMessage(0));
  EXPECT_DEATH(MEMSET(array + 1, element, size + sizeof(T)),
               RightOOBWriteMessage(0));
  // whole interval is to the right
  EXPECT_DEATH(MEMSET(array + length + 1, 0, 10),
               RightOOBWriteMessage(sizeof(T)));

  // try to memset bytes to the left of array
  EXPECT_DEATH(MEMSET((char*)array - 1, element, size),
               LeftOOBWriteMessage(1));
  EXPECT_DEATH(MEMSET((char*)array - 5, 0, 6),
               LeftOOBWriteMessage(5));
  if (length >= 100) {
    // Large OOB, we find it only if the redzone is large enough.
    EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)),
                 LeftOOBWriteMessage(5 * sizeof(T)));
  }
  // whole interval is to the left
  EXPECT_DEATH(MEMSET(array - 2, 0, sizeof(T)),
               LeftOOBWriteMessage(2 * sizeof(T)));

  // try to memset bytes both to the left & to the right
  EXPECT_DEATH(MEMSET((char*)array - 2, element, size + 4),
               LeftOOBWriteMessage(2));

  free(array);
}

TEST(AddressSanitizer, MemSetOOBTest) {
  MemSetOOBTestTemplate<char>(100);
  MemSetOOBTestTemplate<int>(5);
  MemSetOOBTestTemplate<double>(256);
  // We can test arrays of structres/classes here, but what for?
}

// Try to allocate two arrays of 'size' bytes that are near each other.
// Strictly speaking we are not guaranteed to find such two pointers,
// but given the structure of asan's allocator we will.
static bool AllocateTwoAdjacentArrays(char **x1, char **x2, size_t size) {
  vector<uintptr_t> v;
  bool res = false;
  for (size_t i = 0; i < 1000U && !res; i++) {
    v.push_back(reinterpret_cast<uintptr_t>(new char[size]));
    if (i == 0) continue;
    sort(v.begin(), v.end());
    for (size_t j = 1; j < v.size(); j++) {
      assert(v[j] > v[j-1]);
      if ((size_t)(v[j] - v[j-1]) < size * 2) {
        *x2 = reinterpret_cast<char*>(v[j]);
        *x1 = reinterpret_cast<char*>(v[j-1]);
        res = true;
        break;
      }
    }
  }

  for (size_t i = 0; i < v.size(); i++) {
    char *p = reinterpret_cast<char *>(v[i]);
    if (res && p == *x1) continue;
    if (res && p == *x2) continue;
    delete [] p;
  }
  return res;
}

TEST(AddressSanitizer, LargeOOBInMemset) {
  for (size_t size = 200; size < 100000; size += size / 2) {
    char *x1, *x2;
    if (!Ident(AllocateTwoAdjacentArrays)(&x1, &x2, size))
      continue;
    // fprintf(stderr, "  large oob memset: %p %p %zd\n", x1, x2, size);
    // Do a memset on x1 with huge out-of-bound access that will end up in x2.
    EXPECT_DEATH(Ident(memset)(x1, 0, size * 2),
                 "is located 0 bytes to the right");
    delete [] x1;
    delete [] x2;
    return;
  }
  assert(0 && "Did not find two adjacent malloc-ed pointers");
}

// Same test for memcpy and memmove functions
template <typename T, class M>
void MemTransferOOBTestTemplate(size_t length) {
  if (length == 0) return;
  size_t size = Ident(sizeof(T) * length);
  T *src = Ident((T*)malloc(size));
  T *dest = Ident((T*)malloc(size));
  int zero = Ident(0);

  // valid transfer of bytes between arrays
  M::transfer(dest, src, size);
  M::transfer(dest + 1, src, size - sizeof(T));
  M::transfer(dest, src + length - 1, sizeof(T));
  M::transfer(dest, src, 1);

  // transfer zero bytes
  M::transfer(dest - 1, src, 0);
  M::transfer(dest + length, src, zero);
  M::transfer(dest, src - 1, zero);
  M::transfer(dest, src, zero);

  // try to change mem to the right of dest
  EXPECT_DEATH(M::transfer(dest + 1, src, size),
               RightOOBWriteMessage(0));
  EXPECT_DEATH(M::transfer((char*)(dest + length) - 1, src, 5),
               RightOOBWriteMessage(0));

  // try to change mem to the left of dest
  EXPECT_DEATH(M::transfer(dest - 2, src, size),
               LeftOOBWriteMessage(2 * sizeof(T)));
  EXPECT_DEATH(M::transfer((char*)dest - 3, src, 4),
               LeftOOBWriteMessage(3));

  // try to access mem to the right of src
  EXPECT_DEATH(M::transfer(dest, src + 2, size),
               RightOOBReadMessage(0));
  EXPECT_DEATH(M::transfer(dest, (char*)(src + length) - 3, 6),
               RightOOBReadMessage(0));

  // try to access mem to the left of src
  EXPECT_DEATH(M::transfer(dest, src - 1, size),
               LeftOOBReadMessage(sizeof(T)));
  EXPECT_DEATH(M::transfer(dest, (char*)src - 6, 7),
               LeftOOBReadMessage(6));

  // Generally we don't need to test cases where both accessing src and writing
  // to dest address to poisoned memory.

  T *big_src = Ident((T*)malloc(size * 2));
  T *big_dest = Ident((T*)malloc(size * 2));
  // try to change mem to both sides of dest
  EXPECT_DEATH(M::transfer(dest - 1, big_src, size * 2),
               LeftOOBWriteMessage(sizeof(T)));
  // try to access mem to both sides of src
  EXPECT_DEATH(M::transfer(big_dest, src - 2, size * 2),
               LeftOOBReadMessage(2 * sizeof(T)));

  free(src);
  free(dest);
  free(big_src);
  free(big_dest);
}

class MemCpyWrapper {
 public:
  static void* transfer(void *to, const void *from, size_t size) {
    return Ident(memcpy)(to, from, size);
  }
};

TEST(AddressSanitizer, MemCpyOOBTest) {
  MemTransferOOBTestTemplate<char, MemCpyWrapper>(100);
  MemTransferOOBTestTemplate<int, MemCpyWrapper>(1024);
}

class MemMoveWrapper {
 public:
  static void* transfer(void *to, const void *from, size_t size) {
    return Ident(memmove)(to, from, size);
  }
};

TEST(AddressSanitizer, MemMoveOOBTest) {
  MemTransferOOBTestTemplate<char, MemMoveWrapper>(100);
  MemTransferOOBTestTemplate<int, MemMoveWrapper>(1024);
}


TEST(AddressSanitizer, MemCmpOOBTest) {
  size_t size = Ident(100);
  char *s1 = MallocAndMemsetString(size);
  char *s2 = MallocAndMemsetString(size);
  // Normal memcmp calls.
  Ident(memcmp(s1, s2, size));
  Ident(memcmp(s1 + size - 1, s2 + size - 1, 1));
  Ident(memcmp(s1 - 1, s2 - 1, 0));
  // One of arguments points to not allocated memory.
  EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBReadMessage(1));
  EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBReadMessage(1));
  EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBReadMessage(0));
  EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBReadMessage(0));
  // Hit unallocated memory and die.
  EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBReadMessage(0));
  EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBReadMessage(0));
  // Zero bytes are not terminators and don't prevent from OOB.
  s1[size - 1] = '\0';
  s2[size - 1] = '\0';
  EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBReadMessage(0));

  // Even if the buffers differ in the first byte, we still assume that
  // memcmp may access the whole buffer and thus reporting the overflow here:
  s1[0] = 1;
  s2[0] = 123;
  EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBReadMessage(0));

  free(s1);
  free(s2);
}