docs(门禁说明): 新建逐项门禁文档目录,先落索引 README 与 bsp-err 首篇
用户的原话是:"现在门禁都在干什么,我一概不知。" 现有文档只回答"有哪些钩子、怎么配" (docs/代码规范/pre-commit使用说明.md),回答不了"这一条到底在拦什么、什么时候会骗我"。 故新开一个目录,**每种门禁一篇**,八段固定结构:查什么 / 为什么要它(拦下过什么真事)/ 触发面 / 报错怎么读 / 好处 / **限制与盲区** / 豁免与基线口径 / 改门禁本身要注意什么。 **最后一节是这批文档存在的理由**:只看"拦了什么"的门禁文档会让人以为绿就是对的。而本仓 三个增量拦截门禁(layer-deps 6 条、bsp-err 38 条、id-check 台账 640 处)的绿灯含义只是 "没有比上次更差"。 索引按 39 个 hook → 23 篇规划(16 个主机单测是同机制 16 实例,合一篇;cppcheck 与 test-cppcheck 是同工具两套范围,合一篇),文件直接用 hook id 命名,与提交输出里的名字 一致、可 grep。README 另有一节"三种拦截姿态"——改写型 / 硬拦截 / 增量拦截——分清这三类, 报错时才知道该改代码、还是该还基线。钩子清单、files 范围、pass_filenames 全部从 .pre-commit-config.yaml 实测解析(39 个 id:官方 5 + gitlint + local 33),不是手抄。 本次交付:索引 README + 首篇 bsp-err.md。首篇里把今天踩过的坑全写进"限制与盲区",共 10 条, 例如:只扫 Drivers/BSP(App 层同类问题仍无人管);consume-result 只认单行整句的裸调用, 跨行写的调用按正则形状测不到;must-use 名单靠"返回类型 + 注释关键词"启发式,注释不写 "错误码/失败"就漏、改名就能绕开规则;HAL 族是固定清单,清单外的返回 HAL 状态的函数不在 名单里;return-doc 的判据是"注释块里出现『返回值』字样",**散文里出现这三个字也会放行** (本仓真发生过两条靠函数说明里的关键词误判通过);基线键带出现序号,--prune 只报"消失"、 不会报告某处只是搬了家;改标签名等于改基线键,必须原子完成。在册数量与构成按当前基线 逐条数出来写(38 = consume-result 37:gd5f2gq5ue.c 28 含 14 处 HAL 丢弃 + 14 处自家原语、 ui_net_sink.c 6 全为 net_close、rs485.c 2、nand_ftl.c 1;err-type 1 为 bool ui_content_page_open())。 同步指向:AGENTS.md 提交门禁一节改为"每种门禁一篇详解 → docs/门禁说明/README.md",通用 机制仍指 pre-commit使用说明.md;docs/README.md §7.1 增加索引行;.pre-commit-config.yaml 的 bsp-err 注释块顺手更正(原文还写着"三条规则""现 1 条""只能靠书写约定兜着",均已过时)。 迁移状态用索引表的"状态"列跟踪;全部写完后 pre-commit使用说明.md §2 收敛为通用机制 + 总览,同一事实只留一处(docs/文档规范.md 的要求)。 验证:纯文档与注释改动,未触碰构建产物;check_doc_links --all --sections-strict 149 篇 0 问题(新目录的相对链接、跨文档章节引用都能判定);bsp-err 仍 38/38/0;本次受影响文件的 pre-commit 全绿 RC=0;两份新文档字节卫生 ok(无 BOM、无 CR、无 U+FFFD、有尾换行)。
This commit is contained in:
@@ -569,12 +569,13 @@ repos:
|
||||
pass_filenames: false
|
||||
|
||||
# 15) BSP 错误码与超时命名约定(报告 S-5 的机械检查那一半)。
|
||||
# 三条规则:公开 API 不得用 HAL_StatusTypeDef/bool 当错误通道(谓词 is_/has_/can_/valid
|
||||
# 不在此列);*_TIMEOUT 宏不得取负值(名字叫 TIMEOUT 其实是返回码,读代码的人会当成
|
||||
# 时长);非 void 的公开 API 必须有「返回值:」注释。存量钉 BASELINE、只减不增(2026-10-23 首清点 19 条,已还 18 → **1 条**:`bool ui_content_page_open()`),
|
||||
# 四项检查:公开 API 不得用 HAL_StatusTypeDef/bool 当错误通道(谓词 is_/has_/can_/valid
|
||||
# 不在此列);*_TIMEOUT 宏不得取负值(名字叫 TIMEOUT 其实是返回码);非 void 的公开 API
|
||||
# 必须有「返回值:」注释;**返回错误码的函数不得裸丢弃返回值**(接住或显式 (void)——实测
|
||||
# ARMCC v5 与 cppcheck 都不报这类,只有这条会响)。存量钉 BASELINE、只减不增(2026-10-23
|
||||
# 首清点 19 条 → 已还 18;新增一项又暴露 37 条 → **现 38 条**:37 丢弃 + 1 个 bool 待改签名),
|
||||
# 修完跑 --prune 点名收紧基线。约定正文 = Drivers/BSP/bsp_err.h,规则说明 =
|
||||
# scripts/check_bsp_err.py 文件头。注:ARMCC v5 对 warn_unused_result 不生效(实测),
|
||||
# 所以"丢弃返回值必须写 (void)"这条只能靠书写约定与评审兜着。
|
||||
# scripts/check_bsp_err.py 文件头,逐项详解(含限制与盲区)= docs/门禁说明/bsp-err.md。
|
||||
# 恒扫全库(要按 git 清单建公开头索引),故 pass_filenames: false。
|
||||
- id: bsp-err
|
||||
name: BSP 错误码与超时命名约定
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
## 提交门禁(pre-commit)
|
||||
|
||||
- 任一失败即拦截,共 **39 个钩子**(逐个详解 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 6 条基线只减不增,报告 S-1)+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**),报告 S-5)
|
||||
- 任一失败即拦截,共 **39 个钩子**(**每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→ `docs/门禁说明/README.md` 索引**;通用机制与整体口径 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 6 条基线只减不增,报告 S-1)+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**),报告 S-5)
|
||||
- **范围 = 项目代码**:第三方(`Lib/`、`Middlewares/`、CMSIS、HAL、`lftpd`、`dhara`、`MDK-ARM`)排除;`cppcheck`/`coding-standard` 另排除 `test/`,规范检查再排除 CubeMX 生成的 `Src/`/`Inc/`。**`simulator/` 已纳入全部钩子**(2026-10-11)——生成物只靠 `.gitignore` 挡,**不在 `exclude` 里写目录名**(教训见 `docs/代码规范/pre-commit使用说明.md` §5.5)
|
||||
- ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关
|
||||
- **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/`
|
||||
|
||||
@@ -167,6 +167,7 @@ docs/
|
||||
| [`嵌入式C语言代码规范_V1.0.md`](代码规范/嵌入式C语言代码规范_V1.0.md) | 命名/格式/头文件/注释规范(含机械检查项) |
|
||||
| [`pre-commit使用说明.md`](代码规范/pre-commit使用说明.md) | 提交门禁配置、检查范围与排除项 |
|
||||
| [`cppcheck使用说明.md`](代码规范/cppcheck使用说明.md) | cppcheck 配置与告警处置 |
|
||||
| [`../门禁说明/README.md`](门禁说明/README.md) | **每个提交门禁一篇文档**:查什么、拦下过什么真事、**限制与盲区**、豁免与基线口径 |
|
||||
|
||||
### 7.1b 文档规范(写/改文档前必读)
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# 门禁逐项说明(每种门禁一篇)
|
||||
|
||||
> 这一目录回答一个问题:**提交时那 39 个钩子到底在干什么、拦谁、放谁、什么时候会骗人。**
|
||||
> 每个门禁一篇,八段固定结构:查什么 / 为什么要它 / 触发面 / 报错怎么读 / 好处 /
|
||||
> **限制与盲区** / 豁免与基线 / 改门禁本身要注意什么。**最后一节是这套文档存在的理由**——
|
||||
> 一份只看"拦了什么"的门禁文档,会让人以为绿就是对的。
|
||||
|
||||
- 配置事实源:`.pre-commit-config.yaml`(39 个 `- id:`,2026-10-23 清点)。
|
||||
- 通用机制与整体口径:`docs/代码规范/pre-commit使用说明.md`(stages、范围排除、stash 陷阱等)。
|
||||
- 快速自查(不等提交):`& '.venv\Scripts\python.exe' scripts/ci_local.py`(L0 + L0b + L0.5)。
|
||||
|
||||
## 1. 三种拦截姿态(先分清这个,报错时才知道该干什么)
|
||||
|
||||
| 姿态 | 含义 | 有哪些 | 失败时该做什么 |
|
||||
|---|---|---|---|
|
||||
| **改写型** | 不报错,直接改你的文件 | `trailing-whitespace`、`end-of-file-fixer` | 重新 `git add` 被改的文件再提交(它改的是工作区,不是索引) |
|
||||
| **硬拦截** | 违反即失败,没有存量豁免 | `cppcheck`、`coding-standard`、`doc-links`、`no-mojibake`、`mem-budget`、`ui-golden`、`font-charset`、`lv-conf-sync`、`task-table`、`mem-numbers`、`registration-check`、`cubemx-survival`、`test-suite-switch`、16 个主机单测、官方 5 个、`gitlint` | 只能改代码或改文档;**不要**找豁免开关 |
|
||||
| **增量拦截** | 规则恒生效,但存量钉在基线/台账里,只许减不许增 | `layer-deps`(6 条)、`bsp-err`(38 条)、`id-check`(`scripts/id_known_backlog.json` 台账) | 报错先分清是**新增**还是**存量搬家**:`--verbose` 看归类,`--prune` 看已还清 |
|
||||
|
||||
⚠ 增量拦截最容易误会的一点:**绿灯不等于没问题**,只等于"没有比上次更差"。
|
||||
每个增量拦截门禁的基线数字都写进它自己的文档,读那篇时第一眼看的就是"现在在册多少条"。
|
||||
|
||||
## 2. 索引(39 个 hook → 23 篇)
|
||||
|
||||
| 门禁文档 | 覆盖的 hook id | 一句话它在查什么 | 改了哪类文件才会跑 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| [`bsp-err.md`](bsp-err.md) | `bsp-err` | BSP 的对外 API 面:错误通道类型、超时宏命名、必须写返回值、返回值不得裸丢弃 | `Drivers/BSP/**` 或门禁自身 | 已写 |
|
||||
| `layer-deps.md` | `layer-deps` | 分层方向:`Drivers/BSP/**` 不得 include `App/**` | `Drivers/BSP/**` | 待写 |
|
||||
| `id-check.md` | `id-check` | 实体编号的八项校验(未登记前缀、裸形、重复定义、孤儿、悬空引用…) | 几乎所有文本文件 | 待写 |
|
||||
| `doc-links.md` | `doc-links` | 文档里的路径引用是否存在;`--sections-strict` 另查章节号是否还存在 | 任意 `.md` | 待写 |
|
||||
| `cppcheck.md` | `cppcheck`、`test-cppcheck` | 静态分析(真机侧与 `test/` 侧两套范围与抑制项) | C/H 源文件 | 待写 |
|
||||
| `coding-standard.md` | `coding-standard` | 编码规范 V1.0 的可机械检查子集(命名、头文件保护宏、模块头注释块…) | C/H 源文件 | 待写 |
|
||||
| `no-mojibake.md` | `no-mojibake` | 禁 `U+FFFD`——中文被按错误编码重解码后不可逆损坏的痕迹 | 全部文本 | 待写 |
|
||||
| `mem-numbers.md` | `mem-numbers` | 文档里引用的 RAM/Flash 数字与最新 `.map` 报告是否一致(体积档) | `docs/内存管理/**`、相关脚本 | 待写 |
|
||||
| `mem-budget.md` | `mem-budget` | RAM 总量红线:`RW_IRAM1` / `RW_CCM` 超 92% 即拦 | 代码与配置改动 | 待写 |
|
||||
| `task-table.md` | `task-table` | README/AGENTS 里的任务表是否与 `Src/freertos.c` 一致 | `README.md`、`AGENTS.md`、`Src/freertos.c` | 待写 |
|
||||
| `registration-check.md` | `registration-check` | 新 `.c` 是否双端注册(Keil 工程 + 模拟器 CMake);UI 页面描述符表是否接线 | `.c`、`.uvprojx`、`CMakeLists.txt` | 待写 |
|
||||
| `lv-conf-sync.md` | `lv-conf-sync` | 两份 `lv_conf.h`(MCU / 模拟器)的特性子集是否一致 | 两份 lv_conf 或脚本 | 待写 |
|
||||
| `cubemx-survival.md` | `cubemx-survival` | CubeMX 重生成会不会把定制静默抹掉(L3 审计兜底) | `.ioc`、`Src/`、`Inc/` | 待写 |
|
||||
| `test-suite-switch.md` | `test-suite-switch` | 板级套件开关必须互斥;全注释 = 产品构建 | `test/board/test_config.h` | 待写 |
|
||||
| `font-charset.md` | `font-charset` | UI 用到的汉字是否都在子集字体里(缺字 = 白框) | `Drivers/BSP/UI/**`、字体脚本 | 待写 |
|
||||
| `ui-golden.md` | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 11 页黄金参考图比对 | UI 源码、模拟器 | 待写 |
|
||||
| `host-unit-tests.md` | 16 个 `*-unit-test` | 主机侧单测(同一机制 16 个实例):逻辑层在无硬件的 PC 上编译并跑断言 | 各自被测模块与其测试目录 | 待写 |
|
||||
| `trailing-whitespace.md` | `trailing-whitespace` | 行尾空白(改写型;CubeMX/Keil 生成文件豁免) | 全部 | 待写 |
|
||||
| `end-of-file-fixer.md` | `end-of-file-fixer` | 文件末尾必须恰好一个换行(改写型;同豁免) | 全部 | 待写 |
|
||||
| `check-added-large-files.md` | `check-added-large-files` | 别把大文件(日志/镜像/二进制)误提进仓库 | 全部 | 待写 |
|
||||
| `check-merge-conflict.md` | `check-merge-conflict` | 别把没解完的冲突标记(`<<<<<<<`)提上去 | 全部 | 待写 |
|
||||
| `check-yaml.md` | `check-yaml` | YAML 语法是否可解析(门禁配置自己坏了会静默不跑) | `.yml`/`.yaml` | 待写 |
|
||||
| `gitlint.md` | `gitlint` | 提交信息规则:标题长度、**正文每行 ≤80**、空行要求 | 提交信息(commit-msg 阶段) | 待写 |
|
||||
|
||||
## 3. 每篇文档的固定八段
|
||||
|
||||
1. **它在查什么**——逐条判据,能对着代码复核的粒度;
|
||||
2. **为什么要它**——它拦下过什么真事,没有它会怎样(没有真事的门禁要老实写"预防性");
|
||||
3. **触发面**——`files:` / `exclude:` / `pass_filenames` / 阶段,以及**看不见哪些文件**;
|
||||
4. **报错怎么读**——样例输出、怎么单独跑、常见"假报警"(含 stash 陷阱);
|
||||
5. **好处**——它把哪件事从"口头约定"变成了机器判定;
|
||||
6. **限制与盲区**——什么写法能骗过它、什么错它照不到、判据的启发式在哪里;
|
||||
7. **豁免与基线**——白名单形状、在册数量、还债口径(只减不增);
|
||||
8. **改门禁本身**——自测怎么做、改名会波及什么(例如基线键)、为什么不许拿基线洗假阳性。
|
||||
|
||||
## 4. 迁移状态
|
||||
|
||||
`docs/代码规范/pre-commit使用说明.md` §2 目前仍是多数门禁的详解所在地。本目录逐篇写完后,
|
||||
那节收敛为"通用机制 + 总览 + 指向本目录",同一事实只留一处(`docs/文档规范.md` 的要求)。
|
||||
上表「状态」列就是迁移进度;改完一篇就把该行改成"已写",并在 §2 对应小节留一行指针。
|
||||
@@ -0,0 +1,151 @@
|
||||
# `bsp-err` — BSP 错误码与超时命名约定
|
||||
|
||||
> 一句话:它管 **`Drivers/BSP/**` 对外 API 的四件事**——用什么类型表达失败、超时宏叫什么、
|
||||
> 有没有把返回值写进注释、调用点有没有真的去看返回值。**它不管错误码用得对不对**(见 §6)。
|
||||
|
||||
- 配置:`.pre-commit-config.yaml` 第 579 行起(`entry: python scripts/check_bsp_err.py`、
|
||||
`language: system`、`pass_filenames: false`)。
|
||||
- 规则脚本:`scripts/check_bsp_err.py`(纯标准库;文件头就是规则正文)。
|
||||
- 约定正文:`Drivers/BSP/bsp_err.h`(**header-only,无 `.c`** → 不占体积、不需双端注册)。
|
||||
- 背景与整改史:`docs/问题记录/项目审查与优化建议报告.md` §0.4 与 §9 的 S-5 行。
|
||||
|
||||
## 1. 它在查什么
|
||||
|
||||
四项检查,每项一个自描述标签(就是基线键的第二字段,键格式 `文件|标签|名字`):
|
||||
|
||||
| 标签 | 判据 | 拿它挡的具体写法 |
|
||||
|---|---|---|
|
||||
| `err-type` | 公开 API 的返回类型不得是 `HAL_StatusTypeDef`;也不得是 `bool`(除非是谓词形状) | 用 `bool` 表达成败——"为什么失败"整条丢掉;用 HAL 枚举——把 HAL 焊进调用方的 include 链 |
|
||||
| `timeout-name` | `#define` 出来的 `*TIMEOUT*` 宏不得取负值 | 名字叫 TIMEOUT 实际是返回码(本仓曾有 `GD5F_BUSY_TIMEOUT = -2`、`TPAFE5160_BUSY_TIMEOUT = -2`,已改) |
|
||||
| `return-doc` | 非 `void` 的公开 API,声明上方紧邻注释块里必须出现"返回值"(**匹配时忽略空格**) | 只写函数功能不写成败含义 |
|
||||
| `consume-result` | 返回错误码的函数,调用点必须**接住返回值**或**显式 `(void)` 丢弃**;裸调用即违规 | `HAL_SPI_Transmit(&hspi1, cmd, 3, T);` 后面照旧 `return OK` |
|
||||
|
||||
`consume-result` 的"哪些函数算返回错误码"是**自动取名单**,不手写(手写名单必然与代码脱节):
|
||||
|
||||
1. 头文件里返回 `bsp_err_t` 的公开 API(契约规定 `bsp_err_t` 就是错误码);
|
||||
2. 返回 `int`/`int32_t` 且其文档块出现"错误码 / 失败 / BSP_ERR / `_FAIL`"的函数;
|
||||
3. 已知返回 `HAL_StatusTypeDef` 的 HAL 族(`HAL_{SPI,I2C,UART}_{Transmit,Receive,TransmitReceive}`
|
||||
及其 `_DMA`/`_IT` 变体、`HAL_I2C_Mem_{Read,Write}`);
|
||||
4. 与 2 同一套启发式,再扫一遍 `.c` 里的 **static 定义**——只扫头文件等于只守跨编译单元的边界。
|
||||
|
||||
## 2. 为什么要它
|
||||
|
||||
- 这一层今天**六套约定并存**:0/−1、自研枚举 `CH395F_STATUS_*`、芯片寄存器原生码
|
||||
`CH395F_ERR_*`(与前一套同模块并存)、`HAL_StatusTypeDef`、`bool`,外加第三方 dhara 的
|
||||
`dhara_error_t` 与 FatFs 的 `FRESULT`。真正值钱的不是码名好看,而是"**能不能重试**"
|
||||
以前只写在个别注释里——现在 `BSP_ERR_CAN_RETRY(e)` 把它变成跨模块可判定。
|
||||
- **它上线当天就照出真窟窿**:`consume-result` 落地即暴露 37 条存量丢弃,其中一处是
|
||||
`bbt_rebuild` 重新打开 ECC 的那两行返回值全丢——SPI 一抖 ECC 就保持关闭,而 ECC 关闭时
|
||||
ECC 检查恒读"无错",整条链再没有任何信号。修法与运维判据见
|
||||
`docs/测试说明/存储维护与容量恢复.md` §4.3。
|
||||
- **没有工具会替我们做这件事**,两条实测负结果(本门禁存在的正当性全在这两条上):
|
||||
① `__attribute__((warn_unused_result))` 在 ARMCC v5 上**完全不生效**——属性挂上、调用点
|
||||
裸调不接返回值,构建仍 0 错 0 警告(在 `rs485_transmit` 上做过实验);
|
||||
② **cppcheck 也照不出来**——按本仓自己的 `--enable=warning,style,performance,portability`
|
||||
跑 `gd5f2gq5ue.c`,对 14 处丢弃 `HAL_SPI_*` 返回值、以及"未初始化局部量直接进 `P_FAIL`
|
||||
判定"**一条不报**(它假定被调函数会写出参,而失败路径根本不写)。
|
||||
|
||||
## 3. 触发面
|
||||
|
||||
```
|
||||
files: ^Drivers/BSP/|^scripts/check_bsp_err\.py$|^\.pre-commit-config\.yaml$
|
||||
pass_filenames: false # 恒扫全库:要按 git 清单建公开头索引,不能只看暂存的那几个文件
|
||||
```
|
||||
|
||||
- 扫描对象来自 `git ls-files Drivers/BSP`:`err-type`/`timeout-name`/`return-doc` 看 **`.h`**
|
||||
(对外 API 面),`consume-result` 扫 **`.c`**(33 个文件)。
|
||||
- **`lftpd/` 排除**:它的 `226/425/450/550` 是 FTP 协议码,不是内部错误码。
|
||||
- `net_*` BSD 兼容层**不纳入统一码**(用户裁定 2026-10-23,见 `Drivers/BSP/bsp_err.h` 边界纪律 3):
|
||||
调用方要的就是"返回字节数 + 每任务 errno 槽"那个形状,模拟器的 Winsock 后端照同一形状实现。
|
||||
⚠ 但**"不纳入码"不等于"可以丢弃返回值"**——`consume-result` 照样管 `net_close` 这类调用。
|
||||
- 因为 `files:` 覆盖到 `.pre-commit-config.yaml` 与脚本自身,改门禁必然触发它自己。
|
||||
|
||||
## 4. 报错怎么读
|
||||
|
||||
```
|
||||
$ python scripts/check_bsp_err.py
|
||||
BSP 约定:命中 38(在册 38 / 新增 0),基线共 38
|
||||
```
|
||||
|
||||
| 输出 | 意思 | 做什么 |
|
||||
|---|---|---|
|
||||
| `新增 N>0` + `[FAIL]` | 出现了基线之外的新违规 | 按提示改代码(返回 `bsp_err_t` / 时长宏带 `_MS` / 补「返回值:」/ 接住返回值或写 `(void)`) |
|
||||
| `[提醒] 有 N 条基线已还清` | 你把存量修掉了 | 跑 `--prune` 拿到清单,**从脚本的 `BASELINE` 里删掉那些行**,再跑一次确认 0 提醒 |
|
||||
| `命中 0` | 干净 | — |
|
||||
| 本地跑出**与暂存无关的假报警** | pre-commit 会 stash 未暂存改动,钩子看到的是"没有你改动"的树 | 先 `git add` 再跑钩子(本仓踩过:rs485 试点期被假报一批) |
|
||||
|
||||
三个只读旗标:`--verbose`(列出全部命中并标注在册/新增)、`--prune`(只报已消失、可删的基线)、
|
||||
`--emit-baseline`(打印当前全部键,重建基线用)。**脚本刻意没有 `--write`**:基线是人审后手改的,
|
||||
不提供"一条命令把现状洗成合法"的入口。退出码 0 通过 / 1 有新增 / 2 用法或扫描失败。
|
||||
|
||||
## 5. 好处
|
||||
|
||||
- 把"约定"从口头搬到机器上:新代码想沿用旧习惯,提交就被拦,不需要人在评审里记着。
|
||||
- 存量与新增分得开:基线让历史债务不阻塞日常工作,"只减不增 + `--prune` 验收"保证债只会变小。
|
||||
- 可审计:`--verbose` 就是债务清单;每条基线是 `文件|标签|名字`,能对着代码定位。
|
||||
- 零运行时成本:header-only 契约 + 纯标准库脚本,不进固件、不需要构建开关。
|
||||
|
||||
## 6. 限制与盲区(读这篇最重要的部分:绿灯不等于没问题)
|
||||
|
||||
1. **不管错误码用得对不对**。它只判通道类型、命名、有没有文档、有没有被消费。把
|
||||
`BSP_ERR_TIMEOUT` 用在"参数非法"上,它一声不响——那是语义问题,机器判不了。
|
||||
2. **范围只有 `Drivers/BSP`**。`App/**`、`simulator/**`、`Src/**` 不在面内;应用层的同类问题
|
||||
今天仍然只能靠评审。
|
||||
3. `consume-result` **只认单行整句的裸调用**(`foo(...);` 一行写完,行尾注释允许)。跨行写的调用
|
||||
——参数换行、或 `;` 在下一行——按正则形状测不到。这是判据的物理限度,不是漏配。
|
||||
4. **名单是启发式**:靠"返回类型 + 注释关键词"认函数。**注释里不写"错误码/失败"的函数会漏**
|
||||
(假阴性);反过来,把谓词写成 `foo_ok()` 这种不在豁免形状里的名字就会被判违规(假阳性,
|
||||
见 §7 的两次收窄)。**判据依赖命名形状,改名就能绕过规则**——这是所有基于正则的门禁共同的洞。
|
||||
5. HAL 族是**固定清单**(§1 第 3 条)。清单外的返回 `HAL_StatusTypeDef` 的函数(如 FLASH、SD 卡
|
||||
那批)不在名单里;将来用到就得多补一行正则,属于**人工维护的盲区**。
|
||||
6. `err-type`/`return-doc` 只看**头文件里的声明形状**:用宏包出来的 API、函数指针表里的 API、
|
||||
通过 `typedef` 起的函数别名,都不在它的视野里。
|
||||
7. `return-doc` 的判据是"紧邻注释块里出现『返回值』字样",**散文里出现这三个字也会放行**。
|
||||
这不是假想:本仓 `gd5f_write_enable`、`gd5f_read_status` 曾**长期"通过"**,原因竟是它们的
|
||||
*函数说明*里顺口写了"返回值"三个字,而不是真有一行返回值说明。**一条会因为散文里出现关键词
|
||||
而放行的规则,等于在奖励运气**——已改为忽略空格后匹配(规范 V1.0 允许列对齐写法「返 回 值:」,
|
||||
门禁不规定排版),但"关键词出现即放行"的性质没有变,只能靠评审看内容。
|
||||
8. **基线键带出现序号**(`gd5f_read_status#2`)。含义是"该文件里该函数的第 2 处丢弃",不是位置。
|
||||
于是:删掉靠前的丢弃会让后面的序号整体前移,`--prune` 只报"消失的键",**不会告诉你某处只是
|
||||
搬了家**;反过来,同函数新增一处丢弃会多出一个新序号 → 会被判新增(这点是好的,实测过)。
|
||||
9. **改标签名 = 改基线键**。今天把 `R1~R4` 换成现名时,必须脚本与 38 条基线一起换,中途跑
|
||||
`--prune` 会把改名读成"38 条全还清 + 38 条新账"。同理,今后任何标签改名都是**一次原子改动**,
|
||||
不能分两个提交。
|
||||
10. 不提供重试 helper,也不检查重试策略(有上限吗、喂看门狗了吗、跨就绪屏障等待了吗)——
|
||||
见 `Drivers/BSP/bsp_err.h` 边界纪律 5:一个通用 `while` 包装太容易被拿去包住无限等待。
|
||||
|
||||
## 7. 豁免与基线
|
||||
|
||||
- 基线在脚本里的 `BASELINE` 常量(不是外部台账文件),**只减不增**,没有 `--write`。
|
||||
- **当前在册 38 条**(2026-10-23):
|
||||
· `consume-result` 37 条 —— `gd5f2gq5ue.c` 28(**14 处丢弃 `HAL_SPI_*` 状态**:`HAL_SPI_Transmit`×11、
|
||||
`HAL_SPI_Receive`×3;**14 处丢弃自家原语**:`gd5f_write_enable`×4、`gd5f_read_status`×3、
|
||||
`gd5f_bbt_save`×1、`gd5f_block_erase`×1、`gd5f_page_read`×1、`gd5f_program_exec`×1、
|
||||
`gd5f_program_load`×1、`gd5f_read_from_cache`×1、`gd5f_wait_busy`×1)、`ui_net_sink.c` 6(全是
|
||||
`net_close`)、`rs485.c` 2(`rs485_receive_start`)、`nand_ftl.c` 1;
|
||||
· `err-type` 1 条 —— `bool ui_content_page_open()`:要改签名并处理 3 个调用点(三处今天全部
|
||||
丢弃返回值,即"按 ENTER 开页失败是静默的"),与"补注释"不是一档事,见报告变更记录。
|
||||
- 形状白名单(**写进规则,不塞基线**):
|
||||
· 谓词 `is_/has_/can_/should_/need_/valid_`——返回值是"提问"而不是"办成没有";
|
||||
· setter `(?:^|_)set_...$`——返回的整数是状态回声而不是调用成败(实跑误判出 `net_set_errno` 八处);
|
||||
· `mark_block_bad`——今天返回 `void`,预先豁免,防它将来加错误码返回时把整批调用点变成违规。
|
||||
- **原则:规则按真实反例收窄,不拿基线洗假阳性。**本仓第一版 `err-type` 误判 7 个正当谓词,
|
||||
改窄而不是入基线;`consume-result` 两次同类处理见上面两条。
|
||||
|
||||
## 8. 改这个门禁本身
|
||||
|
||||
- 改正则**先做正反例自测**:写一段一次性 python,把"应当命中"和"不应命中"的样例各列几条,
|
||||
`exec` 脚本头部拿到正则再断言(今天三次修正则都是这么验的,6 条样例级别即可)。
|
||||
- 假阳性只能改规则;**把它加进 `BASELINE` 是洗规则**,会把真问题一起豁免掉。
|
||||
- 加新的 must-use HAL 族:改 `HAL_STATUS_RE`,同时补一条正反例自测。
|
||||
- 改标签名:脚本与基线一次改完(§6 第 9 条),随后 `--emit-baseline` 复核数量不变。
|
||||
- 门禁自己的注释里不要写只有本次会话懂的代号;要引用规范就写`文件.md` + 紧邻编号。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-23 | 首版三项检查(错误通道 / 超时宏命名 / 必须写返回值)随 S-5 契约落地,存量基线 19 条 |
|
||||
| 2026-10-23 | 还掉 18 条缺「返回值:」;`return-doc` 改为忽略空格匹配(此前两条因散文里出现"返回值"而误判通过) |
|
||||
| 2026-10-23 | 新增第四项 `consume-result`,上线暴露 37 条存量丢弃;名单扩到 `.c` 内 static 定义;修两次判据(行尾注释漏判、setter 误判);同日修掉 `bbt_rebuild` 的 ECC 开关丢弃 |
|
||||
| 2026-10-23 | 四项由 `R1~R4` 改名为语义标签(该裸形在本仓与"专项风险"编号撞车,且编号门禁因同号不同义白名单不会报);基线 38 条随改名一起换 |
|
||||
Reference in New Issue
Block a user