//===-- Memory bounds check test for wide-read functions ------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// For performance, some vector-based libc functions read data outside of, but
// adjacent to, the input address. For example, string_length can read both
// before and after the data in its src parameter. As part of the
// implementation, it is allowed to do this. However, the code must take care to
// avoid address errors. The sanitizers can't distinguish between "the
// implementation" and user-code, and so report an error. Therefore we can't use
// them to check if functions like these have memory errors.
//
// This test uses mprotect to simulate address sanitization. Tests that read too
// far outside data will segfault.
//
// It creates three adjacent pages in memory. The outer two are mprotected
// unreadable, the middle usable normally. By placing test data at the edges
// between the middle page and the others, we can test for bad accesses.

#include "hdr/sys_mman_macros.h"
#include "src/__support/CPP/array.h"
#include "src/string/memory_utils/inline_memset.h"
#include "src/string/string_utils.h"
#include "src/sys/mman/mmap.h"
#include "src/sys/mman/mprotect.h"
#include "src/sys/mman/munmap.h"
#include "src/unistd/getpagesize.h"
#include "test/UnitTest/MemoryMatcher.h"
#include "test/UnitTest/Test.h"

namespace LIBC_NAMESPACE_DECL {

using TwoKilobyteBuffer = cpp::array<char, 2048>;
// This could be smaller on a target-basis, but that adds complexity and the
// extra testing is fine.
static constexpr unsigned long kLargestTestVectorSize = 512;

class LlvmLibcWideAccessMemoryTest : public testing::Test {
  char *page0_;
  char *page1_;
  char *page2_;
  size_t page_size;

public:
  void SetUp() override {
    page_size = LIBC_NAMESPACE::getpagesize();
    page0_ = static_cast<char *>(
        LIBC_NAMESPACE::mmap(nullptr, page_size * 3, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
    ASSERT_NE(static_cast<void *>(page0_), MAP_FAILED);
    page1_ = page0_ + page_size;
    page2_ = page1_ + page_size;
    LIBC_NAMESPACE::mprotect(page0_, page_size, PROT_NONE);
    LIBC_NAMESPACE::mprotect(page2_, page_size, PROT_NONE);
  }

  void TearDown() override { LIBC_NAMESPACE::munmap(page0_, page_size * 3); }

  // Repeatedly runs "func" on copies of the data in "buf", each progressively
  // closer to the boundary of valid memory. Test will segfault if function
  // under test accesses invalid memory.
  //
  // Func should test the function in question just as normal. Recommend making
  // the amount of test data at least 1.5k, which guarantees a wind-up, multiple
  // iterations of the inner loop, and a wind-down, even on systems with
  // 512-byte vectors. The termination condition, eg, end-of string or character
  // being searched for, should be near the end of the data.
  template <typename TestFunc>
  void TestMemoryAccess(const TwoKilobyteBuffer &buf, TestFunc func) {
    // Run func on data near the start boundary of valid memory.
    for (unsigned long offset = 0; offset < kLargestTestVectorSize; ++offset) {
      char *test_addr = page1_ + offset;
      inline_memcpy(test_addr, buf.data(), buf.size());
      func(test_addr);
    }
    // Run func on data near the end boundary of valid memory.
    for (unsigned long offset = 0; offset < kLargestTestVectorSize; ++offset) {
      char *test_addr = page2_ - buf.size() - offset - 1;
      ASSERT_LE(test_addr + buf.size(), page2_);
      inline_memcpy(test_addr, buf.data(), buf.size());
      func(test_addr);
    }
  }
};

TEST_F(LlvmLibcWideAccessMemoryTest, StringLength) {
  // 1.5 k long vector of a's.
  TwoKilobyteBuffer buf;
  inline_memset(buf.data(), 'a', buf.size());
  // Make sure it is null terminated.
  buf[buf.size() - 1] = '\0';
  this->TestMemoryAccess(buf, [this, buf](const char *test_data) {
    // -1 for the null character.
    ASSERT_EQ(internal::string_length(test_data), size_t(buf.size() - 1));
  });
}

TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstChar) {
  // 1.5 k long vector of a's.
  TwoKilobyteBuffer buf;
  inline_memset(buf.data(), 'a', buf.size());
  buf[buf.size() - 1] = 'b';
  this->TestMemoryAccess(buf, [this, buf](const char *test_data) {
    // Found case
    ASSERT_EQ(
        reinterpret_cast<const void *>(internal::find_first_character_impl(
            reinterpret_cast<const unsigned char *>(test_data), 'b',
            size_t(buf.size()))),
        reinterpret_cast<const void *>(test_data + size_t(buf.size()) - 1));
    // Not found case
    ASSERT_EQ(
        reinterpret_cast<const void *>(internal::find_first_character_impl(
            reinterpret_cast<const unsigned char *>(test_data), 'c',
            size_t(buf.size()))),
        nullptr);
  });
}

} // namespace LIBC_NAMESPACE_DECL
