In LTO builds, some test checks can be optimized away, since the compiler can see through the memory accesses after inlining across TUs. This causes the existing death tests to fail, since the functions are completely optimized out and things like copying a lambda will no longer occur and trigger the sanitizer. To prevent that, we can use an empty inline assembly block to tell the compiler that memory is modified, and prevent it from doing that.
231 lines
6.6 KiB
C++
231 lines
6.6 KiB
C++
//===--- rtsan_test.cpp - Realtime Sanitizer --------------------*- C++ -*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Introduces basic functional tests for the realtime sanitizer.
|
|
// Not meant to be exhaustive, testing all interceptors, please see
|
|
// test_rtsan_interceptors.cpp for those tests.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "rtsan_test_utilities.h"
|
|
|
|
#include "rtsan/rtsan.h"
|
|
#include "sanitizer_common/sanitizer_platform.h"
|
|
#include "sanitizer_common/sanitizer_platform_interceptors.h"
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <thread>
|
|
|
|
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
|
|
__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200
|
|
#define SI_MAC_DEPLOYMENT_AT_LEAST_10_12 1
|
|
#else
|
|
#define SI_MAC_DEPLOYMENT_AT_LEAST_10_12 0
|
|
#endif
|
|
|
|
#define RTSAN_TEST_SHARED_MUTEX (!(SI_MAC) || SI_MAC_DEPLOYMENT_AT_LEAST_10_12)
|
|
|
|
using namespace testing;
|
|
using namespace rtsan_testing;
|
|
using namespace std::chrono_literals;
|
|
|
|
TEST(TestRtsan, VectorPushBackAllocationDiesWhenRealtime) {
|
|
std::vector<float> vec;
|
|
auto Func = [&vec]() { vec.push_back(0.4f); };
|
|
ExpectRealtimeDeath(Func);
|
|
ASSERT_EQ(0u, vec.size());
|
|
ExpectNonRealtimeSurvival(Func);
|
|
ASSERT_EQ(1u, vec.size());
|
|
}
|
|
|
|
TEST(TestRtsan, DestructionOfObjectOnHeapDiesWhenRealtime) {
|
|
auto allocated_ptr = std::make_unique<std::array<float, 256>>();
|
|
auto Func = [&allocated_ptr]() { allocated_ptr.reset(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ASSERT_NE(nullptr, allocated_ptr.get());
|
|
ExpectNonRealtimeSurvival(Func);
|
|
ASSERT_EQ(nullptr, allocated_ptr.get());
|
|
}
|
|
|
|
TEST(TestRtsan, SleepingAThreadDiesWhenRealtime) {
|
|
auto Func = []() { std::this_thread::sleep_for(1us); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, YieldingDiesWhenRealtime) {
|
|
auto Func = []() { std::this_thread::yield(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, IfstreamCreationDiesWhenRealtime) {
|
|
auto Func = []() { std::ifstream ifs{"./file.txt"}; };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
std::remove("./file.txt");
|
|
}
|
|
|
|
TEST(TestRtsan, OfstreamCreationDiesWhenRealtime) {
|
|
auto Func = []() { std::ofstream ofs{"./file.txt"}; };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
std::remove("./file.txt");
|
|
}
|
|
|
|
TEST(TestRtsan, LockingAMutexDiesWhenRealtime) {
|
|
std::mutex mutex;
|
|
auto Func = [&]() { mutex.lock(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, UnlockingAMutexDiesWhenRealtime) {
|
|
std::mutex mutex;
|
|
mutex.lock();
|
|
auto Func = [&]() { mutex.unlock(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
#if RTSAN_TEST_SHARED_MUTEX
|
|
|
|
TEST(TestRtsan, LockingASharedMutexDiesWhenRealtime) {
|
|
std::shared_mutex mutex;
|
|
auto Func = [&]() { mutex.lock(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, UnlockingASharedMutexDiesWhenRealtime) {
|
|
std::shared_mutex mutex;
|
|
mutex.lock();
|
|
auto Func = [&]() { mutex.unlock(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, SharedLockingASharedMutexDiesWhenRealtime) {
|
|
std::shared_mutex mutex;
|
|
auto Func = [&]() { mutex.lock_shared(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, SharedUnlockingASharedMutexDiesWhenRealtime) {
|
|
std::shared_mutex mutex;
|
|
mutex.lock_shared();
|
|
auto Func = [&]() { mutex.unlock_shared(); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
#endif // RTSAN_TEST_SHARED_MUTEX
|
|
|
|
TEST(TestRtsan, LaunchingAThreadDiesWhenRealtime) {
|
|
auto Func = [&]() {
|
|
std::thread Thread{[]() {}};
|
|
Thread.join();
|
|
};
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
namespace {
|
|
void InvokeStdFunction(std::function<void()> &&function) { function(); }
|
|
|
|
template <typename T> void HideMemoryFromCompiler(T *memory) {
|
|
// Pass the pointer to an empty assembly block as an input, and inform
|
|
// the compiler that memory is read to and possibly modified. This should not
|
|
// be architecture specific, since the asm block is empty.
|
|
__asm__ __volatile__("" ::"r"(memory) : "memory");
|
|
}
|
|
} // namespace
|
|
|
|
TEST(TestRtsan, CopyingALambdaWithLargeCaptureDiesWhenRealtime) {
|
|
std::array<float, 16> lots_of_data;
|
|
auto LargeLambda = [lots_of_data]() mutable {
|
|
lots_of_data[3] = 0.25f;
|
|
// In LTO builds, this lambda can be optimized away, since the compiler can
|
|
// see through the memory accesses after inlining across TUs. Ensure it can
|
|
// no longer reason about the memory access, so that won't happen.
|
|
HideMemoryFromCompiler(&lots_of_data[3]);
|
|
EXPECT_EQ(16u, lots_of_data.size());
|
|
EXPECT_EQ(0.25f, lots_of_data[3]);
|
|
};
|
|
auto Func = [&]() { InvokeStdFunction(LargeLambda); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, AccessingALargeAtomicVariableDiesWhenRealtime) {
|
|
std::atomic<float> small_atomic{0.0f};
|
|
ASSERT_TRUE(small_atomic.is_lock_free());
|
|
RealtimeInvoke([&small_atomic]() {
|
|
float x = small_atomic.load();
|
|
return x;
|
|
});
|
|
|
|
std::atomic<std::array<float, 2048>> large_atomic;
|
|
ASSERT_FALSE(large_atomic.is_lock_free());
|
|
auto Func = [&]() {
|
|
std::array<float, 2048> x = large_atomic.load();
|
|
return x;
|
|
};
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, FirstCoutDiesWhenRealtime) {
|
|
auto Func = []() { std::cout << "Hello, world!" << std::endl; };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, SecondCoutDiesWhenRealtime) {
|
|
std::cout << "Hello, world";
|
|
auto Func = []() { std::cout << "Hello, again!" << std::endl; };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, PrintfDiesWhenRealtime) {
|
|
auto Func = []() { printf("Hello, world!\n"); };
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, ThrowingAnExceptionDiesWhenRealtime) {
|
|
auto Func = [&]() {
|
|
try {
|
|
throw std::exception();
|
|
} catch (std::exception &) {
|
|
}
|
|
};
|
|
ExpectRealtimeDeath(Func);
|
|
ExpectNonRealtimeSurvival(Func);
|
|
}
|
|
|
|
TEST(TestRtsan, DoesNotDieIfTurnedOff) {
|
|
std::mutex mutex;
|
|
auto RealtimeBlockingFunc = [&]() {
|
|
__rtsan_disable();
|
|
mutex.lock();
|
|
mutex.unlock();
|
|
__rtsan_enable();
|
|
};
|
|
RealtimeInvoke(RealtimeBlockingFunc);
|
|
}
|