Bug memori pada C++ (out-of-bounds, use-after-free, signed overflow) sering tidak terdeteksi pada saat compile. Program berjalan “benar” pada uji sample tetapi crash atau memberi jawaban salah pada input tertentu. Sanitizer menyelesaikan masalah ini dengan menginstrumentasi kode pada saat compile untuk mendeteksi pelanggaran memori pada saat runtime. Pada Chapter ini kita akan membahas empat sanitizer utama — AddressSanitizer, UndefinedBehaviorSanitizer, ThreadSanitizer, dan MemorySanitizer — beserta flag compile yang benar dan contoh output realistis. Tujuan akhirnya: setiap kode DSA yang akan dikirim ke judge produksi sebaiknya diuji minimal dengan ASan + UBSan sebelum submit.
Empat Sanitizer Utama #
| Sanitizer | Singkatan | Flag Compile | Mendeteksi |
|---|---|---|---|
| AddressSanitizer | ASan | -fsanitize=address | Out-of-bounds, use-after-free, double-free, memory leak |
| UndefinedBehaviorSanitizer | UBSan | -fsanitize=undefined | Signed overflow, null deref, shift berlebih, misaligned access |
| ThreadSanitizer | TSan | -fsanitize=thread | Data race pada multi-threading |
| MemorySanitizer | MSan | -fsanitize=memory | Read uninitialized memory |
Mekanisme: Instrumentasi Compile-Time #
Saat flag -fsanitize=address dipakai, compiler menyisipkan kode tambahan pada setiap akses memori: pointer diperiksa terhadap shadow memory — peta bitmap yang melacak byte mana yang valid. Bila akses keluar dari region yang valid, program dihentikan dengan pesan error mendetail (lokasi file:baris, alamat, jenis pelanggaran).
UBSan bekerja dengan cara berbeda: compiler menyisipkan cek sebelum operasi yang berpotensi UB (penjumlahan signed, shift, dereference). Bila UB terdeteksi, UBSan mencatat pesan ke stderr tetapi program dapat melanjutkan (default) atau dihentikan (dengan halt_on_error=1).
Overhead runtime sanitizer cukup besar: ASan ~2x slowdown + ~3x memori, TSan ~5-15x slowdown, UBSan ~5-10%. Karena itu sanitizer dipakai untuk build debug/test, bukan untuk build produksi.
Kode: Versi Aman (Tanpa Sanitizer Output) #
#include <iostream>
#include <vector>
#include <climits>
// 1) Out-of-bounds read — versi aman: cek bounds secara manual.
int safe_at(const std::vector<int>& v, int i) {
if (i < 0 || i >= (int)v.size()) return -1;
return v[i];
}
// 2) Signed integer overflow — versi aman: pakai long long.
long long safe_sum(int a, int b) {
return (long long)a + (long long)b;
}
// 3) Use-after-free — versi aman: salin nilai sebelum delete.
void demo_uaf() {
int* p = new int(42);
int value = *p; // salin nilai
delete p;
// Setelah delete, *p adalah UB. Di sini kita hanya pakai value.
std::cout << "[UAF demo] value sebelum delete: " << value << '\n';
}
// 4) Uninitialized read — versi aman: selalu inisialisasi.
int demo_uninit_read() {
int x = 0; // aman: diinisialisasi
return x + 1;
}
// 5) Null pointer dereference — versi aman: cek nullptr.
int demo_null_deref(int* p) {
if (p == nullptr) return -1;
return *p;
}
int main() {
std::vector<int> v = {10, 20, 30};
std::cout << "safe_at(v, 0) = " << safe_at(v, 0) << '\n';
std::cout << "safe_at(v, 5) = " << safe_at(v, 5) << " (out-of-bounds -> -1)\n";
std::cout << "safe_sum(INT_MAX, 1) = " << safe_sum(INT_MAX, 1) << '\n';
demo_uaf();
std::cout << "demo_uninit_read() = " << demo_uninit_read() << '\n';
int x = 7;
std::cout << "demo_null_deref(&x) = " << demo_null_deref(&x) << '\n';
std::cout << "demo_null_deref(nullptr) = " << demo_null_deref(nullptr) << '\n';
std::cout << "\n=== Compile flags yang direkomendasikan ===\n";
std::cout << "ASAN : g++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer\n";
std::cout << "UBSAN : g++ -std=c++17 -O1 -g -fsanitize=undefined\n";
std::cout << "TSAN : g++ -std=c++17 -O1 -g -fsanitize=thread\n";
std::cout << "MSAN : g++ -std=c++17 -O1 -g -fsanitize=memory\n";
return 0;
}Output yang Dihasilkan (tanpa sanitizer):
safe_at(v, 0) = 10
safe_at(v, 5) = -1 (out-of-bounds -> -1)
safe_sum(INT_MAX, 1) = 2147483648
[UAF demo] value sebelum delete: 42
demo_uninit_read() = 1
demo_null_deref(&x) = 7
demo_null_deref(nullptr) = -1
=== Compile flags yang direkomendasikan ===
ASAN : g++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer
UBSAN : g++ -std=c++17 -O1 -g -fsanitize=undefined
TSAN : g++ -std=c++17 -O1 -g -fsanitize=thread
MSAN : g++ -std=c++17 -O1 -g -fsanitize=memoryBedah Kode Baris-per-Baris #
if (i < 0 || i >= (int)v.size()) return -1;: validasi indeks sebelum akses. Tanpa cek,v[5]pada vector berukuran 3 adalah out-of-bounds read — ASan akan melaporkanheap-buffer-overflow.(long long)a + (long long)b: cast kelong longsebelum penjumlahan untuk menghindari signed overflow diint. UntukINT_MAX + 1, hasilintoverflow menjadi-2147483648(UB). UBSan akan melaporkansigned integer overflow.int value = *p; delete p;: salin nilai sebelumdelete. Setelahdelete, akses*padalah use-after-free — ASan melaporkanheap-use-after-free. Dengan menyalin dulu, kita tidak pernah mengakses memori yang sudah di-free.int x = 0;: inisialisasi eksplisit. Tanpa inisialisasi,int x;berisi nilai tak terduga (MSan melaporkanuse-of-uninitialized-value).if (p == nullptr) return -1;: cek nullptr sebelum dereference. Tanpa cek,*nullptradalah segfault atau UB — UBSan melaporkannull pointer dereference.
Contoh Output ASan (Heap Buffer Overflow) #
Kode berikut mengakses v[5] pada vector {10, 20, 30} (size 3):
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {10, 20, 30};
std::cout << "v[5] = " << v[5] << '\n';
return 0;
}Kompilasi dan jalankan dengan ASan:
g++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer -o demo demo.cpp
./demoOutput yang Dihasilkan (ASan aktif, dipotong untuk keterbacaan):
=================================================================
==23245==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000024
READ of size 4 at 0x502000000024 thread T0
#0 0x... in main /home/z/wave10a/sanitizers_bug_demo.cpp:11
#1 0x... in __libc_start_main ...
0x502000000024 is located 8 bytes after 12-byte region [0x502000000010,0x50200000001c)
allocated by thread T0 here:
#0 0x... in operator new(unsigned long)
#1 0x... in std::vector<int>::vector(...)
#6 0x... in main /home/z/wave10a/sanitizers_bug_demo.cpp:8
SUMMARY: AddressSanitizer: heap-buffer-overflow
/home/z/wave10a/sanitizers_bug_demo.cpp:11 in mainASan melaporkan lima hal kunci:
- Jenis pelanggaran:
heap-buffer-overflow. - Alamat memori yang diakses:
0x502000000024. - Operasi:
READ of size 4(akses membaca 4 byte, sesuaisizeof(int)). - Lokasi kode: file
sanitizers_bug_demo.cppbaris 11. - Lokasi alokasi: baris 8 tempat vector dialokasikan. Pesan “8 bytes after 12-byte region” menjelaskan bahwa akses keluar 8 byte setelah akhir region 12-byte (3 elemen × 4 byte).
Contoh Output UBSan (Signed Integer Overflow) #
#include <iostream>
#include <climits>
int main() {
int c = INT_MAX + 1; // signed overflow — UB
std::cout << "INT_MAX + 1 = " << c << '\n';
return 0;
}g++ -std=c++17 -O1 -g -fsanitize=undefined -o demo demo.cpp
./demoOutput yang Dihasilkan (UBSan aktif):
sanitizers_ubsan_demo.cpp:5:13: runtime error: signed integer overflow:
2147483647 + 1 cannot be represented in type 'int'
INT_MAX + 1 = -2147483648UBSan melaporkan:
- File:baris:
sanitizers_ubsan_demo.cpp:5. - Kolom: 13 — lokasi ekspresi yang overflow.
- Pesan:
signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'. - Program tetap berjalan dan mencetak hasil (UBSan default: log-only). Untuk menghentikan program saat UB, tambahkan
UBSAN_OPTIONS=halt_on_error=1.
Kombinasi ASan + UBSan untuk DSA #
Kombinasi yang paling berguna untuk DSA single-threaded:
g++ -std=c++17 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer \
-o solusi solusi.cpp
./solusi < input.txtTambahkan -fsanitize=leak (bagian dari ASan) untuk mendeteksi memory leak. Flag -fno-omit-frame-pointer menjaga stack trace tetap akurat.
Ringkasan #
- Empat sanitizer utama: ASan (memori), UBSan (UB), TSan (thread), MSan (uninitialized).
- ASan dan UBSan kompatibel — kombinasi ideal untuk DSA:
-fsanitize=address,undefined. - ASan mendeteksi out-of-bounds, use-after-free, double-free; UBSan mendeteksi signed overflow, null deref, misaligned access.
- Overhead runtime sanitizer besar (2x–15x) — pakai hanya untuk build debug/test.
- ASan + UBSan + edge case test + differential test = jaring pengaman komprehensif sebelum submit ke judge.