diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def index b228ffd07ee74..ffb23974fe371 100644 --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -37,6 +37,9 @@ #endif +// RealtimeSanitizer +SANITIZER("realtime", Realtime) + // AddressSanitizer SANITIZER("address", Address) diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 07070ec4fc065..dd7c128232772 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -79,6 +79,7 @@ class SanitizerArgs { bool needsStableAbi() const { return StableABI; } bool needsMemProfRt() const { return NeedsMemProfRt; } + bool needsRadsanRt() const { return Sanitizers.has(SanitizerKind::Realtime); } bool needsAsanRt() const { return Sanitizers.has(SanitizerKind::Address); } bool needsHwasanRt() const { return Sanitizers.has(SanitizerKind::HWAddress); diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp index 6796b43a15502..6508c4ca690b5 100644 --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -1357,6 +1357,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, if (!Args.hasArg(options::OPT_shared)) HelperStaticRuntimes.push_back("hwasan-preinit"); } + if (SanArgs.needsRadsanRt() && SanArgs.linkRuntimes()) + SharedRuntimes.push_back("radsan"); } // The stats_client library is also statically linked into DSOs. @@ -1382,6 +1384,10 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, StaticRuntimes.push_back("asan_cxx"); } + if (!SanArgs.needsSharedRt() && SanArgs.needsRadsanRt() && SanArgs.linkRuntimes()) { + StaticRuntimes.push_back("radsan"); + } + if (!SanArgs.needsSharedRt() && SanArgs.needsMemProfRt()) { StaticRuntimes.push_back("memprof"); if (SanArgs.linkCXXRuntimes()) diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp index caf6c4a444fdc..cb96f9992ab7f 100644 --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -1487,6 +1487,8 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args, const char *sanitizer = nullptr; if (Sanitize.needsUbsanRt()) { sanitizer = "UndefinedBehaviorSanitizer"; + } else if (Sanitize.needsRadsanRt()) { + sanitizer = "RealtimeSanitizer"; } else if (Sanitize.needsAsanRt()) { sanitizer = "AddressSanitizer"; } else if (Sanitize.needsTsanRt()) { @@ -1509,6 +1511,11 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args, AddLinkSanitizerLibArgs(Args, CmdArgs, "asan"); } } + if(Sanitize.needsRadsanRt()) + { + assert(Sanitize.needsSharedRt() && "Static sanitizer runtimes not supported"); + AddLinkSanitizerLibArgs(Args, CmdArgs, "radsan"); + } if (Sanitize.needsLsanRt()) AddLinkSanitizerLibArgs(Args, CmdArgs, "lsan"); if (Sanitize.needsUbsanRt()) { @@ -3393,6 +3400,7 @@ SanitizerMask Darwin::getSupportedSanitizers() const { const bool IsAArch64 = getTriple().getArch() == llvm::Triple::aarch64; SanitizerMask Res = ToolChain::getSupportedSanitizers(); Res |= SanitizerKind::Address; + Res |= SanitizerKind::Realtime; Res |= SanitizerKind::PointerCompare; Res |= SanitizerKind::PointerSubtract; Res |= SanitizerKind::Leak; diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp index db2c20d7b461d..416d530a36ff7 100644 --- a/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp @@ -799,6 +799,7 @@ SanitizerMask Linux::getSupportedSanitizers() const { const bool IsHexagon = getTriple().getArch() == llvm::Triple::hexagon; SanitizerMask Res = ToolChain::getSupportedSanitizers(); Res |= SanitizerKind::Address; + Res |= SanitizerKind::Realtime; Res |= SanitizerKind::PointerCompare; Res |= SanitizerKind::PointerSubtract; Res |= SanitizerKind::Fuzzer; diff --git a/clang/runtime/CMakeLists.txt b/clang/runtime/CMakeLists.txt index 65fcdc2868f03..c56bff11d476b 100644 --- a/clang/runtime/CMakeLists.txt +++ b/clang/runtime/CMakeLists.txt @@ -150,6 +150,7 @@ if(LLVM_BUILD_EXTERNAL_COMPILER_RT AND EXISTS ${COMPILER_RT_SRC_ROOT}/) check-lsan check-msan check-profile + check-radsan check-safestack check-sanitizer check-tsan diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index 2fe06273a814c..5017d236c1369 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_RADSAN_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 ba740af9e1d60..9b758227d97cb 100644 --- a/compiler-rt/cmake/config-ix.cmake +++ b/compiler-rt/cmake/config-ix.cmake @@ -611,6 +611,9 @@ if(APPLE) list_intersect(ASAN_SUPPORTED_ARCH ALL_ASAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) + list_intersect(RADSAN_SUPPORTED_ARCH + ALL_RADSAN_SUPPORTED_ARCH + SANITIZER_COMMON_SUPPORTED_ARCH) list_intersect(DFSAN_SUPPORTED_ARCH ALL_DFSAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) @@ -674,6 +677,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(RADSAN_SUPPORTED_ARCH ${ALL_RADSAN_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}) @@ -726,7 +730,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;asan_abi) +set(ALL_SANITIZERS asan;radsan;dfsan;msan;hwasan;tsan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;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}") @@ -757,6 +761,12 @@ else() set(COMPILER_RT_HAS_ASAN FALSE) endif() +if (COMPILER_RT_HAS_SANITIZER_COMMON AND RADSAN_SUPPORTED_ARCH) + set(COMPILER_RT_HAS_RADSAN TRUE) +else() + set(COMPILER_RT_HAS_RADSAN 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/radsan/CMakeLists.txt b/compiler-rt/lib/radsan/CMakeLists.txt new file mode 100644 index 0000000000000..c7d1dacf3dbc7 --- /dev/null +++ b/compiler-rt/lib/radsan/CMakeLists.txt @@ -0,0 +1,92 @@ +include_directories(..) + +set(RADSAN_CXX_SOURCES + radsan.cpp + radsan_context.cpp + radsan_stack.cpp + radsan_interceptors.cpp) + +set(RADSAN_PREINIT_SOURCES + radsan_preinit.cpp) + +set(RADSAN_HEADERS + radsan.h + radsan_context.h + radsan_stack.h) + +set(RADSAN_DEPS) + +set(RADSAN_CFLAGS + ${COMPILER_RT_COMMON_CFLAGS} + ${COMPILER_RT_CXX_CFLAGS} + -DSANITIZER_COMMON_NO_REDEFINE_BUILTINS) +set(RADSAN_LINK_FLAGS ${COMPILER_RT_COMMON_LINK_FLAGS}) +set(RADSAN_LINK_LIBS + ${COMPILER_RT_UNWINDER_LINK_LIBS} + ${COMPILER_RT_CXX_LINK_LIBS}) + +if(APPLE) + add_compiler_rt_object_libraries(RTRadsan + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${RADSAN_SUPPORTED_ARCH} + SOURCES ${RADSAN_CXX_SOURCES} + ADDITIONAL_HEADERS ${RADSAN_HEADERS} + CFLAGS ${RADSAN_CFLAGS} + DEPS ${RADSAN_DEPS}) +else() + add_compiler_rt_object_libraries(RTRadsan + ARCHS ${RADSAN_SUPPORTED_ARCH} + SOURCES ${RADSAN_CXX_SOURCES} + ADDITIONAL_HEADERS ${RADSAN_HEADERS} + CFLAGS ${RADSAN_CFLAGS} + DEPS ${RADSAN_DEPS}) + add_compiler_rt_object_libraries(RTRadsan_preinit + ARCHS ${RADSAN_SUPPORTED_ARCH} + SOURCES ${RADSAN_PREINIT_SOURCES} + ADDITIONAL_HEADERS ${RADSAN_HEADERS} + CFLAGS ${RADSAN_CFLAGS}) +endif() + +set(RADSAN_COMMON_RUNTIME_OBJECT_LIBS + RTInterception + RTSanitizerCommon + RTSanitizerCommonLibc + RTSanitizerCommonCoverage + RTSanitizerCommonSymbolizer) + +append_list_if(COMPILER_RT_HAS_LIBDL dl RADSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBRT rt RADSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBM m RADSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBPTHREAD pthread RADSAN_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBLOG log RADSAN_LINK_LIBS) + +add_compiler_rt_component(radsan) + +if (APPLE) + add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS) + set(RADSAN_LINK_FLAGS ${RADSAN_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}) + + add_compiler_rt_runtime(clang_rt.radsan + SHARED + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${RADSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTRadsan + ${RADSAN_COMMON_RUNTIME_OBJECT_LIBS} + LINK_FLAGS ${RADSAN_LINK_FLAGS} + LINK_LIBS ${RADSAN_LINK_LIBS} + PARENT_TARGET radsan) +else() + add_compiler_rt_runtime(clang_rt.radsan + STATIC + ARCHS ${RADSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTRadsan_preinit + RTRadsan + ${RADSAN_COMMON_RUNTIME_OBJECT_LIBS} + LINK_FLAGS ${RADSAN_LINK_FLAGS} + CFLAGS ${RADSAN_CFLAGS} + PARENT_TARGET radsan) +endif() + +if(COMPILER_RT_INCLUDE_TESTS) + add_subdirectory(tests) +endif() diff --git a/compiler-rt/lib/radsan/radsan.cpp b/compiler-rt/lib/radsan/radsan.cpp new file mode 100644 index 0000000000000..bd3295e547aa7 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan.cpp @@ -0,0 +1,32 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include +#include +#include +#include + +extern "C" { +RADSAN_EXPORT void radsan_init() { radsan::initialiseInterceptors(); } + +RADSAN_EXPORT void radsan_realtime_enter() { + radsan::getContextForThisThread().realtimePush(); +} + +RADSAN_EXPORT void radsan_realtime_exit() { + radsan::getContextForThisThread().realtimePop(); +} + +RADSAN_EXPORT void radsan_off() { + radsan::getContextForThisThread().bypassPush(); +} + +RADSAN_EXPORT void radsan_on() { + radsan::getContextForThisThread().bypassPop(); +} +} diff --git a/compiler-rt/lib/radsan/radsan.h b/compiler-rt/lib/radsan/radsan.h new file mode 100644 index 0000000000000..4472fb566ef63 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan.h @@ -0,0 +1,73 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#pragma once + +#define RADSAN_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +/** + Initialise radsan interceptors. A call to this method is added to the + preinit array on Linux systems. + + @warning Do not call this method as a user. +*/ +RADSAN_EXPORT void radsan_init(); + +/** Enter real-time context. + + When in a real-time context, RADSan interceptors will error if realtime + violations are detected. Calls to this method are injected at the code + generation stage when RADSan is enabled. + + @warning Do not call this method as a user +*/ +RADSAN_EXPORT void radsan_realtime_enter(); + +/** Exit the real-time context. + + When not in a real-time context, RADSan interceptors will simply forward + intercepted method calls to the real methods. + + @warning Do not call this method as a user +*/ +RADSAN_EXPORT void radsan_realtime_exit(); + +/** Disable all RADSan error reporting. + + This method might be useful to you if RADSan is presenting you with an error + for some code you are confident is realtime safe. For example, you might + know that a mutex is never contested, and that locking it will never block + on your particular system. Be careful! + + A call to `radsan_off()` MUST be paired with a corresponding `radsan_on()` + to reactivate interception after the code in question. If you don't, radsan + will cease to work. + + Example: + + float process (float x) [[clang::nonblocking]] + { + auto const y = 2.0f * x; + + radsan_off(); + i_know_this_method_is_realtime_safe_but_radsan_complains_about_it(); + radsan_on(); + } + +*/ +RADSAN_EXPORT void radsan_off(); + +/** Re-enable all RADSan error reporting. + + The counterpart to `radsan_off`. See the description for `radsan_off` for + details about how to use this method. +*/ +RADSAN_EXPORT void radsan_on(); +} diff --git a/compiler-rt/lib/radsan/radsan_context.cpp b/compiler-rt/lib/radsan/radsan_context.cpp new file mode 100644 index 0000000000000..4ec216e219c7d --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_context.cpp @@ -0,0 +1,80 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace __sanitizer; + +namespace detail { + +static pthread_key_t key; +static pthread_once_t key_once = PTHREAD_ONCE_INIT; +void internalFree(void *ptr) { InternalFree(ptr); } + +} // namespace detail + +namespace radsan { + +Context::Context() = default; + +void Context::realtimePush() { realtime_depth_++; } + +void Context::realtimePop() { realtime_depth_--; } + +void Context::bypassPush() { bypass_depth_++; } + +void Context::bypassPop() { bypass_depth_--; } + +void Context::expectNotRealtime(const char *intercepted_function_name) { + if (inRealtimeContext() && !isBypassed()) { + bypassPush(); + printDiagnostics(intercepted_function_name); + exit(EXIT_FAILURE); + bypassPop(); + } +} + +bool Context::inRealtimeContext() const { return realtime_depth_ > 0; } + +bool Context::isBypassed() const { return bypass_depth_ > 0; } + +void 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); + radsan::printStackTrace(); +} + +Context &getContextForThisThread() { + auto make_tls_key = []() { + CHECK_EQ(pthread_key_create(&detail::key, detail::internalFree), 0); + }; + + pthread_once(&detail::key_once, make_tls_key); + auto *ptr = static_cast(pthread_getspecific(detail::key)); + if (ptr == nullptr) { + ptr = static_cast(InternalAlloc(sizeof(Context))); + new(ptr) Context(); + pthread_setspecific(detail::key, ptr); + } + + return *ptr; +} + +} // namespace radsan diff --git a/compiler-rt/lib/radsan/radsan_context.h b/compiler-rt/lib/radsan/radsan_context.h new file mode 100644 index 0000000000000..66d810ce97735 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_context.h @@ -0,0 +1,36 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#pragma once + +namespace radsan { + +class Context { +public: + Context(); + + void realtimePush(); + void realtimePop(); + + void bypassPush(); + void bypassPop(); + + void expectNotRealtime(const char *interpreted_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 radsan diff --git a/compiler-rt/lib/radsan/radsan_interceptors.cpp b/compiler-rt/lib/radsan/radsan_interceptors.cpp new file mode 100644 index 0000000000000..53f28f0e98269 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_interceptors.cpp @@ -0,0 +1,410 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include "radsan/radsan_interceptors.h" + +#include "sanitizer_common/sanitizer_platform.h" +#include "sanitizer_common/sanitizer_platform_interceptors.h" + +#include "interception/interception.h" +#include "radsan/radsan_context.h" + +#if !SANITIZER_LINUX && !SANITIZER_APPLE +#error Sorry, radsan does not yet support this platform +#endif + +#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; + +namespace radsan { +void expectNotRealtime(const char *intercepted_function_name) { + getContextForThisThread().expectNotRealtime(intercepted_function_name); +} +} // namespace radsan + +/* + Filesystem +*/ + +INTERCEPTOR(int, open, const char *path, int oflag, ...) { + // TODO Establish whether we should intercept here if the flag contains + // O_NONBLOCK + radsan::expectNotRealtime("open"); + va_list args; + va_start(args, oflag); + auto result = REAL(open)(path, oflag, args); + va_end(args); + 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 + radsan::expectNotRealtime("openat"); + va_list args; + va_start(args, oflag); + auto result = REAL(openat)(fd, path, oflag, args); + va_end(args); + return result; +} + +INTERCEPTOR(int, creat, const char *path, mode_t mode) { + // TODO Establish whether we should intercept here if the flag contains + // O_NONBLOCK + radsan::expectNotRealtime("creat"); + auto result = REAL(creat)(path, mode); + return result; +} + +INTERCEPTOR(int, fcntl, int filedes, int cmd, ...) { + radsan::expectNotRealtime("fcntl"); + va_list args; + va_start(args, cmd); + auto result = REAL(fcntl)(filedes, cmd, args); + va_end(args); + return result; +} + +INTERCEPTOR(int, close, int filedes) { + radsan::expectNotRealtime("close"); + return REAL(close)(filedes); +} + +INTERCEPTOR(FILE *, fopen, const char *path, const char *mode) { + radsan::expectNotRealtime("fopen"); + return REAL(fopen)(path, mode); +} + +INTERCEPTOR(size_t, fread, void *ptr, size_t size, size_t nitems, + FILE *stream) { + radsan::expectNotRealtime("fread"); + return REAL(fread)(ptr, size, nitems, stream); +} + +INTERCEPTOR(size_t, fwrite, const void *ptr, size_t size, size_t nitems, + FILE *stream) { + radsan::expectNotRealtime("fwrite"); + return REAL(fwrite)(ptr, size, nitems, stream); +} + +INTERCEPTOR(int, fclose, FILE *stream) { + radsan::expectNotRealtime("fclose"); + return REAL(fclose)(stream); +} + +INTERCEPTOR(int, fputs, const char *s, FILE *stream) { + radsan::expectNotRealtime("fputs"); + return REAL(fputs)(s, stream); +} + +/* + Streams +*/ + +INTERCEPTOR(int, puts, const char *s) { + radsan::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) { + radsan::expectNotRealtime("OSSpinLockLock"); + return REAL(OSSpinLockLock)(lock); +} +#pragma clang diagnostic pop + +INTERCEPTOR(void, os_unfair_lock_lock, os_unfair_lock_t lock) { + radsan::expectNotRealtime("os_unfair_lock_lock"); + return REAL(os_unfair_lock_lock)(lock); +} +#elif SANITIZER_LINUX +INTERCEPTOR(int, pthread_spin_lock, pthread_spinlock_t *spinlock) { + radsan::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) { + radsan::expectNotRealtime("pthread_create"); + return REAL(pthread_create)(thread, attr, start_routine, arg); +} + +INTERCEPTOR(int, pthread_mutex_lock, pthread_mutex_t *mutex) { + radsan::expectNotRealtime("pthread_mutex_lock"); + return REAL(pthread_mutex_lock)(mutex); +} + +INTERCEPTOR(int, pthread_mutex_unlock, pthread_mutex_t *mutex) { + radsan::expectNotRealtime("pthread_mutex_unlock"); + return REAL(pthread_mutex_unlock)(mutex); +} + +INTERCEPTOR(int, pthread_join, pthread_t thread, void **value_ptr) { + radsan::expectNotRealtime("pthread_join"); + return REAL(pthread_join)(thread, value_ptr); +} + +INTERCEPTOR(int, pthread_cond_signal, pthread_cond_t *cond) { + radsan::expectNotRealtime("pthread_cond_signal"); + return REAL(pthread_cond_signal)(cond); +} + +INTERCEPTOR(int, pthread_cond_broadcast, pthread_cond_t *cond) { + radsan::expectNotRealtime("pthread_cond_broadcast"); + return REAL(pthread_cond_broadcast)(cond); +} + +INTERCEPTOR(int, pthread_cond_wait, pthread_cond_t *cond, + pthread_mutex_t *mutex) { + radsan::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) { + radsan::expectNotRealtime("pthread_cond_timedwait"); + return REAL(pthread_cond_timedwait)(cond, mutex, ts); +} + +INTERCEPTOR(int, pthread_rwlock_rdlock, pthread_rwlock_t *lock) { + radsan::expectNotRealtime("pthread_rwlock_rdlock"); + return REAL(pthread_rwlock_rdlock)(lock); +} + +INTERCEPTOR(int, pthread_rwlock_unlock, pthread_rwlock_t *lock) { + radsan::expectNotRealtime("pthread_rwlock_unlock"); + return REAL(pthread_rwlock_unlock)(lock); +} + +INTERCEPTOR(int, pthread_rwlock_wrlock, pthread_rwlock_t *lock) { + radsan::expectNotRealtime("pthread_rwlock_wrlock"); + return REAL(pthread_rwlock_wrlock)(lock); +} + +/* + Sleeping +*/ + +INTERCEPTOR(unsigned int, sleep, unsigned int s) { + radsan::expectNotRealtime("sleep"); + return REAL(sleep)(s); +} + +INTERCEPTOR(int, usleep, useconds_t u) { + radsan::expectNotRealtime("usleep"); + return REAL(usleep)(u); +} + +INTERCEPTOR(int, nanosleep, const struct timespec *rqtp, + struct timespec *rmtp) { + radsan::expectNotRealtime("nanosleep"); + return REAL(nanosleep)(rqtp, rmtp); +} + +/* + Memory +*/ + +INTERCEPTOR(void *, calloc, SIZE_T num, SIZE_T size) { + radsan::expectNotRealtime("calloc"); + return REAL(calloc)(num, size); +} + +INTERCEPTOR(void, free, void *ptr) { + if (ptr != NULL) { + radsan::expectNotRealtime("free"); + } + return REAL(free)(ptr); +} + +INTERCEPTOR(void *, malloc, SIZE_T size) { + radsan::expectNotRealtime("malloc"); + return REAL(malloc)(size); +} + +INTERCEPTOR(void *, realloc, void *ptr, SIZE_T size) { + radsan::expectNotRealtime("realloc"); + return REAL(realloc)(ptr, size); +} + +INTERCEPTOR(void *, reallocf, void *ptr, SIZE_T size) { + radsan::expectNotRealtime("reallocf"); + return REAL(reallocf)(ptr, size); +} + +INTERCEPTOR(void *, valloc, SIZE_T size) { + radsan::expectNotRealtime("valloc"); + return REAL(valloc)(size); +} + +#if SANITIZER_INTERCEPT_ALIGNED_ALLOC +INTERCEPTOR(void *, aligned_alloc, SIZE_T alignment, SIZE_T size) { + radsan::expectNotRealtime("aligned_alloc"); + return REAL(aligned_alloc)(alignment, size); +} +#define RADSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC INTERCEPT_FUNCTION(aligned_alloc) +#else +#define RADSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC +#endif + +INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { + radsan::expectNotRealtime("posix_memalign"); + return REAL(posix_memalign)(memptr, alignment, size); +} + +#if SANITIZER_INTERCEPT_MEMALIGN +INTERCEPTOR(void *, memalign, size_t alignment, size_t size) { + radsan::expectNotRealtime("memalign"); + return REAL(memalign)(alignment, size); +} +#endif + +#if SANITIZER_INTERCEPT_PVALLOC +INTERCEPTOR(void *, pvalloc, size_t size) { + radsan::expectNotRealtime("pvalloc"); + return REAL(pvalloc)(size); +} +#endif + +/* + Sockets +*/ + +INTERCEPTOR(int, socket, int domain, int type, int protocol) { + radsan::expectNotRealtime("socket"); + return REAL(socket)(domain, type, protocol); +} + +INTERCEPTOR(ssize_t, send, int sockfd, const void *buf, size_t len, int flags) { + radsan::expectNotRealtime("send"); + return REAL(send)(sockfd, buf, len, flags); +} + +INTERCEPTOR(ssize_t, sendmsg, int socket, const struct msghdr *message, + int flags) { + radsan::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) { + radsan::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) { + radsan::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) { + radsan::expectNotRealtime("recvfrom"); + return REAL(recvfrom)(socket, buffer, length, flags, address, address_len); +} + +INTERCEPTOR(ssize_t, recvmsg, int socket, struct msghdr *message, int flags) { + radsan::expectNotRealtime("recvmsg"); + return REAL(recvmsg)(socket, message, flags); +} + +INTERCEPTOR(int, shutdown, int socket, int how) { + radsan::expectNotRealtime("shutdown"); + return REAL(shutdown)(socket, how); +} + +/* + Preinit +*/ + +namespace radsan { +void initialiseInterceptors() { + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(reallocf); + INTERCEPT_FUNCTION(valloc); + RADSAN_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); +} +} // namespace radsan diff --git a/compiler-rt/lib/radsan/radsan_interceptors.h b/compiler-rt/lib/radsan/radsan_interceptors.h new file mode 100644 index 0000000000000..7a7471d3ad23b --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_interceptors.h @@ -0,0 +1,13 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#pragma once + +namespace radsan { +void initialiseInterceptors(); +} diff --git a/compiler-rt/lib/radsan/radsan_preinit.cpp b/compiler-rt/lib/radsan/radsan_preinit.cpp new file mode 100644 index 0000000000000..f130dadb92e4b --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_preinit.cpp @@ -0,0 +1,21 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include "sanitizer_common/sanitizer_internal_defs.h" +#include + +#if SANITIZER_CAN_USE_PREINIT_ARRAY + +// The symbol is called __local_radsan_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_radsan_preinit)(void) = radsan_init; + +#endif diff --git a/compiler-rt/lib/radsan/radsan_stack.cpp b/compiler-rt/lib/radsan/radsan_stack.cpp new file mode 100644 index 0000000000000..4b9c98c6cb000 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_stack.cpp @@ -0,0 +1,50 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include +#include + +using namespace __sanitizer; + +// 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 + +namespace { +void setGlobalStackTraceFormat() { + SetCommonFlagsDefaults(); + CommonFlags cf; + cf.CopyFrom(*common_flags()); + cf.stack_trace_format = "DEFAULT"; + cf.external_symbolizer_path = GetEnv("RADSAN_SYMBOLIZER_PATH"); + OverrideCommonFlags(cf); +} +} // namespace + +namespace radsan { +void printStackTrace() { + + auto stack = BufferedStackTrace{}; + + GET_CURRENT_PC_BP; + stack.Unwind(pc, bp, nullptr, common_flags()->fast_unwind_on_fatal); + + setGlobalStackTraceFormat(); + stack.Print(); +} +} // namespace radsan diff --git a/compiler-rt/lib/radsan/radsan_stack.h b/compiler-rt/lib/radsan/radsan_stack.h new file mode 100644 index 0000000000000..2f702079ff969 --- /dev/null +++ b/compiler-rt/lib/radsan/radsan_stack.h @@ -0,0 +1,13 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#pragma once + +namespace radsan { +void printStackTrace(); +} diff --git a/compiler-rt/lib/radsan/tests/CMakeLists.txt b/compiler-rt/lib/radsan/tests/CMakeLists.txt new file mode 100644 index 0000000000000..73cedac2765d6 --- /dev/null +++ b/compiler-rt/lib/radsan/tests/CMakeLists.txt @@ -0,0 +1,103 @@ +include(CompilerRTCompile) + +include_directories(..) + +set(RADSAN_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/radsan + -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common/tests + -DSANITIZER_COMMON_NO_REDEFINE_BUILTINS + -O2) + +set(RADSAN_INST_TEST_SOURCES + radsan_test.cpp + radsan_test_interceptors.cpp + radsan_test_main.cpp) + +set(RADSAN_NOINST_TEST_SOURCES + ../radsan_preinit.cpp + radsan_test_context.cpp + radsan_test_main.cpp) + +set(RADSAN_UNITTEST_HEADERS + radsan_test_utilities.h) + +add_custom_target(RadsanUnitTests) +set_target_properties(RadsanUnitTests PROPERTIES FOLDER "Compiler-RT Tests") + +set(RADSAN_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 RADSAN_UNITTEST_LINK_FLAGS ${WEAK_SYMBOL_LINK_FLAGS}) + list(APPEND RADSAN_UNITTEST_LINK_FLAGS ${DARWIN_osx_LINK_FLAGS}) + list(APPEND RADSAN_UNITTEST_CFLAGS ${DARWIN_osx_CFLAGS}) +else() + list(APPEND RADSAN_UNITTEST_LINK_FLAGS -latomic) +endif() + +set(COMPILER_RT_GOOGLETEST_SOURCES ${COMPILER_RT_GTEST_SOURCE} ${COMPILER_RT_GMOCK_SOURCE}) + +set(RADSAN_TEST_ARCH ${RADSAN_SUPPORTED_ARCH}) +if(APPLE) + darwin_filter_host_archs(RADSAN_SUPPORTED_ARCH RADSAN_TEST_ARCH) +endif() + +foreach(arch ${RADSAN_TEST_ARCH}) + set(RadsanTestObjects) + generate_compiler_rt_tests(RadsanTestObjects + RadsanUnitTests "Radsan-${arch}-Test" ${arch} + COMPILE_DEPS ${RADSAN_UNITTEST_HEADERS} + SOURCES ${RADSAN_INST_TEST_SOURCES} ${COMPILER_RT_GOOGLETEST_SOURCES} + DEPS llvm_gtest radsan + CFLAGS ${RADSAN_UNITTEST_CFLAGS} -fsanitize=realtime + LINK_FLAGS ${RADSAN_UNITTEST_LINK_FLAGS} -fsanitize=realtime) + set_target_properties(RadsanUnitTests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + set(RADSAN_TEST_RUNTIME RTRadsanTest.${arch}) + if(APPLE) + set(RADSAN_TEST_RUNTIME_OBJECTS + $ + $ + $ + $ + $ + $) + else() + set(RADSAN_TEST_RUNTIME_OBJECTS + $ + $ + $ + $ + $ + $ + $) + endif() + add_library(${RADSAN_TEST_RUNTIME} STATIC ${RADSAN_TEST_RUNTIME_OBJECTS}) + set_target_properties(${RADSAN_TEST_RUNTIME} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + FOLDER "Compiler-RT Runtime tests") + + set(RadsanNoInstTestObjects) + generate_compiler_rt_tests(RadsanNoInstTestObjects + RadsanUnitTests "Radsan-${arch}-NoInstTest" ${arch} + COMPILE_DEPS ${RADSAN_UNITTEST_HEADERS} + SOURCES ${RADSAN_NOINST_TEST_SOURCES} + ${COMPILER_RT_GOOGLETEST_SOURCES} + DEPS llvm_gtest + CFLAGS ${RADSAN_UNITTEST_CFLAGS} + LINK_FLAGS ${RADSAN_UNITTEST_LINK_FLAGS} + RUNTIME ${RADSAN_TEST_RUNTIME}) + set_target_properties(RadsanUnitTests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endforeach() diff --git a/compiler-rt/lib/radsan/tests/radsan_test.cpp b/compiler-rt/lib/radsan/tests/radsan_test.cpp new file mode 100644 index 0000000000000..9e95110adaad9 --- /dev/null +++ b/compiler-rt/lib/radsan/tests/radsan_test.cpp @@ -0,0 +1,201 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include "gtest/gtest.h" + +#include "radsan_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 RADSAN_TEST_SHARED_MUTEX (!(SI_MAC) || SI_MAC_DEPLOYMENT_AT_LEAST_10_12) + +using namespace testing; +using namespace radsan_testing; +using namespace std::chrono_literals; + +namespace { +void invokeStdFunction(std::function &&function) { function(); } +} // namespace + +TEST(TestRadsan, vectorPushBackAllocationDiesWhenRealtime) { + auto vec = std::vector{}; + auto func = [&vec]() { vec.push_back(0.4f); }; + expectRealtimeDeath(func); + ASSERT_EQ(0u, vec.size()); + expectNonrealtimeSurvival(func); + ASSERT_EQ(1u, vec.size()); +} + +TEST(TestRadsan, destructionOfObjectOnHeapDiesWhenRealtime) { + auto obj = std::make_unique>(); + auto func = [&obj]() { obj.reset(); }; + expectRealtimeDeath(func); + ASSERT_NE(nullptr, obj.get()); + expectNonrealtimeSurvival(func); + ASSERT_EQ(nullptr, obj.get()); +} + +TEST(TestRadsan, sleepingAThreadDiesWhenRealtime) { + auto func = []() { std::this_thread::sleep_for(1us); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, ifstreamCreationDiesWhenRealtime) { + auto func = []() { auto ifs = std::ifstream("./file.txt"); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); + std::remove("./file.txt"); +} + +TEST(TestRadsan, ofstreamCreationDiesWhenRealtime) { + auto func = []() { auto ofs = std::ofstream("./file.txt"); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); + std::remove("./file.txt"); +} + +TEST(TestRadsan, lockingAMutexDiesWhenRealtime) { + auto mutex = std::mutex{}; + auto func = [&]() { mutex.lock(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, unlockingAMutexDiesWhenRealtime) { + auto mutex = std::mutex{}; + mutex.lock(); + auto func = [&]() { mutex.unlock(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +#if RADSAN_TEST_SHARED_MUTEX + +TEST(TestRadsan, lockingASharedMutexDiesWhenRealtime) { + auto mutex = std::shared_mutex(); + auto func = [&]() { mutex.lock(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, unlockingASharedMutexDiesWhenRealtime) { + auto mutex = std::shared_mutex(); + mutex.lock(); + auto func = [&]() { mutex.unlock(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, sharedLockingASharedMutexDiesWhenRealtime) { + auto mutex = std::shared_mutex(); + auto func = [&]() { mutex.lock_shared(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, sharedUnlockingASharedMutexDiesWhenRealtime) { + auto mutex = std::shared_mutex(); + mutex.lock_shared(); + auto func = [&]() { mutex.unlock_shared(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +#endif // RADSAN_TEST_SHARED_MUTEX + +TEST(TestRadsan, launchingAThreadDiesWhenRealtime) { + auto func = [&]() { + auto t = std::thread([]() {}); + t.join(); + }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, copyingALambdaWithLargeCaptureDiesWhenRealtime) { + auto lots_of_data = std::array{}; + 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(TestRadsan, accessingALargeAtomicVariableDiesWhenRealtime) { + auto small_atomic = std::atomic{0.0f}; + ASSERT_TRUE(small_atomic.is_lock_free()); + realtimeInvoke([&small_atomic]() { auto x = small_atomic.load(); }); + + auto large_atomic = std::atomic>{{}}; + ASSERT_FALSE(large_atomic.is_lock_free()); + auto func = [&]() { auto x = large_atomic.load(); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, firstCoutDiesWhenRealtime) { + auto func = []() { std::cout << "Hello, world!" << std::endl; }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, secondCoutDiesWhenRealtime) { + std::cout << "Hello, world"; + auto func = []() { std::cout << "Hello, again!" << std::endl; }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, printfDiesWhenRealtime) { + auto func = []() { printf("Hello, world!\n"); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, throwingAnExceptionDiesWhenRealtime) { + auto func = [&]() { + try { + throw std::exception(); + } catch (std::exception &) { + } + }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsan, doesNotDieIfTurnedOff) { + auto mutex = std::mutex{}; + auto realtime_unsafe_func = [&]() { + radsan_off(); + mutex.lock(); + mutex.unlock(); + radsan_on(); + }; + realtimeInvoke(realtime_unsafe_func); +} diff --git a/compiler-rt/lib/radsan/tests/radsan_test_context.cpp b/compiler-rt/lib/radsan/tests/radsan_test_context.cpp new file mode 100644 index 0000000000000..5bf48362f819a --- /dev/null +++ b/compiler-rt/lib/radsan/tests/radsan_test_context.cpp @@ -0,0 +1,67 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include "radsan_test_utilities.h" + +#include "radsan_context.h" + +TEST(TestRadsanContext, canCreateContext) { auto context = radsan::Context{}; } + +TEST(TestRadsanContext, expectNotRealtimeDoesNotDieBeforeRealtimePush) { + auto context = radsan::Context{}; + context.expectNotRealtime("do_some_stuff"); +} + +TEST(TestRadsanContext, expectNotRealtimeDoesNotDieAfterPushAndPop) { + auto context = radsan::Context{}; + context.realtimePush(); + context.realtimePop(); + context.expectNotRealtime("do_some_stuff"); +} + +TEST(TestRadsanContext, expectNotRealtimeDiesAfterRealtimePush) { + auto context = radsan::Context{}; + + context.realtimePush(); + EXPECT_DEATH(context.expectNotRealtime("do_some_stuff"), ""); +} + +TEST(TestRadsanContext, + expectNotRealtimeDiesAfterRealtimeAfterMorePushesThanPops) { + auto context = radsan::Context{}; + + context.realtimePush(); + context.realtimePush(); + context.realtimePush(); + context.realtimePop(); + context.realtimePop(); + EXPECT_DEATH(context.expectNotRealtime("do_some_stuff"), ""); +} + +TEST(TestRadsanContext, expectNotRealtimeDoesNotDieAfterBypassPush) { + auto context = radsan::Context{}; + + context.realtimePush(); + context.bypassPush(); + context.expectNotRealtime("do_some_stuff"); +} + +TEST(TestRadsanContext, + expectNotRealtimeDoesNotDieIfBypassDepthIsGreaterThanZero) { + auto context = radsan::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/radsan/tests/radsan_test_interceptors.cpp b/compiler-rt/lib/radsan/tests/radsan_test_interceptors.cpp new file mode 100644 index 0000000000000..183306a18726d --- /dev/null +++ b/compiler-rt/lib/radsan/tests/radsan_test_interceptors.cpp @@ -0,0 +1,452 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#include "gtest/gtest.h" + +#include +#include + +#include "radsan_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 + +using namespace testing; +using namespace radsan_testing; +using namespace std::chrono_literals; + +namespace { +void *fake_thread_entry_point(void *) { return nullptr; } + +/* + The creat function doesn't seem to work on an ubuntu Docker image when the + path is in a shared volume of the host. For now, to keep testing convenient + with a local Docker container, we just put it somewhere that's not in the + shared volume (/tmp). This is volatile and will be cleaned up as soon as the + container is stopped. +*/ +constexpr const char *temporary_file_path() { +#if SANITIZER_LINUX + return "/tmp/radsan_temporary_test_file.txt"; +#elif SANITIZER_APPLE + return "./radsan_temporary_test_file.txt"; +#endif +} +} // namespace + +/* + Allocation and deallocation +*/ + +TEST(TestRadsanInterceptors, mallocDiesWhenRealtime) { + auto func = []() { EXPECT_NE(nullptr, malloc(1)); }; + expectRealtimeDeath(func, "malloc"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, reallocDiesWhenRealtime) { + auto *ptr_1 = malloc(1); + auto func = [ptr_1]() { EXPECT_NE(nullptr, realloc(ptr_1, 8)); }; + expectRealtimeDeath(func, "realloc"); + expectNonrealtimeSurvival(func); +} + +#if SANITIZER_APPLE +TEST(TestRadsanInterceptors, reallocfDiesWhenRealtime) { + auto *ptr_1 = malloc(1); + auto func = [ptr_1]() { EXPECT_NE(nullptr, reallocf(ptr_1, 8)); }; + expectRealtimeDeath(func, "reallocf"); + expectNonrealtimeSurvival(func); +} +#endif + +TEST(TestRadsanInterceptors, vallocDiesWhenRealtime) { + auto func = []() { EXPECT_NE(nullptr, valloc(4)); }; + expectRealtimeDeath(func, "valloc"); + expectNonrealtimeSurvival(func); +} + +#if SANITIZER_INTERCEPT_ALIGNED_ALLOC +TEST(TestRadsanInterceptors, 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(TestRadsanInterceptors, freeDiesWhenRealtime) { + auto *ptr_1 = malloc(1); + auto *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(TestRadsanInterceptors, freeSurvivesWhenRealtimeIfArgumentIsNull) { + realtimeInvoke([]() { free(NULL); }); + expectNonrealtimeSurvival([]() { free(NULL); }); +} + +TEST(TestRadsanInterceptors, posixMemalignDiesWhenRealtime) { + auto func = []() { + void *mem; + posix_memalign(&mem, 4, 4); + }; + expectRealtimeDeath(func, "posix_memalign"); + expectNonrealtimeSurvival(func); +} + +#if SANITIZER_INTERCEPT_MEMALIGN +TEST(TestRadsanInterceptors, memalignDiesWhenRealtime) { + auto func = []() { EXPECT_NE(memalign(2, 2048), nullptr); }; + expectRealtimeDeath(func, "memalign"); + expectNonrealtimeSurvival(func); +} +#endif + +#if SANITIZER_INTERCEPT_PVALLOC +TEST(TestRadsanInterceptors, pvallocDiesWhenRealtime) { + auto func = []() { EXPECT_NE(pvalloc(2048), nullptr); }; + expectRealtimeDeath(func, "pvalloc"); + expectNonrealtimeSurvival(func); +} +#endif + +/* + Sleeping +*/ + +TEST(TestRadsanInterceptors, sleepDiesWhenRealtime) { + auto func = []() { sleep(0u); }; + expectRealtimeDeath(func, "sleep"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, usleepDiesWhenRealtime) { + auto func = []() { usleep(1u); }; + expectRealtimeDeath(func, "usleep"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, nanosleepDiesWhenRealtime) { + auto func = []() { + auto t = timespec{}; + nanosleep(&t, &t); + }; + expectRealtimeDeath(func, "nanosleep"); + expectNonrealtimeSurvival(func); +} + +/* + Filesystem +*/ + +TEST(TestRadsanInterceptors, openDiesWhenRealtime) { + auto func = []() { open(temporary_file_path(), O_RDONLY); }; + expectRealtimeDeath(func, "open"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, openatDiesWhenRealtime) { + auto func = []() { openat(0, temporary_file_path(), O_RDONLY); }; + expectRealtimeDeath(func, "openat"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, creatDiesWhenRealtime) { + auto func = []() { creat(temporary_file_path(), S_IWOTH | S_IROTH); }; + expectRealtimeDeath(func, "creat"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, fcntlDiesWhenRealtime) { + auto func = []() { fcntl(0, F_GETFL); }; + expectRealtimeDeath(func, "fcntl"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, closeDiesWhenRealtime) { + auto func = []() { close(0); }; + expectRealtimeDeath(func, "close"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, fopenDiesWhenRealtime) { + auto func = []() { + auto fd = fopen(temporary_file_path(), "w"); + EXPECT_THAT(fd, Ne(nullptr)); + }; + expectRealtimeDeath(func, "fopen"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, freadDiesWhenRealtime) { + auto fd = fopen(temporary_file_path(), "w"); + auto func = [fd]() { + char c{}; + fread(&c, 1, 1, fd); + }; + expectRealtimeDeath(func, "fread"); + expectNonrealtimeSurvival(func); + if (fd != nullptr) + fclose(fd); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, fwriteDiesWhenRealtime) { + auto fd = fopen(temporary_file_path(), "w"); + ASSERT_NE(nullptr, fd); + auto message = "Hello, world!"; + auto func = [&]() { fwrite(&message, 1, 4, fd); }; + expectRealtimeDeath(func, "fwrite"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, fcloseDiesWhenRealtime) { + auto fd = fopen(temporary_file_path(), "w"); + EXPECT_THAT(fd, Ne(nullptr)); + auto func = [fd]() { fclose(fd); }; + expectRealtimeDeath(func, "fclose"); + expectNonrealtimeSurvival(func); + std::remove(temporary_file_path()); +} + +TEST(TestRadsanInterceptors, putsDiesWhenRealtime) { + auto func = []() { puts("Hello, world!\n"); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, fputsDiesWhenRealtime) { + auto fd = fopen(temporary_file_path(), "w"); + ASSERT_THAT(fd, Ne(nullptr)) << errno; + auto func = [fd]() { fputs("Hello, world!\n", fd); }; + expectRealtimeDeath(func); + expectNonrealtimeSurvival(func); + if (fd != nullptr) + fclose(fd); + std::remove(temporary_file_path()); +} + +/* + Concurrency +*/ + +TEST(TestRadsanInterceptors, pthreadCreateDiesWhenRealtime) { + auto func = []() { + auto thread = pthread_t{}; + auto const attr = pthread_attr_t{}; + struct thread_info *tinfo; + pthread_create(&thread, &attr, &fake_thread_entry_point, tinfo); + }; + expectRealtimeDeath(func, "pthread_create"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadMutexLockDiesWhenRealtime) { + auto func = []() { + auto mutex = pthread_mutex_t{}; + pthread_mutex_lock(&mutex); + }; + + expectRealtimeDeath(func, "pthread_mutex_lock"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadMutexUnlockDiesWhenRealtime) { + auto func = []() { + auto mutex = pthread_mutex_t{}; + pthread_mutex_unlock(&mutex); + }; + + expectRealtimeDeath(func, "pthread_mutex_unlock"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadMutexJoinDiesWhenRealtime) { + auto func = []() { + auto thread = pthread_t{}; + 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(TestRadsanInterceptors, osSpinLockLockDiesWhenRealtime) { + auto func = []() { + auto spin_lock = OSSpinLock{}; + OSSpinLockLock(&spin_lock); + }; + expectRealtimeDeath(func, "OSSpinLockLock"); + expectNonrealtimeSurvival(func); +} +#pragma clang diagnostic pop + +TEST(TestRadsanInterceptors, osUnfairLockLockDiesWhenRealtime) { + auto func = []() { + auto unfair_lock = os_unfair_lock_s{}; + os_unfair_lock_lock(&unfair_lock); + }; + expectRealtimeDeath(func, "os_unfair_lock_lock"); + expectNonrealtimeSurvival(func); +} +#endif + +#if SANITIZER_LINUX +TEST(TestRadsanInterceptors, spinLockLockDiesWhenRealtime) { + auto spinlock = pthread_spinlock_t{}; + pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED); + auto func = [&]() { pthread_spin_lock(&spinlock); }; + expectRealtimeDeath(func, "pthread_spin_lock"); + expectNonrealtimeSurvival(func); +} +#endif + +TEST(TestRadsanInterceptors, pthreadCondSignalDiesWhenRealtime) { + auto func = []() { + auto cond = pthread_cond_t{}; + pthread_cond_signal(&cond); + }; + expectRealtimeDeath(func, "pthread_cond_signal"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadCondBroadcastDiesWhenRealtime) { + auto func = []() { + auto cond = pthread_cond_t{}; + pthread_cond_broadcast(&cond); + }; + expectRealtimeDeath(func, "pthread_cond_broadcast"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadCondWaitDiesWhenRealtime) { + auto cond = pthread_cond_t{}; + auto mutex = pthread_mutex_t{}; + 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. +} + +TEST(TestRadsanInterceptors, pthreadRwlockRdlockDiesWhenRealtime) { + auto func = []() { + auto rwlock = pthread_rwlock_t{}; + pthread_rwlock_rdlock(&rwlock); + }; + expectRealtimeDeath(func, "pthread_rwlock_rdlock"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadRwlockUnlockDiesWhenRealtime) { + auto func = []() { + auto rwlock = pthread_rwlock_t{}; + pthread_rwlock_unlock(&rwlock); + }; + expectRealtimeDeath(func, "pthread_rwlock_unlock"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, pthreadRwlockWrlockDiesWhenRealtime) { + auto func = []() { + auto rwlock = pthread_rwlock_t{}; + pthread_rwlock_wrlock(&rwlock); + }; + expectRealtimeDeath(func, "pthread_rwlock_wrlock"); + expectNonrealtimeSurvival(func); +} + +/* + Sockets +*/ +TEST(TestRadsanInterceptors, openingASocketDiesWhenRealtime) { + auto func = []() { socket(PF_INET, SOCK_STREAM, 0); }; + expectRealtimeDeath(func, "socket"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, sendToASocketDiesWhenRealtime) { + auto func = []() { send(0, nullptr, 0, 0); }; + expectRealtimeDeath(func, "send"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, sendmsgToASocketDiesWhenRealtime) { + auto const msg = msghdr{}; + auto func = [&]() { sendmsg(0, &msg, 0); }; + expectRealtimeDeath(func, "sendmsg"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, sendtoToASocketDiesWhenRealtime) { + auto const addr = sockaddr{}; + auto const len = socklen_t{}; + auto func = [&]() { sendto(0, nullptr, 0, 0, &addr, len); }; + expectRealtimeDeath(func, "sendto"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, recvFromASocketDiesWhenRealtime) { + auto func = []() { recv(0, nullptr, 0, 0); }; + expectRealtimeDeath(func, "recv"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, recvfromOnASocketDiesWhenRealtime) { + auto addr = sockaddr{}; + auto len = socklen_t{}; + auto func = [&]() { recvfrom(0, nullptr, 0, 0, &addr, &len); }; + expectRealtimeDeath(func, "recvfrom"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, recvmsgOnASocketDiesWhenRealtime) { + auto msg = msghdr{}; + auto func = [&]() { recvmsg(0, &msg, 0); }; + expectRealtimeDeath(func, "recvmsg"); + expectNonrealtimeSurvival(func); +} + +TEST(TestRadsanInterceptors, shutdownOnASocketDiesWhenRealtime) { + auto func = [&]() { shutdown(0, 0); }; + expectRealtimeDeath(func, "shutdown"); + expectNonrealtimeSurvival(func); +} diff --git a/compiler-rt/lib/radsan/tests/radsan_test_main.cpp b/compiler-rt/lib/radsan/tests/radsan_test_main.cpp new file mode 100644 index 0000000000000..f75dc2be2f0a1 --- /dev/null +++ b/compiler-rt/lib/radsan/tests/radsan_test_main.cpp @@ -0,0 +1,15 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#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/radsan/tests/radsan_test_utilities.h b/compiler-rt/lib/radsan/tests/radsan_test_utilities.h new file mode 100644 index 0000000000000..d2e48ecddf741 --- /dev/null +++ b/compiler-rt/lib/radsan/tests/radsan_test_utilities.h @@ -0,0 +1,47 @@ +/** + This file is part of the RealtimeSanitizer (RADSan) project. + https://github.com/realtime-sanitizer/radsan + + Copyright 2023 David Trevelyan & Alistair Barker + Subject to GNU General Public License (GPL) v3.0 +*/ + +#pragma once + +#include "gmock/gmock.h" +#include "radsan.h" +#include + +namespace radsan_testing { + +template +void realtimeInvoke(Function &&func) +{ + radsan_realtime_enter(); + std::forward(func)(); + radsan_realtime_exit(); +} + +template +void expectRealtimeDeath(Function &&func, + const char *intercepted_method_name = nullptr) { + + using namespace testing; + + auto expected_error_substr = [&]() -> 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), expected_error_substr()); +} + +template void expectNonrealtimeSurvival(Function &&func) { + std::forward(func)(); +} + +} // namespace radsan_testing diff --git a/compiler-rt/test/radsan/CMakeLists.txt b/compiler-rt/test/radsan/CMakeLists.txt new file mode 100644 index 0000000000000..51a3ff72859b6 --- /dev/null +++ b/compiler-rt/test/radsan/CMakeLists.txt @@ -0,0 +1,47 @@ +set(RADSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(RADSAN_TESTSUITES) +set(RADSAN_FDR_TESTSUITES) + +set(RADSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +set(RADSAN_FDR_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +set(RADSAN_TEST_ARCH ${RADSAN_SUPPORTED_ARCH}) +if(APPLE) + darwin_filter_host_archs(RADSAN_SUPPORTED_ARCH RADSAN_TEST_ARCH) +endif() + +if (COMPILER_RT_HAS_RADSAN) + foreach(arch ${RADSAN_TEST_ARCH}) + set(RADSAN_TEST_TARGET_ARCH ${arch}) + string(TOLOWER "-${arch}-${OS_NAME}" RADSAN_TEST_CONFIG_SUFFIX) + get_test_cc_for_arch(${arch} RADSAN_TEST_TARGET_CC RADSAN_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 RADSAN_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_RADSAN_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 RADSAN_TEST_DEPS RadsanUnitTests) + list(APPEND RADSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit) + if(COMPILER_RT_RADSAN_HAS_STATIC_RUNTIME) + list(APPEND RADSAN_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit/dynamic) + endif() +endif() + +add_lit_testsuite(check-radsan "Running the Radsan tests" + ${RADSAN_TESTSUITES} + DEPENDS ${RADSAN_TEST_DEPS}) +set_target_properties(check-radsan PROPERTIES FOLDER "Compiler-RT Misc") diff --git a/compiler-rt/test/radsan/Unit/lit.site.cfg.py.in b/compiler-rt/test/radsan/Unit/lit.site.cfg.py.in new file mode 100644 index 0000000000000..432e0ed5e1907 --- /dev/null +++ b/compiler-rt/test/radsan/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/radsan/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 radsan/lit.cfg.py. + if 'RADSAN_OPTIONS' in config.environment: + config.environment['RADSAN_OPTIONS'] += ':ignore_noninstrumented_modules=0' + else: + config.environment['RADSAN_OPTIONS'] = 'ignore_noninstrumented_modules=0' + config.environment['RADSAN_OPTIONS'] += ':ignore_interceptors_accesses=0' diff --git a/compiler-rt/test/radsan/lit.cfg.py b/compiler-rt/test/radsan/lit.cfg.py new file mode 100644 index 0000000000000..773b624900fa6 --- /dev/null +++ b/compiler-rt/test/radsan/lit.cfg.py @@ -0,0 +1,69 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = "RADSAN" + config.name_suffix + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Setup default compiler flags use with -fradsan-instrument option. +clang_radsan_cflags = ["-fradsan-instrument", config.target_cflags] + +# If libc++ was used to build radsan libraries, libc++ is needed. Fix applied +# to Linux only since -rpath may not be portable. This can be extended to +# other platforms. +if config.libcxx_used == "1" and config.host_os == "Linux": + clang_radsan_cflags = clang_radsan_cflags + ( + ["-L%s -lc++ -Wl,-rpath=%s" % (config.llvm_shlib_dir, config.llvm_shlib_dir)] + ) + +clang_radsan_cxxflags = config.cxx_mode_flags + clang_radsan_cflags + + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + + +# Assume that llvm-radsan is in the config.llvm_tools_dir. +llvm_radsan = os.path.join(config.llvm_tools_dir, "llvm-radsan") + +# 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_radsan ", build_invocation(clang_radsan_cflags))) +config.substitutions.append(("%clangxx_radsan", build_invocation(clang_radsan_cxxflags))) +config.substitutions.append(("%llvm_radsan", llvm_radsan)) +config.substitutions.append( + ( + "%radsanlib", + ( + "-lm -lpthread %s -lrt -L%s " + "-Wl,-whole-archive -lclang_rt.radsan%s -Wl,-no-whole-archive" + ) + % (libdl_flag, config.compiler_rt_libdir, config.target_suffix), + ) +) + +# 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/radsan/lit.site.cfg.py.in b/compiler-rt/test/radsan/lit.site.cfg.py.in new file mode 100644 index 0000000000000..7d509c9f2eb91 --- /dev/null +++ b/compiler-rt/test/radsan/lit.site.cfg.py.in @@ -0,0 +1,17 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@RADSAN_TEST_CONFIG_SUFFIX@" +config.radsan_lit_source_dir = "@RADSAN_LIT_SOURCE_DIR@" +config.target_cflags = "@RADSAN_TEST_TARGET_CFLAGS@" +config.target_arch = "@RADSAN_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") diff --git a/compiler-rt/test/sanitizer_common/CMakeLists.txt b/compiler-rt/test/sanitizer_common/CMakeLists.txt index f2df8cec3549b..bdf839b348e25 100644 --- a/compiler-rt/test/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/test/sanitizer_common/CMakeLists.txt @@ -7,7 +7,7 @@ set(SANITIZER_COMMON_TESTSUITES) # FIXME(dliew): We should switch to COMPILER_RT_SANITIZERS_TO_BUILD instead of # the hard coded `SUPPORTED_TOOLS_INIT` list once we know that the other # sanitizers work. -set(SUPPORTED_TOOLS_INIT asan lsan hwasan msan tsan ubsan) +set(SUPPORTED_TOOLS_INIT asan lsan hwasan msan radsan tsan ubsan) set(SUPPORTED_TOOLS) foreach(SANITIZER_TOOL ${SUPPORTED_TOOLS_INIT}) string(TOUPPER ${SANITIZER_TOOL} SANITIZER_TOOL_UPPER) diff --git a/compiler-rt/test/sanitizer_common/lit.common.cfg.py b/compiler-rt/test/sanitizer_common/lit.common.cfg.py index 04af4816eb6e7..80c7372372f44 100644 --- a/compiler-rt/test/sanitizer_common/lit.common.cfg.py +++ b/compiler-rt/test/sanitizer_common/lit.common.cfg.py @@ -18,6 +18,9 @@ tool_options = "HWASAN_OPTIONS" if not config.has_lld: config.unsupported = True +elif config.tool_name == "radsan": + tool_cflags = ["-fsanitize=realtime"] + tool_options = "RADSAN_OPTIONS" elif config.tool_name == "tsan": tool_cflags = ["-fsanitize=thread"] tool_options = "TSAN_OPTIONS"