Bài 58.228 phút đọc
clang-tidy và cppcheck
Sau bài này bạn sẽ làm được
- Chạy clang-tidy với bộ kiểm tra phù hợp
- Sinh compile_commands.json
- Chạy cppcheck và đọc báo cáo
- Tắt báo giả một cách có kiểm soát
Trình biên dịch nhìn từng câu lệnh. Bộ phân tích tĩnh đi theo từng đường thực thi có thể, nên nó thấy được những thứ trình biên dịch không thấy: dùng sau khi giải phóng, rò rỉ trên nhánh lỗi, giải tham chiếu NULL ba hàm sau chỗ gán.
#Mã thử
loi.c, năm lỗi cố ý
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 1. Ro ri bo nho tren nhanh loi */
static char *doc_ten(const char *dd) {
FILE *t = fopen(dd, "rb");
if (!t) return NULL;
char *dem = malloc(256);
if (fread(dem, 1, 255, t) == 0) {
fclose(t);
return NULL; /* quen free(dem) */
}
dem[255] = '\0';
fclose(t);
return dem;
}
/* 2. Dung sau khi giai phong */
static void sau_khi_tha(void) {
char *p = malloc(16);
free(p);
strcpy(p, "abc"); /* dung sau khi tha */
}
/* 3. Gan trong dieu kien, va bien chua khoi tao */
static int lam(int *p) {
int x;
if (p = NULL) return -1; /* gan chu khong so sanh */
return x + *p; /* x chua khoi tao */
}
/* 4. Bo dem co dinh voi ham khong gioi han */
static void chep(const char *nguon) {
char dem[16];
strcpy(dem, nguon); /* co the tran */
printf("%s\n", dem);
}
/* 5. Chia cho khong */
static int chia(int a) { return a / 0; }
int main(int argc, char **argv) {
(void)argc;
char *t = doc_ten(argv[1]);
if (t) { chep(t); free(t); }
sau_khi_tha();
printf("%d\n", chia(5));
return 0;
}#GCC so với clang-tidy
terminal
gcc -std=c11 -Wall -Wextra -O2 -c loi.c -o /dev/null
loi.c:29:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses] loi.c:41:35: warning: division by zero [-Wdiv-by-zero] loi.c:27:12: warning: 'lam' defined but not used [-Wunused-function]
# so canh bao: 3
terminal
clang-tidy --version
LLVM (http://llvm.org/): LLVM version 19.1.1 Optimized build.
clang-tidy loi.c --checks='-*,clang-analyzer-*,bugprone-*,cert-*' -- -std=c11
loi.c:11:9: warning: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c] loi.c:15:5: warning: the value returned by this function should not be disregarded; neglecting it may lead to errors [cert-err33-c] loi.c:23:5: warning: Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119 [clang-analyzer-security.insecureAPI.strcpy] loi.c:23:5: warning: Use of memory after it is freed [clang-analyzer-unix.Malloc] loi.c:29:11: warning: an assignment within an 'if' condition is bug-prone [bugprone-assignment-in-if-condition] loi.c:30:16: warning: Dereference of null pointer (loaded from variable 'p') [clang-analyzer-core.NullDereference] loi.c:36:5: warning: Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119 loi.c:41:35: warning: Division by zero [clang-analyzer-core.DivideZero]
| Lỗi | GCC | clang-tidy |
|---|---|---|
| Gán trong điều kiện if | Có | Có |
| Chia cho không | Có | Có |
| Dùng sau khi giải phóng | Không | Có |
| Giải tham chiếu NULL sau phép gán | Không | Có |
| strcpy không giới hạn | Không | Có |
| Bỏ qua giá trị trả về của fclose | Không | Có |
| Rò rỉ bộ nhớ trong doc_ten | Không | Không, xem bên dưới |
#Tệp .clang-tidy
.clang-tidy, đặt ở gốc dự án
---
Checks: >
-*,
bugprone-*,
cert-*,
clang-analyzer-*,
readability-braces-around-statements,
readability-inconsistent-declaration-parameter-name,
-bugprone-easily-swappable-parameters,
-cert-err33-c
WarningsAsErrors: 'clang-analyzer-*'
HeaderFilterRegex: '.*'
FormatStyle: file| Nhóm kiểm tra | Nội dung | Nên bật cho C |
|---|---|---|
| clang-analyzer-* | Phân tích theo đường: rò rỉ, NULL, dùng sau khi thả | CÓ, quan trọng nhất |
| bugprone-* | Mẫu mã dễ sinh lỗi | CÓ |
| cert-* | Quy tắc CERT C, xem Bài 59.4 | CÓ, phần lớn |
| misc-* | Linh tinh, chất lượng khác nhau | Chọn lọc |
| performance-* | Chủ yếu cho C++ | Không cần |
| readability-* | Phong cách và độ dễ đọc | Chọn lọc |
| modernize-* | Chuyển sang C++ hiện đại | KHÔNG, chỉ C++ |
| cppcoreguidelines-* | Hướng dẫn C++ Core | KHÔNG, chỉ C++ |
#compile_commands.json
Cơ sở dữ liệu biên dịch
Một tệp JSON liệt kê, cho mỗi tệp nguồn, đúng dòng lệnh dùng để biên dịch nó. Mọi công cụ dựa trên Clang đọc tệp này để biết cờ
-I, -D, và chuẩn ngôn ngữ, thay vì bắt bạn gõ lại.compile_commands.json
[
{
"directory": "/du-an",
"command": "gcc -std=c11 -Wall -Wextra -Iinclude -c loi.c",
"file": "loi.c"
}
]terminal
# CMake: mot dong
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
# Make, Autotools, hay bat ky he thong nao: dung bear
bear -- make
# Meson: sinh san trong thu muc dung
meson setup build
# Khong co gi trong so tren: sinh bang mot dong shell
for f in src/*.c; do echo "{\"directory\":\"$PWD\",\"command\":\"gcc -std=c11 -Iinclude -c $f\",\"file\":\"$f\"}"; done | jq -s . > compile_commands.json
# Co tep roi thi khong can dau -- nua
clang-tidy src/*.c
#cppcheck
terminal
cppcheck --enable=all --std=c11 --inline-suppr loi.c
Checking loi.c ...
loi.c:12:9: error: Memory leak: dem [memleak]
return NULL;
^
loi.c:23:5: error: Dereferencing 'p' after it is deallocated / released [deallocuse]
strcpy(p, "abc");
^
loi.c:30:12: error: Uninitialized variable: x [uninitvar]
return x + *p;
^
loi.c:41:34: error: Division by zero. [zerodiv]
static int chia(int a) { return a / 0; }
^
loi.c:9:17: warning: Return value of allocation function 'malloc' is not stored. [leakNoVarFunctionCall]
nofile:0:0: information: Active checkers: method... [checkersReport]| clang-tidy | cppcheck | |
|---|---|---|
| Cần biên dịch được không | Có, cần header và cờ đúng | Không, phân tích được mã không dịch được |
| Cần compile_commands.json | Nên có | Không bắt buộc |
| Xử lý #ifdef | Chỉ nhánh đang bật | Thử NHIỀU tổ hợp |
| Phân tích theo đường | Rất mạnh | Có, kém hơn |
| Báo giả | Ít | Nhiều hơn |
| Tốc độ | Chậm hơn | Nhanh hơn |
| Sửa tự động | Có, với --fix | Không |
| Cài đặt | Cần LLVM | Một tệp thực thi nhỏ |
Cờ cppcheck nên dùng
cppcheck \
--enable=warning,style,performance,portability \
--std=c11 \
--inline-suppr \
--error-exitcode=1 \
--suppress=missingIncludeSystem \
-I include \
src/
# --enable=all bat ca "information" va "unusedFunction", cai thu hai
# bao moi ham cong khai cua thu vien la "khong dung", nen rat on ao.
# Liet ke ro tung nhom thay vi dung all.
# --inline-suppr cho phep tat bang binh luan trong ma.
# --error-exitcode=1 de CI do khi co loi.
# --suppress=missingIncludeSystem tat canh bao "khong tim thay
# header he thong", vi cppcheck khong can chung.#Tắt báo giả có kiểm soát
nolint.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void an_toan(char *dich, const char *nguon) {
/* Nguoi goi bao dam dich >= 256 va nguon < 200, xem giao_thuc.h:41 */
strcpy(dich, nguon); /* NOLINT(clang-analyzer-security.insecureAPI.strcpy) */
}
void khong_an_toan(char *dich, const char *nguon) {
strcpy(dich, nguon);
}terminal
# Dung .clang-tidy o gôc du an, co WarningsAsErrors
clang-tidy nolint.c -- -std=c11
nolint.c:11:5: error: Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119 [clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors]
Bốn cách tắt, theo thứ tự nên ưu tiên
/* 1. NOLINT co ten kiem tra cu the, tren MOT dong. Tot nhat. */
strcpy(dich, nguon); /* NOLINT(clang-analyzer-security.insecureAPI.strcpy) */
/* 2. NOLINTNEXTLINE, khi dong da qua dai */
/* NOLINTNEXTLINE(bugprone-branch-clone) */
if (a) lam_x(); else lam_x();
/* 3. NOLINTBEGIN va NOLINTEND cho mot khoi */
/* NOLINTBEGIN(cert-err33-c) */
fclose(t1);
fclose(t2);
fclose(t3);
/* NOLINTEND(cert-err33-c) */
/* 4. NOLINT tran, tat MOI kiem tra. Tranh dung. */
strcpy(dich, nguon); /* NOLINT */
/* Cua cppcheck: */
/* cppcheck-suppress memleak */
return NULL;
/* Hoac trong tep suppressions.txt: */
memleak:src/cu.c:112
uninitvar:src/tu-sinh/*Tự làm thử
- Chạy
clang-tidytrên một tệp của bạn và đếm cảnh báo theo nhóm. - Viết tệp
.clang-tidybậtclang-analyzer-*vàbugprone-*, tắt hai kiểm tra ồn ào nhất. - Sinh
compile_commands.jsoncho dự án của bạn. - Viết một hàm có rò rỉ trên nhánh lỗi và kiểm tra clang-tidy có tìm ra không; nếu không, thử làm hàm ngắn lại.
- Tắt một báo giả bằng
NOLINTcó tên kiểm tra và bình luận giải thích. - Cài
cppcheckvà so kết quả của nó với clang-tidy trên cùng một tệp.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- GCC bắt 3 trong 5 lỗi trong ví dụ; clang-tidy bắt 6 và in ra cả đường đi dẫn tới lỗi.
- Phân tích tĩnh là heuristic có ngân sách: không báo lỗi không có nghĩa là không có lỗi, và hàm ngắn được phân tích kỹ hơn.
.clang-tidynên bắt đầu bằng-*, vàWarningsAsErrorschỉ nên gồmclang-analyzer-*.compile_commands.jsoncần cho clang-tidy, clangd, và các công cụ Clang khác.cppcheckyếu hơn về phân tích theo đường nhưng thử được nhiều tổ hợp#ifdef.