修复cppcheck钩子YAML参数被逗号/空格拆坏导致钩子空转的问题
.pre-commit-config.yaml: - args 元素加引号,修正 YAML 流序列解析 - 此前 --enable 只生效 warning 类别,-I 路径全部失效 - 新增 --inline-suppr 启用行内抑制注释 App/sys_clock.c: - knownConditionTrueFalse 误报行内抑制 - 闰年 diy=366 分支 cppcheck 数据流未跟踪 docs/pre-commit使用说明.md: - 补充 style 类别完整规则详解 - 新增 YAML 引号陷阱排查方法、行内抑制用法 - 修正 style 不阻断提交的错误论断
This commit is contained in:
+13
-11
@@ -41,17 +41,19 @@ repos:
|
||||
- id: cppcheck
|
||||
name: cppcheck
|
||||
entry: cppcheck
|
||||
args: [--enable=warning,performance,portability,style, --error-exitcode=1,
|
||||
--std=c99, --quiet,
|
||||
--suppress=missingIncludeSystem, --suppress=missingInclude,
|
||||
--suppress=invalidPrintfArgType_sint, --suppress=invalidPrintfArgType_uint,
|
||||
--suppress=unusedFunction, --suppress=unusedStructMember,
|
||||
--suppress=noExplicitConstructor, --suppress=useStlAlgorithm,
|
||||
--suppress=constParameterPointer, --suppress=constParameterCallback,
|
||||
--suppress=constVariablePointer, --suppress=redundantAssignment,
|
||||
-I Inc -I App -I Drivers/BSP,
|
||||
-I Drivers/STM32F4xx_HAL_Driver/Inc -I Drivers/CMSIS/Include,
|
||||
-I Middlewares/Third_Party/FreeRTOS/Source/include]
|
||||
# 注意:YAML 流序列中逗号是元素分隔符,含逗号/空格的参数必须加引号
|
||||
args: ['--enable=warning,performance,portability,style', '--error-exitcode=1',
|
||||
'--inline-suppr',
|
||||
'--std=c99', '--quiet',
|
||||
'--suppress=missingIncludeSystem', '--suppress=missingInclude',
|
||||
'--suppress=invalidPrintfArgType_sint', '--suppress=invalidPrintfArgType_uint',
|
||||
'--suppress=unusedFunction', '--suppress=unusedStructMember',
|
||||
'--suppress=noExplicitConstructor', '--suppress=useStlAlgorithm',
|
||||
'--suppress=constParameterPointer', '--suppress=constParameterCallback',
|
||||
'--suppress=constVariablePointer', '--suppress=redundantAssignment',
|
||||
'-I', 'Inc', '-I', 'App', '-I', 'Drivers/BSP',
|
||||
'-I', 'Drivers/STM32F4xx_HAL_Driver/Inc', '-I', 'Drivers/CMSIS/Include',
|
||||
'-I', 'Middlewares/Third_Party/FreeRTOS/Source/include']
|
||||
language: system
|
||||
types: [c]
|
||||
# 仅扫描项目自有代码;排除第三方库与 LVGL 依赖文件
|
||||
|
||||
@@ -105,6 +105,7 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
||||
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {
|
||||
diy = 366uLL;
|
||||
}
|
||||
// cppcheck-suppress knownConditionTrueFalse ; 闰年 diy=366 时 day_count 可能为 365
|
||||
if (day_count < diy) break;
|
||||
day_count -= diy;
|
||||
y++;
|
||||
|
||||
+112
-4
@@ -97,7 +97,81 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
| `warning` | 通用警告 | 变量未使用、除零风险、空指针解引用、unreachable code |
|
||||
| `performance` | 性能问题 | 不必要的值拷贝、字符串重复查找、低效的函数调用 |
|
||||
| `portability` | 可移植性问题 | 不同平台行为不一致的代码、隐式类型转换 |
|
||||
| `style` | 代码风格 | 变量作用域过大、冗余赋值、恒假条件、switch 缺 default |
|
||||
| `style` | 代码风格 | 变量作用域过大、冗余赋值、恒假条件、自赋值等可疑写法(详见下文详解)|
|
||||
|
||||
#### style 类别具体检查什么(cppcheck 2.21.0)
|
||||
|
||||
`style` 是"风格/质量"级别——报告的问题**不一定是 bug**,但绝大多数是**冗余代码、死代码、可疑写法、可以写得更安全/更清晰的代码**。下面按主题列出本项目(纯 C)实际会命中的规则(`--enable=style` 已开启):
|
||||
|
||||
**① 冗余与死代码(redundant & dead code)— 最值得修**
|
||||
|
||||
| 规则 | 检查什么 | 反例 |
|
||||
|---|---|---|
|
||||
| `redundantAssignment` | 变量赋值后被新值覆盖、旧值从未被读取 | `ret = OK; ret = f();`(首个赋值无意义)|
|
||||
| `unreadVariable` | 变量赋值后从未被读取 | `s = -1;` 随后循环里被覆盖、从未用到 |
|
||||
| `redundantCondition` | 短路求值使某条件恒真 | `if (a && a > 5)`(a 非零时前半已恒真)|
|
||||
| `duplicateCondition` | if/else 分支条件重复 | `if (x){} else if (x){}` |
|
||||
| `duplicateBreak` | 连续重复 break | `break; break;` |
|
||||
| `duplicateExpression` / `duplicateExpressionTernary` | 运算符两侧表达式相同 | `a = a && b`、`x = x ? 1 : 2` |
|
||||
| `unreachableCode` | return 之后不会执行的代码 | `return 0; foo();` |
|
||||
| `unassignedVariable` | 变量声明后从未赋值 | `int x; use(x);` |
|
||||
| `unusedLabel` | 从未被 goto 的标签 | `label:` 无对应 goto |
|
||||
|
||||
**② 恒真/恒假条件(always-true / always-false)— 常藏逻辑 Bug**
|
||||
|
||||
| 规则 | 检查什么 | 反例 |
|
||||
|---|---|---|
|
||||
| `knownConditionTrueFalse` | 依据数据流推断条件恒真/恒假 | `while (n>=365){ ... if (n<360) break; }` |
|
||||
| `knownArgument` | 参数固定时函数结果可推断 | `strlen("")` 恒为 0 |
|
||||
| `knownPointerToBool` | 已知指针当布尔用 | p 恒非空时 `if (p)` |
|
||||
| `compareValueOutOfTypeRangeError` | 与类型范围外的值比较 | `uint16_t x; if (x > 65535)` 恒假 |
|
||||
| `unsignedLessThanZero` | 无符号 < 0 恒假 | 无符号 `n` 的 `if (n < 0)` |
|
||||
| `unsignedPositive` | 无符号 >= 0 / > -1 恒真 | `if (n >= 0)` |
|
||||
| `pointerLessThanZero` / `pointerPositive` | 指针 < 0 / > 0 | `if (p < 0)` |
|
||||
|
||||
**③ const 化与作用域(const & scope)**
|
||||
|
||||
| 规则 | 检查什么 | 备注 |
|
||||
|---|---|---|
|
||||
| `constVariable` / `constVariablePointer` | 变量/指针可加 const 防误改 | `constVariablePointer` 当前**已抑制**(改动量大)|
|
||||
| `constParameter` / `constParameterPointer` / `constParameterCallback` | 参数可加 const | 回调相关两条**已抑制**(HAL 签名不可改)|
|
||||
| `variableScope` | 变量作用域可缩小(声明移入实际使用的最内层块)| 已全库修复 |
|
||||
| `functionStatic` / `staticFunction` | 函数可加 static 限制可见性 | 本库函数多被外部引用,命中少 |
|
||||
|
||||
**④ 可疑写法(suspicious patterns)— 疑似笔误**
|
||||
|
||||
| 规则 | 检查什么 |
|
||||
|---|---|
|
||||
| `clarifyCalculation` | 位运算与逻辑运算易混(`|` vs `‖`、`&` vs `&&`)|
|
||||
| `clarifyCondition` / `assignmentInCondition` | 条件里出现赋值(疑似 `=` 误写 `==`)、链式比较 |
|
||||
| `bitwiseOnBoolean` | 对布尔量做位运算 |
|
||||
| `selfAssignment` | 自赋值 `x = x` |
|
||||
| `oppositeExpression` | 同一条件内互相矛盾的表达式 |
|
||||
| `moduloofone` | `x % 1`(恒为 0)|
|
||||
| `mismatchingBitAnd` | 位运算不匹配 |
|
||||
| `suspiciousFloatingPointCast` | 浮点强转整型(可能丢精度)|
|
||||
| `incrementboolean` | 对布尔量自增 |
|
||||
| `shadowVariable` / `shadowArgument` / `shadowFunction` | 局部变量遮蔽外层变量/参数/函数 |
|
||||
| `arrayIndexThenCheck` | 数组下标先使用、后越界检查 |
|
||||
| `truncLongCastAssignment` / `truncLongCastReturn` | long 强转导致截断 |
|
||||
| `funcArgNamesDifferent` | 函数参数名与声明不一致 |
|
||||
| `nanInArithmeticExpression` | 算术表达式中出现 NaN |
|
||||
| `ignoredReturnErrorCode` | 忽略带错误码的返回值 |
|
||||
| `unpreciseMathCall` | 调用不精确的数学函数 |
|
||||
| `returnNonBoolInBooleanFunction` | 布尔函数返回非布尔值 |
|
||||
| `multiCondition` | 可疑的多条件组合 |
|
||||
| `duplicateAssignExpression` | 同一表达式赋给多个变量 |
|
||||
| `redundantBitwiseOperationInSwitch` / `redundantAssignInSwitch` | switch 中冗余位运算/赋值 |
|
||||
|
||||
**⑤ 不适用本 C 工程的规则(C++ 专属,自动跳过)**
|
||||
|
||||
`catchExceptionByValue`、`cstyleCast`、`missingOverride`、`noConstructor`、`noExplicitConstructor`、`operatorEqRetRefThis`、`unusedScopedObject`、`virtualCallInConstructor`、`useStlAlgorithm`、`constParameterReference` 等(异常/类/STL 相关,纯 C 代码不会命中)。
|
||||
|
||||
> 说明:
|
||||
> - 上表规则均出自 cppcheck 2.21.0 `--errorlist` 中 `severity=style` 的真实清单。
|
||||
> - 部分规则(如 `duplicateBranch`、`suspiciousSemicolon`、`checkConst`)需要额外 `--inconclusive` 才会激活,为减少误报**未开启**。
|
||||
> - `unusedFunction`、`unusedStructMember`、`useStlAlgorithm`、`noExplicitConstructor` 等已在 `--suppress` 中抑制,原因见下文「抑制规则说明」。
|
||||
> - `--error-exitcode=1` 对**所有已启用级别**(warning/performance/portability/style)生效:任何一级发现问题都会**阻止提交**。style 级别的问题若确属误报,用行内抑制(见下文「行内抑制误报」)。
|
||||
|
||||
#### 不开启的常见类别
|
||||
|
||||
@@ -111,6 +185,7 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
```
|
||||
--std=c99 # 按 C99 标准检查(Keil 工程常用)
|
||||
--error-exitcode=1 # 发现问题返回非零退出码(pre-commit 用来拦截提交)
|
||||
--inline-suppr # 启用行内抑制注释(// cppcheck-suppress,默认关闭)
|
||||
--quiet # 只报问题,不显示扫描进度
|
||||
|
||||
# --- 必须抑制(误报/不可修改) ---
|
||||
@@ -133,6 +208,38 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
-I Middlewares/Third_Party/FreeRTOS/Source/include
|
||||
```
|
||||
|
||||
#### ⚠️ YAML 参数引号陷阱(曾导致钩子完全失效)
|
||||
|
||||
`.pre-commit-config.yaml` 的 `args:` 使用 YAML **流序列** `[a, b, c]` 语法,其中**逗号是元素分隔符、空格不产生新元素**。曾出现两个致命错误,导致 cppcheck 钩子**空转**("Passed" 但从未真正检查):
|
||||
|
||||
| 错误写法 | 实际被解析成 | 后果 |
|
||||
|---|---|---|
|
||||
| `[--enable=warning,performance,portability,style]` | `--enable=warning` + 3 个假文件参数 `performance`/`portability`/`style` | **只启用 warning**,style/performance/portability 全部失效 |
|
||||
| `[-I Inc -I App -I Drivers/BSP]` | 单个参数 `"-I Inc -I App -I Drivers/BSP"` | 所有 include 路径失效(指向一个不存在的目录名)|
|
||||
|
||||
**正确写法**:含逗号或空格的参数必须加引号,`-I` 与路径必须分成两个元素:
|
||||
|
||||
```yaml
|
||||
args: ['--enable=warning,performance,portability,style', '--error-exitcode=1',
|
||||
'-I', 'Inc', '-I', 'App']
|
||||
```
|
||||
|
||||
**排查方法**:怀疑参数被拆坏时,在 PATH 前置一个同名 shim(如 `cppcheck.cmd` 把 `%*` 写日志),跑 `pre-commit run cppcheck --files <某文件>` 查看实际执行的完整命令行。
|
||||
|
||||
#### 行内抑制误报(`// cppcheck-suppress`)
|
||||
|
||||
确属误报、又不想全局 `--suppress` 掉整条规则时,在**报错行的上一行**加注释:
|
||||
|
||||
```c
|
||||
// cppcheck-suppress knownConditionTrueFalse ; 说明原因(分号后文字可选)
|
||||
if (day_count < diy) break;
|
||||
```
|
||||
|
||||
要点:
|
||||
- 需要 `--inline-suppr` 开关(已在配置中启用),否则注释无效
|
||||
- 注释必须紧贴**报错报告行**的上一行(cppcheck 报告行可能不是你以为的那行,以输出为准)
|
||||
- 检查 ID 后跟的 `(...)` 括号文字会被当作 suppression attribute 解析而报错,说明文字用 `;` 后跟
|
||||
|
||||
#### 抑制规则说明
|
||||
|
||||
| 抑制规则 | 原因 | 是否永久 |
|
||||
@@ -174,13 +281,14 @@ test/ # 测试代码
|
||||
| `redundantAssignment` | 重复赋值覆盖旧值 | ch395f.c | ✅ 已抑制(HAL 清标志宏读寄存器即目的,误报) |
|
||||
| `unreadVariable` | 变量赋值后未读取 | gd5f2gq5ue.c, time_sync.c, ringbuf.c | ✅ 已修复(移除冗余初始化) |
|
||||
| `variableScope` | 变量作用域可缩小 | sys_clock.c, net_socket.c 等 18 处 | ✅ 已修复(声明移入实际使用块) |
|
||||
| `knownConditionTrueFalse` | `day_count < diy` 被判恒假(闰年 `diy=366` 分支未跟踪,误报) | sys_clock.c:108 | ✅ 已行内抑制(`// cppcheck-suppress`) |
|
||||
|
||||
> 注:`sys_clock.c` 的 `knownConditionTrueFalse`(`day_count < diy` 恒假)为 cppcheck 误报——未跟踪 `diy` 可能被赋为 366(闰年)的分支,实际逻辑正确,暂不处理。
|
||||
> 注:`knownConditionTrueFalse` 属真误报——闰年分支 `diy=366` 时 `day_count==365` 的 break 有意义,cppcheck 数据流分析未覆盖该分支。已用行内抑制处理,不影响该规则在其他文件上的正常检查。
|
||||
|
||||
#### 日常速度
|
||||
|
||||
- **增量扫描**(`git commit` 时):只检查 staged 文件,通常 <1 秒
|
||||
- **全库扫描**(`pre-commit run --all-files`):约30秒,仅在配置变更后使用
|
||||
- **增量扫描**(`git commit` 时):只检查 staged 文件,单文件约 1~2 秒
|
||||
- **全库扫描**(`pre-commit run --all-files`):约 5 秒,用于配置变更后验证
|
||||
|
||||
### 2.7 no-mojibake — U+FFFD 乱码拦截
|
||||
|
||||
|
||||
Reference in New Issue
Block a user