Files
STM32F4-Base/docs/代码规范/cppcheck使用说明.md
wandering1 c63bd029a9 feat(门禁): 新增 skill-size 钩子——SKILL.md 不得超插件写侧上限 65536
dsh-mnemon 的 MAX_SKILL_BYTES = 65536 只在写侧校验,加载侧从不检查,
所以正文越界的技能照常加载、却永远生成不出草案:同日两次卡死
(83194 字节与 72601 字节)都是这个形态,复位台账无效,只能瘦身。
故把上限机械化,越界与悬空指针在提交期就拦下。

- scripts/check_skill_size.py:纯标准库(裸 python 也能跑,故不解析
  .venv);FAIL >65536、FAIL reference/ 指针悬空、FAIL UTF-8 BOM、
  WARN ≥49152(75%——实测一次「采纳」把主文从 49821 顶到 62806);
  总是全树扫,只拿 reference/x.md 触发的提交正是漏网场景;
  --root 可复指到用户级 ~/.dsh/skills;--selftest 五形态(干净 0 /
  临界 0+WARN / 超限 1 / 悬空+BOM 1 / 根不存在 2)全过
- .pre-commit-config.yaml 加 skill-size,files: ^\.dsh/skills/
- 新增 docs/门禁说明/skill-size.md,含盲区:拦不住插件自身的采纳写盘、
  用户级技能目录不在触发面、不解析 frontmatter、不查 U+FFFD
- 计数同步 51 -> 52(实跑 48 -> 49,索引 28 -> 29 篇):AGENTS.md 与
  门禁索引,另 7 处文档旧计数改写、CI 指南带日期实测行只加注

本笔先落地,技能正文的渐进展开拆分在下一笔。
2026-10-29 15:02:23 +08:00

