Как использовать sanitizers: ASan, UBSan, TSan, LSan?
Sanitizers — инструментируемые рантайм-детекторы ошибок. Добавляются флагом -fsanitize= при компиляции GCC/Clang. ASan ловит ошибки памяти, UBSan — неопределённое поведение, TSan — гонки данных, LSan — утечки.
Что такое sanitizers
Sanitizers — это runtime-инструменты, встроенные в компиляторы GCC и Clang. При компиляции с соответствующим флагом компилятор вставляет дополнительный код, который отслеживает обращения к памяти, типы операций и порядок доступа из нескольких потоков. При обнаружении ошибки программа завершается с подробным отчётом и стектрейсом.
AddressSanitizer (ASan)
Обнаруживает: heap-buffer-overflow, stack-buffer-overflow, global-buffer-overflow, use-after-free, use-after-return, use-after-scope, double-free, invalid-free.
# Компиляция
g++ -fsanitize=address -fno-omit-frame-pointer -g -O1 main.cpp -o main
# Переменные окружения для управления поведением
ASAN_OPTIONS=abort_on_error=1:detect_leaks=1:verbosity=1 ./main
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
return v[5]; // heap-buffer-overflow — ASan поймает
}
ASan добавляет ~2x overhead по памяти (shadow memory 1/8 от адресного пространства) и ~2x замедление.
UndefinedBehaviorSanitizer (UBSan)
Обнаруживает: signed integer overflow, null pointer dereference, misaligned pointer access, invalid bool/enum value, division by zero, shift by negative/oversized value и десятки других UB.
# Базовый набор проверок
g++ -fsanitize=undefined -g main.cpp -o main
# Расширенный набор
g++ -fsanitize=undefined,integer,nullability -g main.cpp -o main
# Вывод стектрейса вместо просто сообщения
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./main
int main() {
int x = INT_MAX;
int y = x + 1; // signed overflow: UBSan сообщит
int arr[4];
return arr[10]; // out-of-bounds: тоже UBSan
}
ThreadSanitizer (TSan)
Обнаруживает гонки данных (data races) — одновременный доступ из разных потоков без синхронизации. Несовместим с ASan (нельзя использовать одновременно).
g++ -fsanitize=thread -g -O1 main.cpp -o main -lpthread
TSAN_OPTIONS=halt_on_error=1:history_size=7 ./main
#include <thread>
int counter = 0; // не атомарная переменная
void inc() { for (int i = 0; i < 100000; ++i) ++counter; }
int main() {
std::thread t1(inc), t2(inc);
t1.join(); t2.join();
// TSan: WARNING: ThreadSanitizer: data race on counter
}
TSan добавляет ~5-15x замедление и требует ~5-10x больше памяти. Запускайте только целенаправленные тесты, а не всю suite.
LeakSanitizer (LSan)
На Linux LSan включён по умолчанию вместе с ASan. Можно запустить отдельно без ASan-overhead.
# Только LSan (Linux)
g++ -fsanitize=leak -g main.cpp -o main
LSAN_OPTIONS=verbosity=1:log_threads=1 ./main
# Подавление ложных срабатываний — файл suppressions.txt
# leak:libssl.so
LSAN_OPTIONS=suppressions=suppressions.txt ./main
int main() {
int* p = new int[100]; // никогда не delete[]
// LSan: 400 bytes in 1 block are definitely lost
}
Комбинирование sanitizers
# ASan + UBSan (хорошая комбинация для CI)
g++ -fsanitize=address,undefined -fno-omit-frame-pointer -g -O1 main.cpp -o main
# TSan несовместим с ASan/LSan — отдельный билд
g++ -fsanitize=thread -g -O1 main.cpp -o main_tsan
Интеграция в CMake
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
if(ENABLE_ASAN)
add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
add_link_options(-fsanitize=address,undefined)
endif()
Подводные камни
- TSan и ASan несовместимы в одном бинарнике — держите два отдельных билда или два CI-джоба.
- На macOS LSan не работает в составе ASan (ограничение платформы); утечки проверяйте через Instruments Leaks или Valgrind.
- UBSan в Release-сборке (
-O2) может не поймать некоторые UB, которые компилятор уже «оптимизировал» — запускайте с-O1. - Статические библиотеки сторонних зависимостей, скомпилированные без sanitizer-флагов, могут давать ложные срабатывания — подавляйте через suppressions.
- ASan не совместим с
LD_PRELOAD-аллокаторами (jemalloc, tcmalloc) без пересборки с ASan-вариантом. ASAN_OPTIONS=detect_odr_violation=0иногда нужен при линковке нескольких shared-библиотек с дублирующимися символами.- Sanitizers не гарантируют полного покрытия — ошибка обнаруживается только при выполнении конкретного пути кода. Без хорошего покрытия тестов sanitizer бесполезен.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.