diff --git a/compiler-rt/CODE_OWNERS.TXT b/compiler-rt/CODE_OWNERS.TXT index ad136edf96781..570ab86508060 100644 --- a/compiler-rt/CODE_OWNERS.TXT +++ b/compiler-rt/CODE_OWNERS.TXT @@ -67,3 +67,7 @@ D: ThreadSanitizer N: Bill Wendling E: isanbard@gmail.com D: Profile runtime library + +N: Christopher Apple, David Trevelyan +E: cja-private@pm.me, realtime.sanitizer@gmail.com +D: Realtime Sanitizer (RTSan) diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index ac4a71202384d..bc152e304aaaf 100644 --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -32,6 +32,9 @@ set(ALL_ASAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64} ${LOONGARCH64}) set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32}) set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}) +set(ALL_RTSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64} + ${MIPS32} ${MIPS64} ${PPC64} ${S390X} ${SPARC} ${SPARCV9} ${HEXAGON} + ${LOONGARCH64}) if(ANDROID) set(OS_NAME "Android") diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake index 7037bf89d6532..1130f9f721df4 100644 --- a/compiler-rt/cmake/config-ix.cmake +++ b/compiler-rt/cmake/config-ix.cmake @@ -597,6 +597,9 @@ if(APPLE) list_intersect(ASAN_SUPPORTED_ARCH ALL_ASAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) + list_intersect(RTSAN_SUPPORTED_ARCH + ALL_RTSAN_SUPPORTED_ARCH + SANITIZER_COMMON_SUPPORTED_ARCH) list_intersect(DFSAN_SUPPORTED_ARCH ALL_DFSAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) @@ -663,6 +666,7 @@ else() filter_available_targets(UBSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH}) filter_available_targets(ASAN_SUPPORTED_ARCH ${ALL_ASAN_SUPPORTED_ARCH}) + filter_available_targets(RTSAN_SUPPORTED_ARCH ${ALL_RTSAN_SUPPORTED_ARCH}) filter_available_targets(FUZZER_SUPPORTED_ARCH ${ALL_FUZZER_SUPPORTED_ARCH}) filter_available_targets(DFSAN_SUPPORTED_ARCH ${ALL_DFSAN_SUPPORTED_ARCH}) filter_available_targets(LSAN_SUPPORTED_ARCH ${ALL_LSAN_SUPPORTED_ARCH}) @@ -716,7 +720,7 @@ if(COMPILER_RT_SUPPORTED_ARCH) endif() message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}") -set(ALL_SANITIZERS asan;dfsan;msan;hwasan;tsan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi) +set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi) set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING "sanitizers to build if supported on the target (all;${ALL_SANITIZERS})") list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}") @@ -747,6 +751,12 @@ else() set(COMPILER_RT_HAS_ASAN FALSE) endif() +if (COMPILER_RT_HAS_SANITIZER_COMMON AND RTSAN_SUPPORTED_ARCH) + set(COMPILER_RT_HAS_RTSAN TRUE) +else() + set(COMPILER_RT_HAS_RTSAN FALSE) +endif() + if (OS_NAME MATCHES "Linux|FreeBSD|Windows|NetBSD|SunOS") set(COMPILER_RT_ASAN_HAS_STATIC_RUNTIME TRUE) else() diff --git a/compiler-rt/lib/rtsan/CMakeLists.txt b/compiler-rt/lib/rtsan/CMakeLists.txt new file mode 100644 index 0000000000000..bd7358e86e59b --- /dev/null +++ b/compiler-rt/lib/rtsan/CMakeLists.txt @@ -0,0 +1,92 @@ +include_directories(..) + +set(RTSAN_CXX_SOURCES + rtsan.cpp + rtsan_context.cpp + rtsan_stack.cpp + rtsan_interceptors.cpp) + +set(RTSAN_PREINIT_SOURCES + rtsan_preinit.cpp) + +set(RTSAN_HEADERS + rtsan.h + rtsan_context.h + rtsan_stack.h) + +set(RTSAN_DEPS) + +set(RTSAN_CFLAGS + ${COMPILER_RT_COMMON_CFLAGS} + ${COMPILER_RT_CXX_CFLAGS} + -DSANITIZER_COMMON_NO_REDEFINE_BUILTINS) +set(RTSAN_LINK_FLAGS ${COMPILER_RT_COMMON_LINK_FLAGS}) +set(RTSAN_LINK_LIBS + ${COMPILER_RT_UNWINDER_LINK_LIBS} + ${COMPILER_RT_CXX_LINK_LIBS}) + +if(APPLE) + add_compiler_rt_object_libraries(RTRtsan + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${RTSAN_SUPPORTED_ARCH} + SOURCES ${RTSAN_CXX_SOURCES} + ADDITIONAL_HEADERS ${RTSAN_HEADERS} + CFLAGS ${RTSAN_CFLAGS} + DEPS ${RTSAN_DEPS}) +else() + add_compiler_rt_object_libraries(RTRtsan + ARCHS ${RTSAN_SUPPORTED_ARCH} + SOURCES ${RTSAN_CXX_SOURCES} + ADDITIONAL_HEADERS ${RTSAN_HEADERS} + CFLAGS ${RTSAN_CFLAGS} + DEPS ${RTSAN_DEPS}) + add_compiler_rt_object_libraries(RTRtsan_preinit + ARCHS ${RTSAN_SUPPORTED_ARCH} + SOURCES ${RTSAN_PREINIT_SOURCES} + ADDITIONAL_HEADERS ${RTSAN_HEADERS} + CFLAGS ${RTSAN_CFLAGS}) +endif() + +set(RTSAN_COMMON_RUNTIME_OBJECT_LIBS + RTInterception + RTSanitizerCommon + RTSanitizerCommonLibc + RTSanitizerCommonCoverage + RTSanitizerCommonSymbolizer) + +append_list_if(COMPILER_RT_HAS_LIBDL dl RTSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBRT rt RTSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBM m RTSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBPTHREAD pthread RTSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBLOG log RTSAN_LINK_LIBS) + +add_compiler_rt_component(rtsan) + +if (APPLE) + add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS) + set(RTSAN_LINK_FLAGS ${RTSAN_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}) + + add_compiler_rt_runtime(clang_rt.rtsan + SHARED + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${RTSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTRtsan + ${RTSAN_COMMON_RUNTIME_OBJECT_LIBS} + LINK_FLAGS ${RTSAN_LINK_FLAGS} + LINK_LIBS ${RTSAN_LINK_LIBS} + PARENT_TARGET rtsan) +else() + add_compiler_rt_runtime(clang_rt.rtsan + STATIC + ARCHS ${RTSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTRtsan_preinit + RTRtsan + ${RTSAN_COMMON_RUNTIME_OBJECT_LIBS} + LINK_FLAGS ${RTSAN_LINK_FLAGS} + CFLAGS ${RTSAN_CFLAGS} + PARENT_TARGET rtsan) +endif() + +if(COMPILER_RT_INCLUDE_TESTS) + add_subdirectory(tests) +endif() diff --git a/compiler-rt/lib/rtsan/rtsan.cpp b/compiler-rt/lib/rtsan/rtsan.cpp new file mode 100644 index 0000000000000..43a63b4636f1e --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan.cpp @@ -0,0 +1,37 @@ +//===--- rtsan.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +extern "C" { + +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_init() { + __rtsan::InitializeInterceptors(); +} + +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_enter() { + __rtsan::GetContextForThisThread().RealtimePush(); +} + +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_exit() { + __rtsan::GetContextForThisThread().RealtimePop(); +} + +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_off() { + __rtsan::GetContextForThisThread().BypassPush(); +} + +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_on() { + __rtsan::GetContextForThisThread().BypassPop(); +} + +} // extern "C" diff --git a/compiler-rt/lib/rtsan/rtsan.h b/compiler-rt/lib/rtsan/rtsan.h new file mode 100644 index 0000000000000..8b1219c13cbd3 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan.h @@ -0,0 +1,40 @@ +//===--- rtsan.h - 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "sanitizer_common/sanitizer_internal_defs.h" + +extern "C" { + +// Initialise rtsan interceptors. +// A call to this method is added to the preinit array on Linux systems. +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_init(); + +// Enter real-time context. +// When in a real-time context, RTSan interceptors will error if realtime +// violations are detected. Calls to this method are injected at the code +// generation stage when RTSan is enabled. +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_enter(); + +// Exit the real-time context. +// When not in a real-time context, RTSan interceptors will simply forward +// intercepted method calls to the real methods. +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_exit(); + +// Disable all RTSan error reporting. +// Injected into the code if "nosanitize(realtime)" is on a function. +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_off(); + +// Re-enable all RTSan error reporting. +// The counterpart to `__rtsan_off`. +SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_on(); + +} // extern "C" diff --git a/compiler-rt/lib/rtsan/rtsan_context.cpp b/compiler-rt/lib/rtsan/rtsan_context.cpp new file mode 100644 index 0000000000000..f761ddedce672 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_context.cpp @@ -0,0 +1,98 @@ +//===--- rtsan_context.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +static pthread_key_t context_key; +static pthread_once_t key_once = PTHREAD_ONCE_INIT; + +// InternalFree cannot be passed directly to pthread_key_create +// because it expects a signature with only one arg +static void InternalFreeWrapper(void *ptr) { __sanitizer::InternalFree(ptr); } + +static __rtsan::Context &GetContextForThisThreadImpl() { + auto make_thread_local_context_key = []() { + CHECK_EQ(pthread_key_create(&context_key, InternalFreeWrapper), 0); + }; + + pthread_once(&key_once, make_thread_local_context_key); + __rtsan::Context *current_thread_context = + static_cast<__rtsan::Context *>(pthread_getspecific(context_key)); + if (current_thread_context == nullptr) { + current_thread_context = static_cast<__rtsan::Context *>( + __sanitizer::InternalAlloc(sizeof(__rtsan::Context))); + new (current_thread_context) __rtsan::Context(); + pthread_setspecific(context_key, current_thread_context); + } + + return *current_thread_context; +} + +/* + This is a placeholder stub for a future feature that will allow + a user to configure RTSan's behaviour when a real-time safety + violation is detected. The RTSan developers intend for the + following choices to be made available, via a RTSAN_OPTIONS + environment variable, in a future PR: + + i) exit, + ii) continue, or + iii) wait for user input from stdin. + + Until then, and to keep the first PRs small, only the exit mode + is available. +*/ +static void InvokeViolationDetectedAction() { exit(EXIT_FAILURE); } + +__rtsan::Context::Context() = default; + +void __rtsan::Context::RealtimePush() { realtime_depth++; } + +void __rtsan::Context::RealtimePop() { realtime_depth--; } + +void __rtsan::Context::BypassPush() { bypass_depth++; } + +void __rtsan::Context::BypassPop() { bypass_depth--; } + +void __rtsan::Context::ExpectNotRealtime( + const char *intercepted_function_name) { + if (InRealtimeContext() && !IsBypassed()) { + BypassPush(); + PrintDiagnostics(intercepted_function_name); + InvokeViolationDetectedAction(); + BypassPop(); + } +} + +bool __rtsan::Context::InRealtimeContext() const { return realtime_depth > 0; } + +bool __rtsan::Context::IsBypassed() const { return bypass_depth > 0; } + +void __rtsan::Context::PrintDiagnostics(const char *intercepted_function_name) { + fprintf(stderr, + "Real-time violation: intercepted call to real-time unsafe function " + "`%s` in real-time context! Stack trace:\n", + intercepted_function_name); + __rtsan::PrintStackTrace(); +} + +__rtsan::Context &__rtsan::GetContextForThisThread() { + return GetContextForThisThreadImpl(); +} diff --git a/compiler-rt/lib/rtsan/rtsan_context.h b/compiler-rt/lib/rtsan/rtsan_context.h new file mode 100644 index 0000000000000..515bb8ade1eb9 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_context.h @@ -0,0 +1,38 @@ +//===--- rtsan_context.h - 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#pragma once + +namespace __rtsan { + +class Context { +public: + Context(); + + void RealtimePush(); + void RealtimePop(); + + void BypassPush(); + void BypassPop(); + + void ExpectNotRealtime(const char *intercepted_function_name); + +private: + bool InRealtimeContext() const; + bool IsBypassed() const; + void PrintDiagnostics(const char *intercepted_function_name); + + int realtime_depth{0}; + int bypass_depth{0}; +}; + +Context &GetContextForThisThread(); + +} // namespace __rtsan diff --git a/compiler-rt/lib/rtsan/rtsan_interceptors.cpp b/compiler-rt/lib/rtsan/rtsan_interceptors.cpp new file mode 100644 index 0000000000000..3a65f9d3f779d --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_interceptors.cpp @@ -0,0 +1,405 @@ +//===--- rtsan_interceptors.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "rtsan/rtsan_interceptors.h" + +#include "sanitizer_common/sanitizer_platform.h" +#include "sanitizer_common/sanitizer_platform_interceptors.h" + +#include "interception/interception.h" +#include "rtsan/rtsan_context.h" + +#if SANITIZER_APPLE +#include +#include +#endif + +#if SANITIZER_INTERCEPT_MEMALIGN || SANITIZER_INTERCEPT_PVALLOC +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +using namespace __sanitizer; + +void ExpectNotRealtime(const char *intercepted_function_name) { + __rtsan::GetContextForThisThread().ExpectNotRealtime( + intercepted_function_name); +} + +// Filesystem + +INTERCEPTOR(int, open, const char *path, int oflag, ...) { + // TODO Establish whether we should intercept here if the flag contains + // O_NONBLOCK + ExpectNotRealtime("open"); + + va_list args; + va_start(args, oflag); + const mode_t mode = va_arg(args, int); + va_end(args); + + const int result = REAL(open)(path, oflag, mode); + return result; +} + +INTERCEPTOR(int, openat, int fd, const char *path, int oflag, ...) { + // TODO Establish whether we should intercept here if the flag contains + // O_NONBLOCK + ExpectNotRealtime("openat"); + + va_list args; + va_start(args, oflag); + mode_t mode = va_arg(args, int); + va_end(args); + + const int result = REAL(openat)(fd, path, oflag, mode); + return result; +} + +INTERCEPTOR(int, creat, const char *path, mode_t mode) { + // TODO Establish whether we should intercept here if the flag contains + // O_NONBLOCK + ExpectNotRealtime("creat"); + const int result = REAL(creat)(path, mode); + return result; +} + +INTERCEPTOR(int, fcntl, int filedes, int cmd, ...) { + ExpectNotRealtime("fcntl"); + + va_list args; + va_start(args, cmd); + + // Following precedent here. The linux source (fcntl.c, do_fcntl) accepts the + // final argument in a variable that will hold the largest of the possible + // argument types (pointers and ints are typical in fcntl) It is then assumed + // that the implementation of fcntl will cast it properly depending on cmd. + // + // This is also similar to what is done in + // sanitizer_common/sanitizer_common_syscalls.inc + const unsigned long arg = va_arg(args, unsigned long); + int result = REAL(fcntl)(filedes, cmd, arg); + + va_end(args); + + return result; +} + +INTERCEPTOR(int, close, int filedes) { + ExpectNotRealtime("close"); + return REAL(close)(filedes); +} + +INTERCEPTOR(FILE *, fopen, const char *path, const char *mode) { + ExpectNotRealtime("fopen"); + return REAL(fopen)(path, mode); +} + +INTERCEPTOR(size_t, fread, void *ptr, size_t size, size_t nitems, + FILE *stream) { + ExpectNotRealtime("fread"); + return REAL(fread)(ptr, size, nitems, stream); +} + +INTERCEPTOR(size_t, fwrite, const void *ptr, size_t size, size_t nitems, + FILE *stream) { + ExpectNotRealtime("fwrite"); + return REAL(fwrite)(ptr, size, nitems, stream); +} + +INTERCEPTOR(int, fclose, FILE *stream) { + ExpectNotRealtime("fclose"); + return REAL(fclose)(stream); +} + +INTERCEPTOR(int, fputs, const char *s, FILE *stream) { + ExpectNotRealtime("fputs"); + return REAL(fputs)(s, stream); +} + +// Streams +INTERCEPTOR(int, puts, const char *s) { + ExpectNotRealtime("puts"); + return REAL(puts)(s); +} + +// Concurrency +#if SANITIZER_APPLE +#pragma clang diagnostic push +// OSSpinLockLock is deprecated, but still in use in libc++ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +INTERCEPTOR(void, OSSpinLockLock, volatile OSSpinLock *lock) { + ExpectNotRealtime("OSSpinLockLock"); + return REAL(OSSpinLockLock)(lock); +} +#pragma clang diagnostic pop + +INTERCEPTOR(void, os_unfair_lock_lock, os_unfair_lock_t lock) { + ExpectNotRealtime("os_unfair_lock_lock"); + return REAL(os_unfair_lock_lock)(lock); +} +#elif SANITIZER_LINUX +INTERCEPTOR(int, pthread_spin_lock, pthread_spinlock_t *spinlock) { + ExpectNotRealtime("pthread_spin_lock"); + return REAL(pthread_spin_lock)(spinlock); +} +#endif + +INTERCEPTOR(int, pthread_create, pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) { + ExpectNotRealtime("pthread_create"); + return REAL(pthread_create)(thread, attr, start_routine, arg); +} + +INTERCEPTOR(int, pthread_mutex_lock, pthread_mutex_t *mutex) { + ExpectNotRealtime("pthread_mutex_lock"); + return REAL(pthread_mutex_lock)(mutex); +} + +INTERCEPTOR(int, pthread_mutex_unlock, pthread_mutex_t *mutex) { + ExpectNotRealtime("pthread_mutex_unlock"); + return REAL(pthread_mutex_unlock)(mutex); +} + +INTERCEPTOR(int, pthread_join, pthread_t thread, void **value_ptr) { + ExpectNotRealtime("pthread_join"); + return REAL(pthread_join)(thread, value_ptr); +} + +INTERCEPTOR(int, pthread_cond_signal, pthread_cond_t *cond) { + ExpectNotRealtime("pthread_cond_signal"); + return REAL(pthread_cond_signal)(cond); +} + +INTERCEPTOR(int, pthread_cond_broadcast, pthread_cond_t *cond) { + ExpectNotRealtime("pthread_cond_broadcast"); + return REAL(pthread_cond_broadcast)(cond); +} + +INTERCEPTOR(int, pthread_cond_wait, pthread_cond_t *cond, + pthread_mutex_t *mutex) { + ExpectNotRealtime("pthread_cond_wait"); + return REAL(pthread_cond_wait)(cond, mutex); +} + +INTERCEPTOR(int, pthread_cond_timedwait, pthread_cond_t *cond, + pthread_mutex_t *mutex, const timespec *ts) { + ExpectNotRealtime("pthread_cond_timedwait"); + return REAL(pthread_cond_timedwait)(cond, mutex, ts); +} + +INTERCEPTOR(int, pthread_rwlock_rdlock, pthread_rwlock_t *lock) { + ExpectNotRealtime("pthread_rwlock_rdlock"); + return REAL(pthread_rwlock_rdlock)(lock); +} + +INTERCEPTOR(int, pthread_rwlock_unlock, pthread_rwlock_t *lock) { + ExpectNotRealtime("pthread_rwlock_unlock"); + return REAL(pthread_rwlock_unlock)(lock); +} + +INTERCEPTOR(int, pthread_rwlock_wrlock, pthread_rwlock_t *lock) { + ExpectNotRealtime("pthread_rwlock_wrlock"); + return REAL(pthread_rwlock_wrlock)(lock); +} + +// Sleeping + +INTERCEPTOR(unsigned int, sleep, unsigned int s) { + ExpectNotRealtime("sleep"); + return REAL(sleep)(s); +} + +INTERCEPTOR(int, usleep, useconds_t u) { + ExpectNotRealtime("usleep"); + return REAL(usleep)(u); +} + +INTERCEPTOR(int, nanosleep, const struct timespec *rqtp, + struct timespec *rmtp) { + ExpectNotRealtime("nanosleep"); + return REAL(nanosleep)(rqtp, rmtp); +} + +// Memory + +INTERCEPTOR(void *, calloc, SIZE_T num, SIZE_T size) { + ExpectNotRealtime("calloc"); + return REAL(calloc)(num, size); +} + +INTERCEPTOR(void, free, void *ptr) { + if (ptr != NULL) { + ExpectNotRealtime("free"); + } + return REAL(free)(ptr); +} + +INTERCEPTOR(void *, malloc, SIZE_T size) { + ExpectNotRealtime("malloc"); + return REAL(malloc)(size); +} + +INTERCEPTOR(void *, realloc, void *ptr, SIZE_T size) { + ExpectNotRealtime("realloc"); + return REAL(realloc)(ptr, size); +} + +INTERCEPTOR(void *, reallocf, void *ptr, SIZE_T size) { + ExpectNotRealtime("reallocf"); + return REAL(reallocf)(ptr, size); +} + +INTERCEPTOR(void *, valloc, SIZE_T size) { + ExpectNotRealtime("valloc"); + return REAL(valloc)(size); +} + +#if SANITIZER_INTERCEPT_ALIGNED_ALLOC +INTERCEPTOR(void *, aligned_alloc, SIZE_T alignment, SIZE_T size) { + ExpectNotRealtime("aligned_alloc"); + return REAL(aligned_alloc)(alignment, size); +} +#define RTSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC INTERCEPT_FUNCTION(aligned_alloc) +#else +#define RTSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC +#endif + +INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { + ExpectNotRealtime("posix_memalign"); + return REAL(posix_memalign)(memptr, alignment, size); +} + +#if SANITIZER_INTERCEPT_MEMALIGN +INTERCEPTOR(void *, memalign, size_t alignment, size_t size) { + ExpectNotRealtime("memalign"); + return REAL(memalign)(alignment, size); +} +#endif + +#if SANITIZER_INTERCEPT_PVALLOC +INTERCEPTOR(void *, pvalloc, size_t size) { + ExpectNotRealtime("pvalloc"); + return REAL(pvalloc)(size); +} +#endif + +// Sockets +INTERCEPTOR(int, socket, int domain, int type, int protocol) { + ExpectNotRealtime("socket"); + return REAL(socket)(domain, type, protocol); +} + +INTERCEPTOR(ssize_t, send, int sockfd, const void *buf, size_t len, int flags) { + ExpectNotRealtime("send"); + return REAL(send)(sockfd, buf, len, flags); +} + +INTERCEPTOR(ssize_t, sendmsg, int socket, const struct msghdr *message, + int flags) { + ExpectNotRealtime("sendmsg"); + return REAL(sendmsg)(socket, message, flags); +} + +INTERCEPTOR(ssize_t, sendto, int socket, const void *buffer, size_t length, + int flags, const struct sockaddr *dest_addr, socklen_t dest_len) { + ExpectNotRealtime("sendto"); + return REAL(sendto)(socket, buffer, length, flags, dest_addr, dest_len); +} + +INTERCEPTOR(ssize_t, recv, int socket, void *buffer, size_t length, int flags) { + ExpectNotRealtime("recv"); + return REAL(recv)(socket, buffer, length, flags); +} + +INTERCEPTOR(ssize_t, recvfrom, int socket, void *buffer, size_t length, + int flags, struct sockaddr *address, socklen_t *address_len) { + ExpectNotRealtime("recvfrom"); + return REAL(recvfrom)(socket, buffer, length, flags, address, address_len); +} + +INTERCEPTOR(ssize_t, recvmsg, int socket, struct msghdr *message, int flags) { + ExpectNotRealtime("recvmsg"); + return REAL(recvmsg)(socket, message, flags); +} + +INTERCEPTOR(int, shutdown, int socket, int how) { + ExpectNotRealtime("shutdown"); + return REAL(shutdown)(socket, how); +} + +// Preinit +void __rtsan::InitializeInterceptors() { + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(reallocf); + INTERCEPT_FUNCTION(valloc); + RTSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC; + INTERCEPT_FUNCTION(posix_memalign); +#if SANITIZER_INTERCEPT_MEMALIGN + INTERCEPT_FUNCTION(memalign); +#endif +#if SANITIZER_INTERCEPT_PVALLOC + INTERCEPT_FUNCTION(pvalloc); +#endif + + INTERCEPT_FUNCTION(open); + INTERCEPT_FUNCTION(openat); + INTERCEPT_FUNCTION(close); + INTERCEPT_FUNCTION(fopen); + INTERCEPT_FUNCTION(fread); + INTERCEPT_FUNCTION(fwrite); + INTERCEPT_FUNCTION(fclose); + INTERCEPT_FUNCTION(fcntl); + INTERCEPT_FUNCTION(creat); + INTERCEPT_FUNCTION(puts); + INTERCEPT_FUNCTION(fputs); + +#if SANITIZER_APPLE + INTERCEPT_FUNCTION(OSSpinLockLock); + INTERCEPT_FUNCTION(os_unfair_lock_lock); +#elif SANITIZER_LINUX + INTERCEPT_FUNCTION(pthread_spin_lock); +#endif + + INTERCEPT_FUNCTION(pthread_create); + INTERCEPT_FUNCTION(pthread_mutex_lock); + INTERCEPT_FUNCTION(pthread_mutex_unlock); + INTERCEPT_FUNCTION(pthread_join); + INTERCEPT_FUNCTION(pthread_cond_signal); + INTERCEPT_FUNCTION(pthread_cond_broadcast); + INTERCEPT_FUNCTION(pthread_cond_wait); + INTERCEPT_FUNCTION(pthread_cond_timedwait); + INTERCEPT_FUNCTION(pthread_rwlock_rdlock); + INTERCEPT_FUNCTION(pthread_rwlock_unlock); + INTERCEPT_FUNCTION(pthread_rwlock_wrlock); + + INTERCEPT_FUNCTION(sleep); + INTERCEPT_FUNCTION(usleep); + INTERCEPT_FUNCTION(nanosleep); + + INTERCEPT_FUNCTION(socket); + INTERCEPT_FUNCTION(send); + INTERCEPT_FUNCTION(sendmsg); + INTERCEPT_FUNCTION(sendto); + INTERCEPT_FUNCTION(recv); + INTERCEPT_FUNCTION(recvmsg); + INTERCEPT_FUNCTION(recvfrom); + INTERCEPT_FUNCTION(shutdown); +} diff --git a/compiler-rt/lib/rtsan/rtsan_interceptors.h b/compiler-rt/lib/rtsan/rtsan_interceptors.h new file mode 100644 index 0000000000000..a3dac27544c66 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_interceptors.h @@ -0,0 +1,15 @@ +//===--- rtsan_interceptors.h - 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#pragma once + +namespace __rtsan { +void InitializeInterceptors(); +} // namespace __rtsan diff --git a/compiler-rt/lib/rtsan/rtsan_preinit.cpp b/compiler-rt/lib/rtsan/rtsan_preinit.cpp new file mode 100644 index 0000000000000..8cea61c3ea8b7 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_preinit.cpp @@ -0,0 +1,23 @@ +//===--- rtsan_preinit.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_internal_defs.h" +#include + +#if SANITIZER_CAN_USE_PREINIT_ARRAY + +// The symbol is called __local_rtsan_preinit, because it's not intended to be +// exported. +// This code is linked into the main executable when -fsanitize=realtime is in +// the link flags. It can only use exported interface functions. +__attribute__((section(".preinit_array"), + used)) void (*__local_rtsan_preinit)(void) = __rtsan_init; + +#endif diff --git a/compiler-rt/lib/rtsan/rtsan_stack.cpp b/compiler-rt/lib/rtsan/rtsan_stack.cpp new file mode 100644 index 0000000000000..43fd5fbf05509 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_stack.cpp @@ -0,0 +1,50 @@ +//===--- rtsan_stack.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "rtsan_stack.h" + +#include +#include + +using namespace __sanitizer; +using namespace __rtsan; + +// We must define our own implementation of this method for our runtime. +// This one is just copied from UBSan. +namespace __sanitizer { +void BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context, + bool request_fast, u32 max_depth) { + uptr top = 0; + uptr bottom = 0; + GetThreadStackTopAndBottom(false, &top, &bottom); + bool fast = StackTrace::WillUseFastUnwind(request_fast); + Unwind(max_depth, pc, bp, context, top, bottom, fast); +} +} // namespace __sanitizer + +static void SetGlobalStackTraceFormat() { + SetCommonFlagsDefaults(); + CommonFlags cf; + cf.CopyFrom(*common_flags()); + cf.stack_trace_format = "DEFAULT"; + cf.external_symbolizer_path = GetEnv("RTSAN_SYMBOLIZER_PATH"); + OverrideCommonFlags(cf); +} + +void __rtsan::PrintStackTrace() { + + BufferedStackTrace stack{}; + + GET_CURRENT_PC_BP; + stack.Unwind(pc, bp, nullptr, common_flags()->fast_unwind_on_fatal); + + SetGlobalStackTraceFormat(); + stack.Print(); +} diff --git a/compiler-rt/lib/rtsan/rtsan_stack.h b/compiler-rt/lib/rtsan/rtsan_stack.h new file mode 100644 index 0000000000000..cecdd43045db5 --- /dev/null +++ b/compiler-rt/lib/rtsan/rtsan_stack.h @@ -0,0 +1,15 @@ +//===--- rtsan_stack.h - 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#pragma once + +namespace __rtsan { +void PrintStackTrace(); +} // namespace __rtsan diff --git a/compiler-rt/lib/rtsan/tests/CMakeLists.txt b/compiler-rt/lib/rtsan/tests/CMakeLists.txt new file mode 100644 index 0000000000000..d96e538b255f4 --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/CMakeLists.txt @@ -0,0 +1,103 @@ +include(CompilerRTCompile) + +include_directories(..) + +set(RTSAN_UNITTEST_CFLAGS + ${COMPILER_RT_UNITTEST_CFLAGS} + ${COMPILER_RT_GTEST_CFLAGS} + ${COMPILER_RT_GMOCK_CFLAGS} + ${SANITIZER_TEST_CXX_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/lib/ + -I${COMPILER_RT_SOURCE_DIR}/include/ + -I${COMPILER_RT_SOURCE_DIR}/lib/rtsan + -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common/tests + -DSANITIZER_COMMON_NO_REDEFINE_BUILTINS + -O2) + +set(RTSAN_INST_TEST_SOURCES + rtsan_test_functional.cpp + rtsan_test_interceptors.cpp + rtsan_test_main.cpp) + +set(RTSAN_NOINST_TEST_SOURCES + ../rtsan_preinit.cpp + rtsan_test_context.cpp + rtsan_test_main.cpp) + +set(RTSAN_UNITTEST_HEADERS + rtsan_test_utilities.h) + +add_custom_target(RtsanUnitTests) +set_target_properties(RtsanUnitTests PROPERTIES FOLDER "Compiler-RT Tests") + +set(RTSAN_UNITTEST_LINK_FLAGS + ${COMPILER_RT_UNITTEST_LINK_FLAGS} + ${COMPILER_RT_UNWINDER_LINK_LIBS} + ${SANITIZER_TEST_CXX_LIBRARIES} + -no-pie) + +if (APPLE) + add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS) + list(APPEND RTSAN_UNITTEST_LINK_FLAGS ${WEAK_SYMBOL_LINK_FLAGS}) + list(APPEND RTSAN_UNITTEST_LINK_FLAGS ${DARWIN_osx_LINK_FLAGS}) + list(APPEND RTSAN_UNITTEST_CFLAGS ${DARWIN_osx_CFLAGS}) +else() + list(APPEND RTSAN_UNITTEST_LINK_FLAGS -latomic) +endif() + +set(COMPILER_RT_GOOGLETEST_SOURCES ${COMPILER_RT_GTEST_SOURCE} ${COMPILER_RT_GMOCK_SOURCE}) + +set(RTSAN_TEST_ARCH ${RTSAN_SUPPORTED_ARCH}) +if(APPLE) + darwin_filter_host_archs(RTSAN_SUPPORTED_ARCH RTSAN_TEST_ARCH) +endif() + +foreach(arch ${RTSAN_TEST_ARCH}) + set(RtsanTestObjects) + + # TODO: Re-enable once -fsanitize=realtime exists in clang driver + #generate_compiler_rt_tests(RtsanTestObjects + # RtsanUnitTests "Rtsan-${arch}-Test" ${arch} + # COMPILE_DEPS ${RTSAN_UNITTEST_HEADERS} + # SOURCES ${RTSAN_INST_TEST_SOURCES} ${COMPILER_RT_GOOGLETEST_SOURCES} + # DEPS llvm_gtest rtsan + # CFLAGS ${RTSAN_UNITTEST_CFLAGS} -fsanitize=realtime + # LINK_FLAGS ${RTSAN_UNITTEST_LINK_FLAGS} -fsanitize=realtime) + + set(RTSAN_TEST_RUNTIME RTRtsanTest.${arch}) + if(APPLE) + set(RTSAN_TEST_RUNTIME_OBJECTS + $ + $ + $ + $ + $ + $) + else() + set(RTSAN_TEST_RUNTIME_OBJECTS + $ + $ + $ + $ + $ + $ + $) + endif() + add_library(${RTSAN_TEST_RUNTIME} STATIC ${RTSAN_TEST_RUNTIME_OBJECTS}) + set_target_properties(${RTSAN_TEST_RUNTIME} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + FOLDER "Compiler-RT Runtime tests") + + set(RtsanNoInstTestObjects) + generate_compiler_rt_tests(RtsanNoInstTestObjects + RtsanUnitTests "Rtsan-${arch}-NoInstTest" ${arch} + COMPILE_DEPS ${RTSAN_UNITTEST_HEADERS} + SOURCES ${RTSAN_NOINST_TEST_SOURCES} + ${COMPILER_RT_GOOGLETEST_SOURCES} + DEPS llvm_gtest + CFLAGS ${RTSAN_UNITTEST_CFLAGS} + LINK_FLAGS ${RTSAN_UNITTEST_LINK_FLAGS} + RUNTIME ${RTSAN_TEST_RUNTIME}) + set_target_properties(RtsanUnitTests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endforeach() diff --git a/compiler-rt/lib/rtsan/tests/rtsan_test_context.cpp b/compiler-rt/lib/rtsan/tests/rtsan_test_context.cpp new file mode 100644 index 0000000000000..b7e73236a14cc --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/rtsan_test_context.cpp @@ -0,0 +1,69 @@ +//===--- rtsan_test_context.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "rtsan_test_utilities.h" + +#include "rtsan_context.h" + +TEST(TestRtsanContext, CanCreateContext) { __rtsan::Context context{}; } + +TEST(TestRtsanContext, ExpectNotRealtimeDoesNotDieBeforeRealtimePush) { + __rtsan::Context context{}; + context.ExpectNotRealtime("do_some_stuff"); +} + +TEST(TestRtsanContext, ExpectNotRealtimeDoesNotDieAfterPushAndPop) { + __rtsan::Context context{}; + context.RealtimePush(); + context.RealtimePop(); + context.ExpectNotRealtime("do_some_stuff"); +} + +TEST(TestRtsanContext, ExpectNotRealtimeDiesAfterRealtimePush) { + __rtsan::Context context{}; + + context.RealtimePush(); + EXPECT_DEATH(context.ExpectNotRealtime("do_some_stuff"), ""); +} + +TEST(TestRtsanContext, + ExpectNotRealtimeDiesAfterRealtimeAfterMorePushesThanPops) { + __rtsan::Context context{}; + + context.RealtimePush(); + context.RealtimePush(); + context.RealtimePush(); + context.RealtimePop(); + context.RealtimePop(); + EXPECT_DEATH(context.ExpectNotRealtime("do_some_stuff"), ""); +} + +TEST(TestRtsanContext, ExpectNotRealtimeDoesNotDieAfterBypassPush) { + __rtsan::Context context{}; + + context.RealtimePush(); + context.BypassPush(); + context.ExpectNotRealtime("do_some_stuff"); +} + +TEST(TestRtsanContext, + ExpectNotRealtimeDoesNotDieIfBypassDepthIsGreaterThanZero) { + __rtsan::Context context{}; + + context.RealtimePush(); + context.BypassPush(); + context.BypassPush(); + context.BypassPush(); + context.BypassPop(); + context.BypassPop(); + context.ExpectNotRealtime("do_some_stuff"); + context.BypassPop(); + EXPECT_DEATH(context.ExpectNotRealtime("do_some_stuff"), ""); +} diff --git a/compiler-rt/lib/rtsan/tests/rtsan_test_functional.cpp b/compiler-rt/lib/rtsan/tests/rtsan_test_functional.cpp new file mode 100644 index 0000000000000..97afb1eefb640 --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/rtsan_test_functional.cpp @@ -0,0 +1,207 @@ +//===--- 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 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>(); + 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, 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 &&function) { function(); } +} // namespace + +TEST(TestRtsan, CopyingALambdaWithLargeCaptureDiesWhenRealtime) { + std::array lots_of_data; + auto lambda = [lots_of_data]() mutable { + // Stop everything getting optimised out + lots_of_data[3] = 0.25f; + EXPECT_EQ(16, lots_of_data.size()); + EXPECT_EQ(0.25f, lots_of_data[3]); + }; + auto Func = [&]() { InvokeStdFunction(lambda); }; + ExpectRealtimeDeath(Func); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsan, AccessingALargeAtomicVariableDiesWhenRealtime) { + std::atomic small_atomic{0.0f}; + ASSERT_TRUE(small_atomic.is_lock_free()); + RealtimeInvoke([&small_atomic]() { float x = small_atomic.load(); }); + + std::atomic> large_atomic; + ASSERT_FALSE(large_atomic.is_lock_free()); + auto Func = [&]() { auto x = large_atomic.load(); }; + 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 RealtimeUnsafeFunc = [&]() { + __rtsan_off(); + mutex.lock(); + mutex.unlock(); + __rtsan_on(); + }; + RealtimeInvoke(RealtimeUnsafeFunc); +} diff --git a/compiler-rt/lib/rtsan/tests/rtsan_test_interceptors.cpp b/compiler-rt/lib/rtsan/tests/rtsan_test_interceptors.cpp new file mode 100644 index 0000000000000..f5b016089087d --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/rtsan_test_interceptors.cpp @@ -0,0 +1,516 @@ +//===--- rtsan_test_interceptors.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include +#include + +#include "rtsan_test_utilities.h" + +#if SANITIZER_APPLE +#include +#include +#endif + +#if SANITIZER_INTERCEPT_MEMALIGN || SANITIZER_INTERCEPT_PVALLOC +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace testing; +using namespace rtsan_testing; +using namespace std::chrono_literals; + +void *FakeThreadEntryPoint(void *) { return nullptr; } + +class RtsanFileTest : public ::testing::Test { +protected: + void SetUp() override { + const ::testing::TestInfo *const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + file_path_ = std::string("/tmp/rtsan_temporary_test_file_") + + test_info->name() + ".txt"; + RemoveTemporaryFile(); + } + + // Gets a file path with the test's name in it + // This file will be removed if it exists at the end of the test + const char *GetTemporaryFilePath() const { return file_path_.c_str(); } + + void TearDown() override { RemoveTemporaryFile(); } + +private: + void RemoveTemporaryFile() const { std::remove(GetTemporaryFilePath()); } + std::string file_path_; +}; + +/* + Allocation and deallocation +*/ + +TEST(TestRtsanInterceptors, MallocDiesWhenRealtime) { + auto Func = []() { EXPECT_NE(nullptr, malloc(1)); }; + ExpectRealtimeDeath(Func, "malloc"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, ReallocDiesWhenRealtime) { + void *ptr_1 = malloc(1); + auto Func = [ptr_1]() { EXPECT_NE(nullptr, realloc(ptr_1, 8)); }; + ExpectRealtimeDeath(Func, "realloc"); + ExpectNonRealtimeSurvival(Func); +} + +#if SANITIZER_APPLE +TEST(TestRtsanInterceptors, ReallocfDiesWhenRealtime) { + void *ptr_1 = malloc(1); + auto Func = [ptr_1]() { EXPECT_NE(nullptr, reallocf(ptr_1, 8)); }; + ExpectRealtimeDeath(Func, "reallocf"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +TEST(TestRtsanInterceptors, VallocDiesWhenRealtime) { + auto Func = []() { EXPECT_NE(nullptr, valloc(4)); }; + ExpectRealtimeDeath(Func, "valloc"); + ExpectNonRealtimeSurvival(Func); +} + +#if SANITIZER_INTERCEPT_ALIGNED_ALLOC +TEST(TestRtsanInterceptors, AlignedAllocDiesWhenRealtime) { + auto Func = []() { EXPECT_NE(nullptr, aligned_alloc(16, 32)); }; + ExpectRealtimeDeath(Func, "aligned_alloc"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +// free_sized and free_aligned_sized (both C23) are not yet supported +TEST(TestRtsanInterceptors, FreeDiesWhenRealtime) { + void *ptr_1 = malloc(1); + void *ptr_2 = malloc(1); + ExpectRealtimeDeath([ptr_1]() { free(ptr_1); }, "free"); + ExpectNonRealtimeSurvival([ptr_2]() { free(ptr_2); }); + + // Prevent malloc/free pair being optimised out + ASSERT_NE(nullptr, ptr_1); + ASSERT_NE(nullptr, ptr_2); +} + +TEST(TestRtsanInterceptors, FreeSurvivesWhenRealtimeIfArgumentIsNull) { + RealtimeInvoke([]() { free(NULL); }); + ExpectNonRealtimeSurvival([]() { free(NULL); }); +} + +TEST(TestRtsanInterceptors, PosixMemalignDiesWhenRealtime) { + auto Func = []() { + void *ptr; + posix_memalign(&ptr, 4, 4); + }; + ExpectRealtimeDeath(Func, "posix_memalign"); + ExpectNonRealtimeSurvival(Func); +} + +#if SANITIZER_INTERCEPT_MEMALIGN +TEST(TestRtsanInterceptors, MemalignDiesWhenRealtime) { + auto Func = []() { EXPECT_NE(memalign(2, 2048), nullptr); }; + ExpectRealtimeDeath(Func, "memalign"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +#if SANITIZER_INTERCEPT_PVALLOC +TEST(TestRtsanInterceptors, PvallocDiesWhenRealtime) { + auto Func = []() { EXPECT_NE(pvalloc(2048), nullptr); }; + ExpectRealtimeDeath(Func, "pvalloc"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +/* + Sleeping +*/ + +TEST(TestRtsanInterceptors, SleepDiesWhenRealtime) { + auto Func = []() { sleep(0u); }; + ExpectRealtimeDeath(Func, "sleep"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, UsleepDiesWhenRealtime) { + auto Func = []() { usleep(1u); }; + ExpectRealtimeDeath(Func, "usleep"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, NanosleepDiesWhenRealtime) { + auto Func = []() { + timespec T{}; + nanosleep(&T, &T); + }; + ExpectRealtimeDeath(Func, "nanosleep"); + ExpectNonRealtimeSurvival(Func); +} + +/* + Filesystem +*/ + +TEST_F(RtsanFileTest, OpenDiesWhenRealtime) { + auto func = [this]() { open(GetTemporaryFilePath(), O_RDONLY); }; + ExpectRealtimeDeath(func, "open"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, OpenatDiesWhenRealtime) { + auto func = [this]() { openat(0, GetTemporaryFilePath(), O_RDONLY); }; + ExpectRealtimeDeath(func, "openat"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, OpenCreatesFileWithProperMode) { + const int mode = S_IRGRP | S_IROTH | S_IRUSR | S_IWUSR; + + const int fd = open(GetTemporaryFilePath(), O_CREAT | O_WRONLY, mode); + ASSERT_THAT(fd, Ne(-1)); + close(fd); + + struct stat st; + ASSERT_THAT(stat(GetTemporaryFilePath(), &st), Eq(0)); + + // Mask st_mode to get permission bits only + ASSERT_THAT(st.st_mode & 0777, Eq(mode)); +} + +TEST_F(RtsanFileTest, CreatDiesWhenRealtime) { + auto func = [this]() { creat(GetTemporaryFilePath(), S_IWOTH | S_IROTH); }; + ExpectRealtimeDeath(func, "creat"); + ExpectNonRealtimeSurvival(func); +} + +TEST(TestRtsanInterceptors, FcntlDiesWhenRealtime) { + auto func = []() { fcntl(0, F_GETFL); }; + ExpectRealtimeDeath(func, "fcntl"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, FcntlFlockDiesWhenRealtime) { + int fd = creat(GetTemporaryFilePath(), S_IRUSR | S_IWUSR); + ASSERT_THAT(fd, Ne(-1)); + + auto func = [fd]() { + struct flock lock {}; + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_pid = ::getpid(); + + ASSERT_THAT(fcntl(fd, F_GETLK, &lock), Eq(0)); + ASSERT_THAT(lock.l_type, F_UNLCK); + }; + ExpectRealtimeDeath(func, "fcntl"); + ExpectNonRealtimeSurvival(func); + + close(fd); +} + +TEST_F(RtsanFileTest, FcntlSetFdDiesWhenRealtime) { + int fd = creat(GetTemporaryFilePath(), S_IRUSR | S_IWUSR); + ASSERT_THAT(fd, Ne(-1)); + + auto func = [fd]() { + int old_flags = fcntl(fd, F_GETFD); + ASSERT_THAT(fcntl(fd, F_SETFD, FD_CLOEXEC), Eq(0)); + + int flags = fcntl(fd, F_GETFD); + ASSERT_THAT(flags, Ne(-1)); + ASSERT_THAT(flags & FD_CLOEXEC, Eq(FD_CLOEXEC)); + + ASSERT_THAT(fcntl(fd, F_SETFD, old_flags), Eq(0)); + ASSERT_THAT(fcntl(fd, F_GETFD), Eq(old_flags)); + }; + + ExpectRealtimeDeath(func, "fcntl"); + ExpectNonRealtimeSurvival(func); + + close(fd); +} + +TEST(TestRtsanInterceptors, CloseDiesWhenRealtime) { + auto func = []() { close(0); }; + ExpectRealtimeDeath(func, "close"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, FopenDiesWhenRealtime) { + auto func = [this]() { + auto fd = fopen(GetTemporaryFilePath(), "w"); + EXPECT_THAT(fd, Ne(nullptr)); + }; + ExpectRealtimeDeath(func, "fopen"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, FreadDiesWhenRealtime) { + auto fd = fopen(GetTemporaryFilePath(), "w"); + auto func = [fd]() { + char c{}; + fread(&c, 1, 1, fd); + }; + ExpectRealtimeDeath(func, "fread"); + ExpectNonRealtimeSurvival(func); + if (fd != nullptr) + fclose(fd); +} + +TEST_F(RtsanFileTest, FwriteDiesWhenRealtime) { + auto fd = fopen(GetTemporaryFilePath(), "w"); + ASSERT_NE(nullptr, fd); + auto message = "Hello, world!"; + auto func = [&]() { fwrite(&message, 1, 4, fd); }; + ExpectRealtimeDeath(func, "fwrite"); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, FcloseDiesWhenRealtime) { + auto fd = fopen(GetTemporaryFilePath(), "w"); + EXPECT_THAT(fd, Ne(nullptr)); + auto func = [fd]() { fclose(fd); }; + ExpectRealtimeDeath(func, "fclose"); + ExpectNonRealtimeSurvival(func); +} + +TEST(TestRtsanInterceptors, PutsDiesWhenRealtime) { + auto func = []() { puts("Hello, world!\n"); }; + ExpectRealtimeDeath(func); + ExpectNonRealtimeSurvival(func); +} + +TEST_F(RtsanFileTest, FputsDiesWhenRealtime) { + auto fd = fopen(GetTemporaryFilePath(), "w"); + ASSERT_THAT(fd, Ne(nullptr)) << errno; + auto func = [fd]() { fputs("Hello, world!\n", fd); }; + ExpectRealtimeDeath(func); + ExpectNonRealtimeSurvival(func); + if (fd != nullptr) + fclose(fd); +} + +/* + Concurrency +*/ + +TEST(TestRtsanInterceptors, PthreadCreateDiesWhenRealtime) { + auto Func = []() { + pthread_t thread{}; + const pthread_attr_t attr{}; + struct thread_info *thread_info; + pthread_create(&thread, &attr, &FakeThreadEntryPoint, thread_info); + }; + ExpectRealtimeDeath(Func, "pthread_create"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, PthreadMutexLockDiesWhenRealtime) { + auto Func = []() { + pthread_mutex_t mutex{}; + pthread_mutex_lock(&mutex); + }; + + ExpectRealtimeDeath(Func, "pthread_mutex_lock"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, PthreadMutexUnlockDiesWhenRealtime) { + auto Func = []() { + pthread_mutex_t mutex{}; + pthread_mutex_unlock(&mutex); + }; + + ExpectRealtimeDeath(Func, "pthread_mutex_unlock"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, PthreadMutexJoinDiesWhenRealtime) { + auto Func = []() { + pthread_t thread{}; + pthread_join(thread, nullptr); + }; + + ExpectRealtimeDeath(Func, "pthread_join"); + ExpectNonRealtimeSurvival(Func); +} + +#if SANITIZER_APPLE + +#pragma clang diagnostic push +// OSSpinLockLock is deprecated, but still in use in libc++ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +TEST(TestRtsanInterceptors, OsSpinLockLockDiesWhenRealtime) { + auto Func = []() { + OSSpinLock spin_lock{}; + OSSpinLockLock(&spin_lock); + }; + ExpectRealtimeDeath(Func, "OSSpinLockLock"); + ExpectNonRealtimeSurvival(Func); +} +#pragma clang diagnostic pop + +TEST(TestRtsanInterceptors, OsUnfairLockLockDiesWhenRealtime) { + auto Func = []() { + os_unfair_lock_s unfair_lock{}; + os_unfair_lock_lock(&unfair_lock); + }; + ExpectRealtimeDeath(Func, "os_unfair_lock_lock"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +#if SANITIZER_LINUX +TEST(TestRtsanInterceptors, SpinLockLockDiesWhenRealtime) { + pthread_spinlock_t spin_lock; + pthread_spin_init(&spin_lock, PTHREAD_PROCESS_SHARED); + auto Func = [&]() { pthread_spin_lock(&spin_lock); }; + ExpectRealtimeDeath(Func, "pthread_spin_lock"); + ExpectNonRealtimeSurvival(Func); +} +#endif + +TEST(TestRtsanInterceptors, PthreadCondSignalDiesWhenRealtime) { + pthread_cond_t cond{}; + pthread_cond_init(&cond, NULL); + + auto Func = [&cond]() { pthread_cond_signal(&cond); }; + ExpectRealtimeDeath(Func, "pthread_cond_signal"); + ExpectNonRealtimeSurvival(Func); + + pthread_cond_destroy(&cond); +} + +TEST(TestRtsanInterceptors, PthreadCondBroadcastDiesWhenRealtime) { + pthread_cond_t cond{}; + pthread_cond_init(&cond, NULL); + + auto Func = [&cond]() { pthread_cond_broadcast(&cond); }; + ExpectRealtimeDeath(Func, "pthread_cond_broadcast"); + ExpectNonRealtimeSurvival(Func); + + pthread_cond_destroy(&cond); +} + +TEST(TestRtsanInterceptors, PthreadCondWaitDiesWhenRealtime) { + pthread_cond_t cond; + pthread_mutex_t mutex; + ASSERT_EQ(0, pthread_cond_init(&cond, nullptr)); + ASSERT_EQ(0, pthread_mutex_init(&mutex, nullptr)); + + auto Func = [&]() { pthread_cond_wait(&cond, &mutex); }; + ExpectRealtimeDeath(Func, "pthread_cond_wait"); + // It's very difficult to test the success case here without doing some + // sleeping, which is at the mercy of the scheduler. What's really important + // here is the interception - so we're only testing that for now. + + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +TEST(TestRtsanInterceptors, PthreadRwlockRdlockDiesWhenRealtime) { + auto Func = []() { + pthread_rwlock_t rw_lock; + pthread_rwlock_rdlock(&rw_lock); + }; + ExpectRealtimeDeath(Func, "pthread_rwlock_rdlock"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, PthreadRwlockUnlockDiesWhenRealtime) { + auto Func = []() { + pthread_rwlock_t rw_lock; + pthread_rwlock_unlock(&rw_lock); + }; + ExpectRealtimeDeath(Func, "pthread_rwlock_unlock"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, PthreadRwlockWrlockDiesWhenRealtime) { + auto Func = []() { + pthread_rwlock_t rw_lock; + pthread_rwlock_wrlock(&rw_lock); + }; + ExpectRealtimeDeath(Func, "pthread_rwlock_wrlock"); + ExpectNonRealtimeSurvival(Func); +} + +/* + Sockets +*/ +TEST(TestRtsanInterceptors, OpeningASocketDiesWhenRealtime) { + auto Func = []() { socket(PF_INET, SOCK_STREAM, 0); }; + ExpectRealtimeDeath(Func, "socket"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, SendToASocketDiesWhenRealtime) { + auto Func = []() { send(0, nullptr, 0, 0); }; + ExpectRealtimeDeath(Func, "send"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, SendmsgToASocketDiesWhenRealtime) { + msghdr msg{}; + auto Func = [&]() { sendmsg(0, &msg, 0); }; + ExpectRealtimeDeath(Func, "sendmsg"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, SendtoToASocketDiesWhenRealtime) { + sockaddr addr{}; + socklen_t len{}; + auto Func = [&]() { sendto(0, nullptr, 0, 0, &addr, len); }; + ExpectRealtimeDeath(Func, "sendto"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, RecvFromASocketDiesWhenRealtime) { + auto Func = []() { recv(0, nullptr, 0, 0); }; + ExpectRealtimeDeath(Func, "recv"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, RecvfromOnASocketDiesWhenRealtime) { + sockaddr addr{}; + socklen_t len{}; + auto Func = [&]() { recvfrom(0, nullptr, 0, 0, &addr, &len); }; + ExpectRealtimeDeath(Func, "recvfrom"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, RecvmsgOnASocketDiesWhenRealtime) { + msghdr msg{}; + auto Func = [&]() { recvmsg(0, &msg, 0); }; + ExpectRealtimeDeath(Func, "recvmsg"); + ExpectNonRealtimeSurvival(Func); +} + +TEST(TestRtsanInterceptors, ShutdownOnASocketDiesWhenRealtime) { + auto Func = [&]() { shutdown(0, 0); }; + ExpectRealtimeDeath(Func, "shutdown"); + ExpectNonRealtimeSurvival(Func); +} diff --git a/compiler-rt/lib/rtsan/tests/rtsan_test_main.cpp b/compiler-rt/lib/rtsan/tests/rtsan_test_main.cpp new file mode 100644 index 0000000000000..255ac9497103e --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/rtsan_test_main.cpp @@ -0,0 +1,17 @@ +//===--- rtsan_test_main.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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_test_utils.h" + +int main(int argc, char **argv) { + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/compiler-rt/lib/rtsan/tests/rtsan_test_utilities.h b/compiler-rt/lib/rtsan/tests/rtsan_test_utilities.h new file mode 100644 index 0000000000000..6ca09cf657094 --- /dev/null +++ b/compiler-rt/lib/rtsan/tests/rtsan_test_utilities.h @@ -0,0 +1,47 @@ +//===--- rtsan_test_utilities.h - 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "rtsan.h" +#include "gmock/gmock.h" +#include + +namespace rtsan_testing { + +template void RealtimeInvoke(Function &&Func) { + __rtsan_realtime_enter(); + std::forward(Func)(); + __rtsan_realtime_exit(); +} + +template +void ExpectRealtimeDeath(Function &&Func, + const char *intercepted_method_name = nullptr) { + + using namespace testing; + + auto GetExpectedErrorSubstring = [&]() -> std::string { + return intercepted_method_name != nullptr + ? "Real-time violation: intercepted call to real-time unsafe " + "function `" + + std::string(intercepted_method_name) + "`" + : ""; + }; + + EXPECT_EXIT(RealtimeInvoke(std::forward(Func)), + ExitedWithCode(EXIT_FAILURE), GetExpectedErrorSubstring()); +} + +template void ExpectNonRealtimeSurvival(Function &&Func) { + std::forward(Func)(); +} + +} // namespace rtsan_testing diff --git a/compiler-rt/test/rtsan/CMakeLists.txt b/compiler-rt/test/rtsan/CMakeLists.txt new file mode 100644 index 0000000000000..ba1070467f1a3 --- /dev/null +++ b/compiler-rt/test/rtsan/CMakeLists.txt @@ -0,0 +1,58 @@ + + + + +###### +# TODO: Full lit tests coming in a future review when we introduce the codegen +###### + + + + +set(RTSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(RTSAN_TESTSUITES) +set(RTSAN_FDR_TESTSUITES) + +set(RTSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +set(RTSAN_FDR_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +set(RTSAN_TEST_ARCH ${RTSAN_SUPPORTED_ARCH}) +if(APPLE) + darwin_filter_host_archs(RTSAN_SUPPORTED_ARCH RTSAN_TEST_ARCH) +endif() + +if (COMPILER_RT_HAS_RTSAN) + foreach(arch ${RTSAN_TEST_ARCH}) + set(RTSAN_TEST_TARGET_ARCH ${arch}) + string(TOLOWER "-${arch}-${OS_NAME}" RTSAN_TEST_CONFIG_SUFFIX) + get_test_cc_for_arch(${arch} RTSAN_TEST_TARGET_CC RTSAN_TEST_TARGET_CFLAGS) + string(TOUPPER ${arch} ARCH_UPPER_CASE) + set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config) + + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py) + list(APPEND RTSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) + endforeach() +endif() + +if(COMPILER_RT_INCLUDE_TESTS) + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg.py) + if(COMPILER_RT_RTSAN_HAS_STATIC_RUNTIME) + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/dynamic/lit.site.cfg.py) + endif() + list(APPEND RTSAN_TEST_DEPS RtsanUnitTests) + list(APPEND RTSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit) + if(COMPILER_RT_RTSAN_HAS_STATIC_RUNTIME) + list(APPEND RTSAN_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit/dynamic) + endif() +endif() + +add_lit_testsuite(check-rtsan "Running the Rtsan tests" + ${RTSAN_TESTSUITES} + DEPENDS ${RTSAN_TEST_DEPS}) +set_target_properties(check-rtsan PROPERTIES FOLDER "Compiler-RT Misc") diff --git a/compiler-rt/test/rtsan/Unit/lit.site.cfg.py.in b/compiler-rt/test/rtsan/Unit/lit.site.cfg.py.in new file mode 100644 index 0000000000000..59e1e10360b52 --- /dev/null +++ b/compiler-rt/test/rtsan/Unit/lit.site.cfg.py.in @@ -0,0 +1,25 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Load common config for all compiler-rt unit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/unittests/lit.common.unit.configured") + +# Setup config name. +config.name = 'RealtimeSanitizer-Unit' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with ASan unit tests. +# FIXME: De-hardcode this path. +config.test_exec_root = "@COMPILER_RT_BINARY_DIR@/lib/rtsan/tests" +config.test_source_root = config.test_exec_root + +if not config.parallelism_group: + config.parallelism_group = 'shadow-memory' + +if config.host_os == 'Darwin': + # On Darwin, we default to ignore_noninstrumented_modules=1, which also + # suppresses some races the tests are supposed to find. See rtsan/lit.cfg.py. + if 'RTSAN_OPTIONS' in config.environment: + config.environment['RTSAN_OPTIONS'] += ':ignore_noninstrumented_modules=0' + else: + config.environment['RTSAN_OPTIONS'] = 'ignore_noninstrumented_modules=0' + config.environment['RTSAN_OPTIONS'] += ':ignore_interceptors_accesses=0' diff --git a/compiler-rt/test/rtsan/lit.cfg.py b/compiler-rt/test/rtsan/lit.cfg.py new file mode 100644 index 0000000000000..b262ecfa7fb4b --- /dev/null +++ b/compiler-rt/test/rtsan/lit.cfg.py @@ -0,0 +1,49 @@ +import os + +# Setup config name. +config.name = "RTSAN" + config.name_suffix + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Setup default compiler flags use with -frtsan-instrument option. +clang_rtsan_cflags = ["-frtsan-instrument", config.target_cflags] + +clang_rtsan_cxxflags = config.cxx_mode_flags + clang_rtsan_cflags + + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + + +# Assume that llvm-rtsan is in the config.llvm_tools_dir. +llvm_rtsan = os.path.join(config.llvm_tools_dir, "llvm-rtsan") + +# Setup substitutions. +if config.host_os == "Linux": + libdl_flag = "-ldl" +else: + libdl_flag = "" + +config.substitutions.append(("%clang ", build_invocation([config.target_cflags]))) +config.substitutions.append( + ("%clangxx ", build_invocation(config.cxx_mode_flags + [config.target_cflags])) +) +config.substitutions.append(("%clang_rtsan ", build_invocation(clang_rtsan_cflags))) +config.substitutions.append(("%clangxx_rtsan", build_invocation(clang_rtsan_cxxflags))) +config.substitutions.append(("%llvm_rtsan", llvm_rtsan)) + +# Default test suffixes. +config.suffixes = [".c", ".cpp"] + +if config.host_os not in ["Darwin", "FreeBSD", "Linux", "NetBSD", "OpenBSD"]: + config.unsupported = True +elif "64" not in config.host_arch: + if "arm" in config.host_arch: + if "-mthumb" in config.target_cflags: + config.unsupported = True + else: + config.unsupported = True + +if config.host_os == "NetBSD": + config.substitutions.insert(0, ("%run", config.netbsd_nomprotect_prefix)) diff --git a/compiler-rt/test/rtsan/lit.site.cfg.py.in b/compiler-rt/test/rtsan/lit.site.cfg.py.in new file mode 100644 index 0000000000000..5b458645e94dc --- /dev/null +++ b/compiler-rt/test/rtsan/lit.site.cfg.py.in @@ -0,0 +1,17 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@RTSAN_TEST_CONFIG_SUFFIX@" +config.rtsan_lit_source_dir = "@RTSAN_LIT_SOURCE_DIR@" +config.target_cflags = "@RTSAN_TEST_TARGET_CFLAGS@" +config.target_arch = "@RTSAN_TEST_TARGET_ARCH@" +config.built_with_llvm = ("@COMPILER_RT_STANDALONE_BUILD@" != "TRUE") + +if config.built_with_llvm: + config.available_features.add('built-in-llvm-tree') + +# Load common config for all compiler-rt lit tests +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py")