219 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cppcheck C 静态分析 — 使用说明
> 本文档是 `docs/代码规范/pre-commit使用说明.md` 的配套详解,聚焦 cppcheck 钩子的检查规则、配置参数与误报抑制方法。
> pre-commit 的整体部署架构与其他钩子规则见 `docs/代码规范/pre-commit使用说明.md`。
---
## 1. 概述
cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法分析和数据流推理发现潜在缺陷。
**工作原理**:逐个读取 C 文件 → 分析语法/数据流/控制流 → 发现问题则报错。
**与 pre-commit 的关系**:`cppcheck` / `test-cppcheck` 两根钩子都带 `stages: [manual]`——**`git commit` 阶段不跑**,由远端 CI(`.gitea/workflows/ci.yml` 的「手动阶段门禁」步,逐根 `--hook-stage manual`)与 `scripts/ci_local.py` 的 manual 段调用 cppcheck 命令行;任一已启用类别发现问题即失败(`--error-exitcode=1` 对 warning/performance/portability/style 全部生效)。
### 日常速度
- **提交期**:不跑(`stages: [manual]`),一次带代码的 `git commit` 只跑 49 根快钩子
- **全库扫描**(约 20 s;2026-10-28 实测 `cppcheck` 20.5 s、`test-cppcheck` 38.1 s):手工复跑 `& '.venv\Scripts\python.exe' -m pre_commit run --all-files --hook-stage manual <hook-id>`——**一次只能给一个 id**(`pre_commit run a b c` 实测报 `unrecognized arguments`),故 `cppcheck` / `test-cppcheck` 各跑一次
---
## 2. 检查类别
### 本项目开启的类别
| 类别 | 含义 | 典型问题 |
|---|---|---|
| `warning` | 通用警告 | 变量未使用、除零风险、空指针解引用、unreachable code |
| `performance` | 性能问题 | 不必要的值拷贝、字符串重复查找、低效的函数调用 |
| `portability` | 可移植性问题 | 不同平台行为不一致的代码、隐式类型转换 |
| `style` | 代码风格 | 变量作用域过大、冗余赋值、恒假条件、自赋值等可疑写法(详见第 3 节)|
### 不开启的常见类别
| 类别 | 为什么不开 |
|---|---|
| `error` | 严重错误(数组越界、内存泄漏),cppcheck 默认已包含,不需要显式加 |
| `information` | 信息类(未解析的头文件等),无实际价值 |
---
## 3. 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 防误改 | 已全库启用并修复 |
| `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` 中抑制,原因见第 4.3 节「抑制规则说明」。
> - `--error-exitcode=1` 对**所有已启用级别**(warning/performance/portability/style)生效:任何一级发现问题都会让门禁**失败**(在 CI / `ci_local.py` 的 manual 段拦下)。style 级别的问题若确属误报,用行内抑制(见第 5 节)。
---
## 4. 配置
### 4.1 配置参数
```
--std=c99 # 按 C99 标准检查(Keil 工程常用)
--error-exitcode=1 # 发现问题返回非零退出码(pre-commit 据此判该钩子失败)
--inline-suppr # 启用行内抑制注释(// cppcheck-suppress,默认关闭)
--quiet # 只报问题,不显示扫描进度
# --- 必须抑制(误报/不可修改) ---
--suppress=missingInclude # HAL/CMSIS 路径在 Keil 工程配置里,cppcheck 不知道
--suppress=invalidPrintfArgType_sint # printf 的 %d 传 uint32_t —— 实际安全
--suppress=invalidPrintfArgType_uint # printf 的 %u 传 int —— 实际安全
--suppress=constParameterPointer # HAL 回调签名固定(如 SPI_HandleTypeDef*),不能加 const
--suppress=constParameterCallback # 回调函数指针类型限制,不能改签名
--suppress=unusedFunction # 第三方库回调/弱符号函数被误报为未使用
--suppress=unusedStructMember # HAL 寄存器位域成员未显式使用
--suppress=noExplicitConstructor # C++ 兼容,不影响纯 C
--suppress=useStlAlgorithm # C++ STL 相关,不影响纯 C
# --- 头文件路径 ---
-I Inc -I App -I Drivers/BSP
-I Drivers/STM32F4xx_HAL_Driver/Inc -I Drivers/CMSIS/Include
-I Middlewares/Third_Party/FreeRTOS/Source/include
```
> 注意:以上为逻辑参数列表。在 `.pre-commit-config.yaml` 中书写时必须遵守第 4.2 节的 YAML 引号规则。
### 4.2 ⚠️ 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 <某文件>` 查看实际执行的完整命令行。
### 4.3 抑制规则说明
| 抑制规则 | 原因 | 是否永久 |
|---|---|---|
| `constParameterPointer` | HAL 回调签名由驱动框架定义,用户不能改 | 永久 |
| `constParameterCallback` | 同上,回调函数指针类型受限 | 永久 |
| `unusedFunction` | CubeMX 生成的弱符号、第三方库回调被误报 | 永久 |
| `unusedStructMember` | HAL 寄存器位域按位操作,cppcheck 不理解 | 永久 |
| `redundantAssignment` | HAL 清标志宏(如 `__HAL_SPI_CLEAR_OVRFLAG`)读寄存器覆盖旧值,cppcheck 不理解"读即目的" | 永久 |
### 4.4 扫描范围
通过 `types: [c]` + `exclude` 控制:
**检查**:`App/`、`Src/`、`Inc/`、`Drivers/BSP/` 下的 .c 文件
**不检查**(第三方库,问题不是我们的):
```
Drivers/CMSIS/ # ARM CMSIS
Drivers/STM32F4xx_HAL_Driver/ # ST HAL
Drivers/BSP/UI/ # LVGL UI 层
Drivers/BSP/NET/lftpd/ # LwIP FTP
Drivers/BSP/dhara/ # NAND FTL
Lib/ # LVGL 库
Middlewares/ # FreeRTOS
MDK-ARM/ # Keil 工程文件
test/ # 测试代码
```
---
## 5. 误报抑制方法
### 5.1 行内抑制(`// cppcheck-suppress`)
确属误报、又不想全局 `--suppress` 掉整条规则时,在**报错行的上一行**加注释:
```c
// cppcheck-suppress knownConditionTrueFalse ; 说明原因(分号后文字可选)
if (day_count < diy) break;
```
要点:
- 需要 `--inline-suppr` 开关(已在配置中启用),否则注释无效
- 注释必须紧贴**报错报告行**的上一行(cppcheck 报告行可能不是你以为的那行,以输出为准)
- 检查 ID 后跟的 `(...)` 括号文字会被当作 suppression attribute 解析而报错,说明文字用 `;` 后跟
### 5.2 全局抑制(`--suppress`)
某条规则在整个工程中都是噪音/不适用时,在 `.pre-commit-config.yaml` 的 args 中添加 `--suppress=规则名`(同样遵守 4.2 节引号规则),现有清单见 4.1 节。
---