# RealtimeSanitizer

```{contents}
:local: true
```

## Introduction

RealtimeSanitizer (a.k.a. RTSan) is a real-time safety testing tool for C and C++
projects. RTSan can be used to detect real-time violations, i.e. calls to methods
that are not safe for use in functions with deterministic run time requirements.
RTSan considers any function marked with the `[[clang::nonblocking]]` attribute
to be a real-time function. At run-time, if RTSan detects a call to `malloc`,
`free`, `pthread_mutex_lock`, or anything else known to have a
non-deterministic execution time in a function marked `[[clang::nonblocking]]`
it raises an error.

RTSan performs its analysis at run-time but shares the `[[clang::nonblocking]]`
attribute with the {doc}`FunctionEffectAnalysis` system, which operates at
compile-time to detect potential real-time safety violations. For comprehensive
detection of real-time safety issues, it is recommended to use both systems together.

The runtime slowdown introduced by RealtimeSanitizer is negligible.

## How to build

Build LLVM/Clang with [CMake](https://llvm.org/docs/CMake.html) and enable the
`compiler-rt` runtime. An example CMake configuration that will allow for the
use/testing of RealtimeSanitizer:

```console
$ cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" <path to source>/llvm
```

## Usage

There are two requirements:

1. The code must be compiled with the `-fsanitize=realtime` flag.
2. Functions that are subject to real-time constraints must be marked
   with the `[[clang::nonblocking]]` attribute.

Typically, these attributes should be added onto the functions that are entry
points for threads with real-time priority. These threads are subject to a fixed
callback time, such as audio callback threads or rendering loops in video game
code.

```console
% cat example_realtime_violation.cpp
#include <vector>

void violation() [[clang::nonblocking]]{
  std::vector<float> v;
  v.resize(100);
}

int main() {
  violation();
  return 0;
}
# Compile and link
% clang++ -fsanitize=realtime example_realtime_violation.cpp
```

If a real-time safety violation is detected in a `[[clang::nonblocking]]`
context, or any function invoked by that function, the program will exit with a
non-zero exit code.

```console
% clang++ -fsanitize=realtime example_realtime_violation.cpp
% ./a.out
==76290==ERROR: RealtimeSanitizer: unsafe-library-call
Intercepted call to real-time unsafe function `malloc` in real-time context!
    #0 0x000102a7b884 in malloc rtsan_interceptors.cpp:426
    #1 0x00019c326bd0 in operator new(unsigned long)+0x1c (libc++abi.dylib:arm64+0x16bd0)
    #2 0xa30d0001024f79a8  (<unknown module>)
    #3 0x0001024f794c in std::__1::__libcpp_allocate[abi:ne200000](unsigned long, unsigned long)+0x44
    #4 0x0001024f78c4 in std::__1::allocator<float>::allocate[abi:ne200000](unsigned long)+0x44
    ... snip ...
    #9 0x0001024f6868 in std::__1::vector<float, std::__1::allocator<float>>::resize(unsigned long)+0x48
    #10 0x0001024f67b4 in violation()+0x24
    #11 0x0001024f68f0 in main+0x18 (a.out:arm64+0x1000028f0)
    #12 0x00019bfe3150  (<unknown module>)
    #13 0xed5efffffffffffc  (<unknown module>)
```

### Blocking functions

Calls to system library functions such as `malloc` are automatically caught by
RealtimeSanitizer. Real-time programmers may also write their own blocking
(real-time unsafe) functions that they wish RealtimeSanitizer to be aware of.
RealtimeSanitizer will raise an error at run time if any function attributed
with `[[clang::blocking]]` is called in a `[[clang::nonblocking]]` context.

```console
$ cat example_blocking_violation.cpp
#include <atomic>
#include <thread>

std::atomic<bool> has_permission{false};

int wait_for_permission() [[clang::blocking]] {
  while (has_permission.load() == false)
    std::this_thread::yield();
  return 0;
}

int real_time_function() [[clang::nonblocking]] {
  return wait_for_permission();
}

int main() {
  return real_time_function();
}

$ clang++ -fsanitize=realtime example_blocking_violation.cpp && ./a.out
==76131==ERROR: RealtimeSanitizer: blocking-call
Call to blocking function `wait_for_permission()` in real-time context!
    #0 0x0001000c3db0 in wait_for_permission()+0x10 (a.out:arm64+0x100003db0)
    #1 0x0001000c3e3c in real_time_function()+0x10 (a.out:arm64+0x100003e3c)
    #2 0x0001000c3e68 in main+0x10 (a.out:arm64+0x100003e68)
    #3 0x00019bfe3150  (<unknown module>)
    #4 0x5a27fffffffffffc  (<unknown module>)
```

### Run-time flags

RealtimeSanitizer supports a number of run-time flags, which can be specified in the `RTSAN_OPTIONS` environment variable:

```console
% RTSAN_OPTIONS=option_1=true:path_option_2="/some/file.txt" ./a.out
...
```

Or at compile-time by providing the symbol `__rtsan_default_options`:

```c
__attribute__((__visibility__("default")))
extern "C" const char *__rtsan_default_options() {
  return "symbolize=false:abort_on_error=0:log_to_syslog=0";
}
```

You can see all sanitizer options (some of which are unsupported) by using the `help` flag:

```console
% RTSAN_OPTIONS=help=true ./a.out
```

A **partial** list of flags RealtimeSanitizer respects:

```{list-table} Run-time Flags
:widths: 20 10 10 70
:header-rows: 1

* - Flag name
  - Default value
  - Type
  - Short description
* - `halt_on_error`
  - `true`
  - boolean
  - Exit after first reported error.
* - `suppress_equal_stacks`
  - `true`
  - boolean
  - If true, suppress duplicate reports (i.e. only print each unique error once). Only particularly useful when `halt_on_error=false`.
* - `print_stats_on_exit`
  - `false`
  - boolean
  - Print stats on exit. Includes total and unique errors.
* - `color`
  - `"auto"`
  - string
  - Colorize reports: (always|never|auto).
* - `fast_unwind_on_fatal`
  - `false`
  - boolean
  - If available, use the fast frame-pointer-based unwinder on detected errors. If true, ensure the code under test has been compiled with frame pointers with `-fno-omit-frame-pointers` or similar.
* - `abort_on_error`
  - OS dependent
  - boolean
  - If true, the tool calls `abort()` instead of `_exit()` after printing the error report. On some OSes (macOS, for example) this is beneficial because a better stack trace is emitted on crash.
* - `symbolize`
  - `true`
  - boolean
  - If set, use the symbolizer to turn virtual addresses to file/line locations. If false, can greatly speed up the error reporting.
* - `suppressions`
  - `""`
  - path
  - If set to a valid suppressions file, will suppress issue reporting. See details in [Disabling and Suppressing](#disabling-and-suppressing).
* - `verify_interceptors`
  - `true`
  - boolean
  - If true, verifies interceptors are working at initialization. The program will abort with error `==ERROR: Interceptors are not working. This may be because RealtimeSanitizer is loaded too late (e.g. via dlopen)` if an issue is detected.
```

Some issues with flags can be debugged using the `verbosity=$NUM` flag:

```console
% RTSAN_OPTIONS=verbosity=1:misspelled_flag=true ./a.out
WARNING: found 1 unrecognized flag(s):
misspelled_flag
...
```

### Additional customization

In addition to `__rtsan_default_options` outlined above, you can provide definitions of other functions that affect how RTSan operates.

To be notified on every error reported by RTsan, provide a definition of `__sanitizer_report_error_summary`.

```c
extern "C" void __sanitizer_report_error_summary(const char *error_summary) {
   fprintf(stderr, "%s %s\n", "In custom handler! ", error_summary);
   /* do other custom things */
}
```

The error summary will be of the form:

```console
SUMMARY: RealtimeSanitizer: unsafe-library-call main.cpp:8 in process(std::__1::vector<int, std::__1::allocator<int>>&)
```

To register a callback which will be invoked before a RTSan kills the process:

```c
extern "C" void __sanitizer_set_death_callback(void (*callback)(void));

void custom_on_die_callback() {
  fprintf(stderr, "In custom handler!")
  /* do other custom things */
}

int main()
{
  __sanitizer_set_death_callback(custom_on_die_callback);
  ...
}
```

(disabling-and-suppressing)=

### Disabling and suppressing

There are multiple ways to disable error reporting when using RealtimeSanitizer.

In general, `ScopedDisabler` should be preferred, as it is the most performant.

```{list-table} Suppression methods
:widths: 30 15 15 10 70
:header-rows: 1

* - Method
  - Specified at?
  - Scope
  - Run-time cost
  - Description
* - `ScopedDisabler`
  - Compile-time
  - Stack
  - Very low
  - Violations are ignored for the lifetime of the `ScopedDisabler` object.
* - `function-name-matches` suppression
  - Run-time
  - Single function
  - Medium
  - Suppresses intercepted and `[[clang::blocking]]` function calls by name.
* - `call-stack-contains` suppression
  - Run-time
  - Stack
  - High
  - Suppresses any stack trace containing the specified pattern.

```

#### `ScopedDisabler`

At compile time, RealtimeSanitizer may be disabled using `__rtsan::ScopedDisabler`. RTSan ignores any errors originating within the `ScopedDisabler` instance variable scope.

```c++
#include <sanitizer/rtsan_interface.h>

void process(const std::vector<float>& buffer) [[clang::nonblocking]] {
    {
        __rtsan::ScopedDisabler d;
        ...
    }
}
```

If RealtimeSanitizer is not enabled at compile time (i.e., the code is not compiled with the `-fsanitize=realtime` flag), the `ScopedDisabler` is compiled as a no-op.

In C, you can use the `__rtsan_disable()` and `rtsan_enable()` functions to manually disable and re-enable RealtimeSanitizer checks.

```c++
#include <sanitizer/rtsan_interface.h>

int process(const float* buffer) [[clang::nonblocking]]
{
    {
        __rtsan_disable();

        ...

        __rtsan_enable();
    }
}
```

Each call to `__rtsan_disable()` must be paired with a subsequent call to `__rtsan_enable()` to restore normal sanitizer functionality. If a corresponding `rtsan_enable()` call is not made, the behavior is undefined.

#### Suppression file

At run-time, suppressions may be specified using a suppressions file passed in `RTSAN_OPTIONS`. Run-time suppression may be useful if the source cannot be changed.

```console
> cat suppressions.supp
call-stack-contains:MallocViolation
call-stack-contains:std::*vector
function-name-matches:free
function-name-matches:CustomMarkedBlocking*
> RTSAN_OPTIONS="suppressions=suppressions.supp" ./a.out
...
```

Suppressions specified in this file are one of two flavors.

`function-name-matches` suppresses reporting of any intercepted library call, or function marked `[[clang::blocking]]` by name. If, for instance, you know that `malloc` is real-time safe on your system, you can disable the check for it via `function-name-matches:malloc`.

`call-stack-contains` suppresses reporting of errors in any stack that contains a string matching the pattern specified. For example, suppressing error reporting of any non-real-time-safe behavior in `std::vector` may be specified `call-stack-contains:std::*vector`. You must include symbols in your build for this method to be effective, unsymbolicated stack traces cannot be matched. `call-stack-contains` has the highest run-time cost of any method of suppression.

Patterns may be exact matches or are "regex-light" patterns, containing special characters such as `^$*`.

The number of potential errors suppressed via this method may be seen on exit when using the `print_stats_on_exit` flag.

### Compile-time sanitizer detection

Clang provides the pre-processor macro `__has_feature` which may be used to detect if RealtimeSanitizer is enabled at compile-time.

```c++
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
...
#endif
```
