| //===-- sanitizer_posix_test.cpp ------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Tests for POSIX-specific code. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "sanitizer_common/sanitizer_platform.h" |
| #if SANITIZER_POSIX |
| |
| # include <pthread.h> |
| # include <sys/mman.h> |
| |
| # include <algorithm> |
| # include <numeric> |
| |
| # include "gtest/gtest.h" |
| # include "sanitizer_common/sanitizer_common.h" |
| |
| namespace __sanitizer { |
| |
| static pthread_key_t key; |
| static bool destructor_executed; |
| |
| extern "C" |
| void destructor(void *arg) { |
| uptr iter = reinterpret_cast<uptr>(arg); |
| if (iter > 1) { |
| ASSERT_EQ(0, pthread_setspecific(key, reinterpret_cast<void *>(iter - 1))); |
| return; |
| } |
| destructor_executed = true; |
| } |
| |
| extern "C" |
| void *thread_func(void *arg) { |
| return reinterpret_cast<void*>(pthread_setspecific(key, arg)); |
| } |
| |
| static void SpawnThread(uptr iteration) { |
| destructor_executed = false; |
| pthread_t tid; |
| ASSERT_EQ(0, pthread_create(&tid, 0, &thread_func, |
| reinterpret_cast<void *>(iteration))); |
| void *retval; |
| ASSERT_EQ(0, pthread_join(tid, &retval)); |
| ASSERT_EQ(0, retval); |
| } |
| |
| TEST(SanitizerCommon, PthreadDestructorIterations) { |
| ASSERT_EQ(0, pthread_key_create(&key, &destructor)); |
| SpawnThread(GetPthreadDestructorIterations()); |
| EXPECT_TRUE(destructor_executed); |
| SpawnThread(GetPthreadDestructorIterations() + 1); |
| #if SANITIZER_SOLARIS |
| // Solaris continues calling destructors beyond PTHREAD_DESTRUCTOR_ITERATIONS. |
| EXPECT_TRUE(destructor_executed); |
| #else |
| EXPECT_FALSE(destructor_executed); |
| #endif |
| ASSERT_EQ(0, pthread_key_delete(key)); |
| } |
| |
| TEST(SanitizerCommon, IsAccessibleMemoryRange) { |
| const int page_size = GetPageSize(); |
| InternalMmapVector<char> buffer(3 * page_size); |
| uptr mem = reinterpret_cast<uptr>(buffer.data()); |
| // Protect the middle page. |
| mprotect((void *)(mem + page_size), page_size, PROT_NONE); |
| EXPECT_TRUE(IsAccessibleMemoryRange(mem, page_size - 1)); |
| EXPECT_TRUE(IsAccessibleMemoryRange(mem, page_size)); |
| EXPECT_FALSE(IsAccessibleMemoryRange(mem, page_size + 1)); |
| EXPECT_TRUE(IsAccessibleMemoryRange(mem + page_size - 1, 1)); |
| EXPECT_FALSE(IsAccessibleMemoryRange(mem + page_size - 1, 2)); |
| EXPECT_FALSE(IsAccessibleMemoryRange(mem + 2 * page_size - 1, 1)); |
| EXPECT_TRUE(IsAccessibleMemoryRange(mem + 2 * page_size, page_size)); |
| EXPECT_FALSE(IsAccessibleMemoryRange(mem, 3 * page_size)); |
| EXPECT_FALSE(IsAccessibleMemoryRange(0x0, 2)); |
| } |
| |
| TEST(SanitizerCommon, IsAccessibleMemoryRangeLarge) { |
| InternalMmapVector<char> buffer(10000 * GetPageSize()); |
| EXPECT_TRUE(IsAccessibleMemoryRange(reinterpret_cast<uptr>(buffer.data()), |
| buffer.size())); |
| } |
| |
| TEST(SanitizerCommon, TryMemCpy) { |
| std::vector<char> src(10000000); |
| std::iota(src.begin(), src.end(), 123); |
| std::vector<char> dst; |
| |
| // Don't use ::testing::ElementsAreArray or similar, as the huge output on an |
| // error is not helpful. |
| |
| dst.assign(1, 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| |
| dst.assign(100, 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| |
| dst.assign(534, 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| |
| dst.assign(GetPageSize(), 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| |
| dst.assign(src.size(), 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| |
| dst.assign(src.size() - 1, 0); |
| EXPECT_TRUE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), src.begin())); |
| } |
| |
| TEST(SanitizerCommon, TryMemCpyNull) { |
| std::vector<char> dst(100); |
| EXPECT_FALSE(TryMemCpy(dst.data(), nullptr, dst.size())); |
| } |
| |
| TEST(SanitizerCommon, MemCpyAccessible) { |
| const int page_num = 1000; |
| const int page_size = GetPageSize(); |
| InternalMmapVector<char> src(page_num * page_size); |
| std::iota(src.begin(), src.end(), 123); |
| std::vector<char> dst; |
| std::vector<char> exp = {src.begin(), src.end()}; |
| |
| // Protect some pages. |
| for (int i = 7; i < page_num; i *= 2) { |
| mprotect(src.data() + i * page_size, page_size, PROT_NONE); |
| std::fill(exp.data() + i * page_size, exp.data() + (i + 1) * page_size, 0); |
| } |
| |
| dst.assign(src.size(), 0); |
| EXPECT_FALSE(TryMemCpy(dst.data(), src.data(), dst.size())); |
| |
| // Full page aligned range with mprotect pages. |
| dst.assign(src.size(), 0); |
| MemCpyAccessible(dst.data(), src.data(), dst.size()); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), exp.begin())); |
| |
| // Misaligned range with mprotect pages. |
| size_t offb = 3; |
| size_t offe = 7; |
| dst.assign(src.size() - offb - offe, 0); |
| MemCpyAccessible(dst.data(), src.data() + offb, dst.size()); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), exp.begin() + offb)); |
| |
| // Misaligned range with ends in mprotect pages. |
| offb = 3 + 7 * page_size; |
| offe = 7 + 14 * page_size; |
| dst.assign(src.size() - offb - offe, 0); |
| MemCpyAccessible(dst.data(), src.data() + offb, dst.size()); |
| EXPECT_TRUE(std::equal(dst.begin(), dst.end(), exp.begin() + offb)); |
| } |
| |
| } // namespace __sanitizer |
| |
| #endif // SANITIZER_POSIX |