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.

c
/* 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;
}
bash
gcc -fanalyzer -std=c11 -Wall -Wextra /tmp/test_static2.c -o /tmp/test_static2

Output yang Dihasilkan:

text
/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:

  1. 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.
  2. 'main': events 1-6: gcc menampilkan 6 event yang merupakan trace jalur eksekusi dari alokasi sampai bug terjadi. Setiap event bernomor (1), (2), …, (6).
  3. (1) allocated here: baris 3—malloc mengalokasikan blok memori.
  4. (2) assuming 'p' is non-NULL: analyzer mengasumsikan cabang if (!p) return 1; mengembalikan false (p tidak NULL), sehingga eksekusi berlanjut.
  5. (3) following 'false' branch (when 'p' is non-NULL): analyzer mengikuti cabang false dari pemeriksaan !p.
  6. (5) first 'free' here dan (6) second 'free' here; first 'free' was at (5): dua pemanggilan free(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.

c
/* 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;
}
bash
gcc -fanalyzer -std=c11 -Wall -Wextra /tmp/test_static3.c -o /tmp/test_static3

Output yang Dihasilkan:

text
/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:

  1. 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”.
  2. (1) this call could return NULL: baris 3—malloc bisa mengembalikan NULL bila memori tidak cukup. Analyzer menandai titik ini sebagai sumber nilai yang berpotensi NULL.
  3. (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 pemeriksaan if (p == NULL), sehingga nilai NULL mungkin lolos ke dereference.

Perbaikan yang benar adalah menambah pemeriksaan:

c
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.

bash
cppcheck --enable=all --suppress=missingInclude /tmp/test_static2.c

Output yang Dihasilkan (format resmi cppcheck):

text
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:

  1. error: Memory pointed to by 'p' is freed twice. [doubleFree]: kategori error menandakan bug pasti. Tag [doubleFree] adalah ID pemeriksaan cppcheck yang bisa dipakai untuk suppression.
  2. note: Memory pointed to by 'p' was freed: cppcheck menampilkan baris free(p) pertama sebagai referensi.
  3. style: Variable 'p' can be declared as pointer to const [pointerToConst]: kategori style menandakan saran peningkatan kode, bukan bug. Flag --enable=all mengaktifkan kategori style, performance, portability, warning selain error.

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-*.

bash
clang-tidy --checks='bugprone-*,cert-*,performance-*' /tmp/test_static2.c -- -std=c11

Output yang Dihasilkan (format resmi clang-tidy):

text
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:

yaml
# File: .clang-tidy
Checks: >
  -*,
  bugprone-*,
  cert-*,
  cppcoreguidelines-*,
  performance-*,
  readability-*,
  -bugprone-unsafe-malloc
WarningsAsErrors: ''
HeaderFilterRegex: '.*'

Perbandingan Tool Static Analysis #

ToolSumberFokusKelebihanKekurangan
gcc -fanalyzerbawaan gccpath-sensitive memory bugtidak butuh tool terpisahcakupan check terbatas
cppcheckstandalonefalse positive minimmudah diintegrasikan CIkurang sensitif vs clang-tidy
clang-tidybagian clangkategori check luasauto-fix banyak checkbutuh compile_commands.json

Integrasi dengan CMake #

Untuk proyek CMake, integrasi static analysis paling bersih adalah dengan menambahkan target khusus:

cmake
# 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:

  1. Build normal: gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ... sebagai gerbang pertama.
  2. Build dengan -fanalyzer: gcc -fanalyzer ... untuk pemeriksaan path-sensitive.
  3. cppcheck: cppcheck --enable=all --error-exitcode=1 . untuk pemeriksaan standalone.
  4. clang-tidy: clang-tidy -p build/ src/*.c menggunakan database kompilasi dari CMake.
  5. 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.