| //===--- 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); |
| } |