Static analysis adalah pemeriksaan kode tanpa menjalankannya. Berbeda dengan sanitizers yang membutuhkan eksekusi untuk menemukan bug, static analyzer membedah source code (atau bytecode) untuk menemukan pola bug secara symbolic—mengikuti semua cabang kode yang mungkin. Pendekatan ini bisa menemukan bug yang sulit dipicu dari input normal karena analyzer mengeksplorasi seluruh state space kode secara statis. Pada chapter ini kita akan memakai gcc -fanalyzer (static analyzer bawaan gcc), serta membahas konseptual cppcheck dan clang-tidy yang merupakan tool standalone paling populer. Dokumentasi resmi cppcheck di cppcheck.sourceforge.io, clang-tidy di clang.llvm.org/extra/clang-tidy/.
gcc -fanalyzer: Static Analyzer Bawaan gcc #
Sejak gcc 10, compiler gcc menyertakan static analyzer yang diaktifkan dengan flag -fanalyzer. Analyzer ini melakukan symbolic execution pada kode untuk melacak aliran data lewat berbagai cabang dan mendeteksi bug seperti double-free, use-after-free, null dereference, dan memory leak. Berbeda dengan -Wall -Wextra yang hanya melakukan analisis lokal pada satu statement, -fanalyzer menelusuri seluruh path eksekusi yang mungkin.
/* File: /tmp/test_static2.c */
#include <stdlib.h>
int main(void) {
int *p = (int *)malloc(sizeof(int));
if (!p) return 1;
*p = 10;
free(p);
free(p); /* bug: double-free */
return 0;
}gcc -fanalyzer -std=c11 -Wall -Wextra /tmp/test_static2.c -o /tmp/test_static2Output yang Dihasilkan:
/tmp/test_static2.c: In function 'main':
/tmp/test_static2.c:7:5: warning: pointer 'p' used after 'free' [-Wuse-after-free]
7 | free(p); /* bug: double-free */
| ^~~~~~~
/tmp/test_static2.c:6:5: note: call to 'free' here
6 | free(p);
| ^~~~~~~
/tmp/test_static2.c:7:5: warning: double-'free' of 'p' [CWE-415] [-Wanalyzer-double-free]
7 | free(p); /* bug: double-free */
| ^~~~~~~
'main': events 1-6
|
| 3 | int *p = (int *)malloc(sizeof(int));
| | ^~~~~~~~~~~~~~~~~~~
| | |
| | (1) allocated here
| 4 | if (!p) return 1;
| | ~
| | |
| | (2) assuming 'p' is non-NULL
| | (3) following 'false' branch (when 'p' is non-NULL)...
| 5 | *p = 10;
| | ~~~~~~~
| | |
| | (4) ...to here
| 6 | free(p);
| | ~~~~~~~
| | |
| | (5) first 'free' here
| 7 | free(p); /* bug: double-free */
| | ~~~~~~~
| | |
| | (6) second 'free' here; first 'free' was at (5)
|Mari kita bedah elemen kunci pada laporan -fanalyzer di atas:
warning: double-'free' of 'p' [CWE-415] [-Wanalyzer-double-free]: jenis bug yang terdeteksi adalah double-free. Tag[CWE-415]merujuk pada Common Weakness Enumeration nomor 415—katalog bug keamanan standar industri.'main': events 1-6: gcc menampilkan 6 event yang merupakan trace jalur eksekusi dari alokasi sampai bug terjadi. Setiap event bernomor(1),(2), …,(6).(1) allocated here: baris 3—mallocmengalokasikan blok memori.(2) assuming 'p' is non-NULL: analyzer mengasumsikan cabangif (!p) return 1;mengembalikan false (p tidak NULL), sehingga eksekusi berlanjut.(3) following 'false' branch (when 'p' is non-NULL): analyzer mengikuti cabang false dari pemeriksaan!p.(5) first 'free' heredan(6) second 'free' here; first 'free' was at (5): dua pemanggilanfree(p)berturut-turut pada baris 6 dan 7. Trace ini menjelaskan mengapa analyzer menyimpulkan terjadi double-free.
Deteksi Possible Null Dereference #
Analyzer juga mendeteksi path yang mengakses pointer hasil malloc tanpa memeriksa NULL. Bug ini adalah UB tetapi sering tidak crash karena malloc jarang mengembalikan NULL pada sistem dengan memori cukup.
/* File: /tmp/test_static3.c */
#include <stdlib.h>
int main(void) {
int *p = (int *)malloc(sizeof(int));
/* bug: tidak cek p == NULL */
*p = 10;
free(p);
return 0;
}gcc -fanalyzer -std=c11 -Wall -Wextra /tmp/test_static3.c -o /tmp/test_static3Output yang Dihasilkan:
/tmp/test_static3.c: In function 'main':
/tmp/test_static3.c:5:8: warning: dereference of possibly-NULL 'p' [CWE-690] [-Wanalyzer-possible-null-dereference]
5 | *p = 10;
| ~~~^~~~
'main': events 1-2
|
| 3 | int *p = (int *)malloc(sizeof(int));
| | ^~~~~~~~~~~~~~~~~~~
| | |
| | (1) this call could return NULL
| 4 | /* bug: tidak cek p == NULL */
| 5 | *p = 10;
| | ~~~~~~~
| | |
| | (2) 'p' could be NULL: unchecked value from (1)
|Mari kita uraikan elemen kunci pada laporan null dereference:
warning: dereference of possibly-NULL 'p' [CWE-690]: jenis bug adalah possible null dereference. Tag[CWE-690]merujuk pada CWE untuk “Unchecked Return Value: NULL Pointer Dereference”.(1) this call could return NULL: baris 3—mallocbisa mengembalikan NULL bila memori tidak cukup. Analyzer menandai titik ini sebagai sumber nilai yang berpotensi NULL.(2) 'p' could be NULL: unchecked value from (1): baris 5 (*p = 10) melakukan dereference. Analyzer menelusuri bahwa antara baris 3 dan baris 5 tidak ada pemeriksaanif (p == NULL), sehingga nilai NULL mungkin lolos ke dereference.
Perbaikan yang benar adalah menambah pemeriksaan:
int *p = (int *)malloc(sizeof(int));
if (p == NULL) return 1; /* tangani kegagalan alokasi */
*p = 10;cppcheck #
cppcheck adalah static analyzer standalone gratis yang berfokus pada false positive minim. Berbeda dengan -fanalyzer yang merupakan bagian gcc, cppcheck adalah tool terpisah yang bisa dijalankan pada kode sumber tanpa kompilasi.
cppcheck --enable=all --suppress=missingInclude /tmp/test_static2.cOutput yang Dihasilkan (format resmi cppcheck):
Checking /tmp/test_static2.c ...
/tmp/test_static2.c:7:5: error: Memory pointed to by 'p' is freed twice. [doubleFree]
free(p);
^
/tmp/test_static2.c:6:5: note: Memory pointed to by 'p' was freed
free(p);
^
/tmp/test_static2.c:4:5: style: Variable 'p' can be declared as pointer to const [pointerToConst]
int *p = (int *)malloc(sizeof(int));
^Mari kita bedah elemen cppcheck:
error: Memory pointed to by 'p' is freed twice. [doubleFree]: kategorierrormenandakan bug pasti. Tag[doubleFree]adalah ID pemeriksaan cppcheck yang bisa dipakai untuk suppression.note: Memory pointed to by 'p' was freed: cppcheck menampilkan barisfree(p)pertama sebagai referensi.style: Variable 'p' can be declared as pointer to const [pointerToConst]: kategoristylemenandakan saran peningkatan kode, bukan bug. Flag--enable=allmengaktifkan kategoristyle,performance,portability,warningselainerror.
clang-tidy #
clang-tidy adalah static analyzer yang merupakan bagian ekosistem clang. Kekuatan utamanya adalah koleksi check yang sangat banyak (lebih dari 400) yang dikelompokkan dalam kategori seperti bugprone-*, cert-*, cppcoreguidelines-*, modernize-*, performance-*, dan readability-*.
clang-tidy --checks='bugprone-*,cert-*,performance-*' /tmp/test_static2.c -- -std=c11Output yang Dihasilkan (format resmi clang-tidy):
2 warnings generated.
/tmp/test_static2.c:4:5: warning: calling 'malloc' is unsafe, prefer 'new' or 'std::vector' [bugprone-unsafe-malloc]
int *p = (int *)malloc(sizeof(int));
^
/tmp/test_static2.c:7:5: warning: 'p' is used after it is freed [bugprone-use-after-free]
free(p); /* bug: double-free */
^clang-tidy sering dipakai bersamaan dengan file konfigurasi .clang-tidy di root proyek untuk menentukan check mana yang aktif:
# File: .clang-tidy
Checks: >
-*,
bugprone-*,
cert-*,
cppcoreguidelines-*,
performance-*,
readability-*,
-bugprone-unsafe-malloc
WarningsAsErrors: ''
HeaderFilterRegex: '.*'Perbandingan Tool Static Analysis #
| Tool | Sumber | Fokus | Kelebihan | Kekurangan |
|---|---|---|---|---|
gcc -fanalyzer | bawaan gcc | path-sensitive memory bug | tidak butuh tool terpisah | cakupan check terbatas |
cppcheck | standalone | false positive minim | mudah diintegrasikan CI | kurang sensitif vs clang-tidy |
clang-tidy | bagian clang | kategori check luas | auto-fix banyak check | butuh compile_commands.json |
Integrasi dengan CMake #
Untuk proyek CMake, integrasi static analysis paling bersih adalah dengan menambahkan target khusus:
# CMakeLists.txt
option(ENABLE_ANALYSIS "Enable static analysis" OFF)
if(ENABLE_ANALYSIS)
find_program(CPPCHECK cppcheck)
if(CPPCHECK)
set(CMAKE_C_CPPCHECK "${CPPCHECK};--enable=all;--suppress=missingInclude")
endif()
find_program(CLANGTIDY clang-tidy)
if(CLANGTIDY)
set(CMAKE_C_CLANG_TIDY "${CLANGTIDY};-checks=bugprone-*,cert-*")
endif()
endif()Dengan konfigurasi di atas, cmake -DENABLE_ANALYSIS=ON .. akan otomatis menjalankan cppcheck dan clang-tidy (bila tersedia) pada setiap source file saat kompilasi.
Workflow Static Analysis di CI #
Workflow standar untuk proyek dengan CI/CD:
- Build normal:
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ...sebagai gerbang pertama. - Build dengan
-fanalyzer:gcc -fanalyzer ...untuk pemeriksaan path-sensitive. - cppcheck:
cppcheck --enable=all --error-exitcode=1 .untuk pemeriksaan standalone. - clang-tidy:
clang-tidy -p build/ src/*.cmenggunakan database kompilasi dari CMake. - Sanitizer build:
gcc -fsanitize=address,undefined ...untuk pemeriksaan runtime.
Kelima langkah di atas saling melengkapi dan bisa dijalankan paralel pada CI untuk meminimalkan waktu build.