Files
clang-p2996/compiler-rt/lib/rtsan/tests/rtsan_test_functional.cpp
Paul Kirth 44d9beef7d [rtsan][test] Prevent test check from being optimized out in LTO builds (#122524)
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.
2025-01-14 11:55:19 -08:00

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