diff --git a/AGENTS.md b/AGENTS.md index 4926271e..98d69e47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,7 +110,7 @@ - **`osKernelStart` 之前禁止任何 FreeRTOS/CMSIS-RTOS 创建类 API**(`osMessageQueueNew` 等会杀死启动,2026-09-20 实证根因)→ 内核前标准模式(纯寄存器/HAL 轮询 + **无锁静态缓冲** + 内核启动后**惰性创建**真队列)见 `docs/架构设计/项目启动顺序管理.md` - **就绪屏障位必须 `osFlagsNoClear`**:`osEventFlagsWait` **默认消费**所等位,就绪位被先消费掉 → `ftpTask` 的 `WaitAll` 永不成立 → **FTP 21 端口静默不监听**(无日志无报错)。命令位(`SYS_EVT_CFG_SAVE`/`NET_RECONFIG`)保持消费型 -- **自研代码禁用 C 库堆分配**(`malloc/calloc/realloc/strdup/free`):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL,而**模拟器(宿主 CRT 堆)完全正常、照不出来** → 只用 `pvPortMalloc/vPortFree`,或把分配收进平台后端 → `docs/内存管理/RAM使用分析与优化方案.md` +- **自研代码禁用 C 库堆分配**(`malloc/calloc/realloc/strdup/free`):**2026-10-23 起由提交门禁机器拦截**(`coding-standard` 的 CODE-RULE-20,唯一豁免 = 平台分配后端的宿主侧实现,见 `docs/门禁说明/coding-standard.md` §7):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL,而**模拟器(宿主 CRT 堆)完全正常、照不出来** → 只用 `pvPortMalloc/vPortFree`,或把分配收进平台后端 → `docs/内存管理/RAM使用分析与优化方案.md` - **内存预算紧张**:新增任何任务/缓冲/栈前先核算 RAM 余量;改栈或加大局部变量**前后各跑一次** `scripts/stack_report.py`(静态最坏链 + 串口 `[STACK]` 水位)与 `scripts/ram_report.py`(.map 静态占用);**总量红线已机械化**:`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%,子系统配额 / CCM 台账 / 趋势见 `docs/内存管理/RAM预算台账.md`(**每次 `@build` 后跑 `scripts/ram_baseline.py --update`**)→ `docs/内存管理/` - FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = **TIM7**(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,**外设中断优先级 ≥ 5** diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index ca4048e0..21b43b0d 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -245,7 +245,8 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env | `scripts/check_font_charset.py` | scripts/ | UI 字符集差集检查(复用 `tools/font_gen/` 提取与 cmap 解析规则,防缺字白框) | | `scripts/check_layer_deps.py` | scripts/ | 分层方向门禁(BSP 反向 include App 的增量拦截,存量基线只减不增) | | `scripts/check_bsp_err.py` | scripts/ | BSP 错误码与超时命名门禁(三条规则的增量拦截,存量 19 条基线只减不增;`--emit-baseline` 建基线) | -| `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(CODE-RULE-01~CODE-RULE-19 规则来源) | +| `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(CODE-RULE-01~CODE-RULE-20 规则来源;`CODE-RULE-20` 出处不在规范正文而在 +AGENTS.md 平台约束,见 `docs/门禁说明/coding-standard.md`) | | `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) | | `docs/文档规范.md` | docs/ | 文档体系规范(链接形态/命名/门禁要求) | | `docs/代码规范/pre-commit使用说明.md` | 本文档 | 部署与使用说明 | diff --git a/docs/代码规范/嵌入式C语言代码规范_V1.0.md b/docs/代码规范/嵌入式C语言代码规范_V1.0.md index 712a129a..ffc42d66 100644 --- a/docs/代码规范/嵌入式C语言代码规范_V1.0.md +++ b/docs/代码规范/嵌入式C语言代码规范_V1.0.md @@ -370,6 +370,15 @@ void function(void) { p_data = NULL; } ``` + +> ⚠ **本仓例外(2026-10-23 补,别照抄这段示例)**:示例里的 `malloc`/`free` 是**通用 C 写法**,本仓 +> **禁止**——工具链是 MicroLIB,其 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 只有 **512 B**, +> KB 级分配必然返回 NULL,而模拟器(宿主 CRT 堆)完全正常、**照不出来**(只有真机才炸)。这段示例只教 +> 三件事:**声明即初始化、检查 NULL、释放后置 NULL**。实际分配一律 `pvPortMalloc`/`vPortFree`,或把 +> 分配**收进平台后端**(MCU 端与宿主端各一份实现,例:`simulator/sim/cfg_file_store_sim.c` 的 +> `cfg_store_alloc`)。机器拦截 = 提交门禁 `coding-standard` 的 **CODE-RULE-20**,细则见 +> `docs/门禁说明/coding-standard.md`。 + ### 6.2 函数设计原则 1. 函数应遵循"单一职责原则",一个函数只做一件事 2. 函数长度不宜过长,一般不超过50行 diff --git a/docs/测试说明/主机单测覆盖率口径.md b/docs/测试说明/主机单测覆盖率口径.md index bc3bf7d7..14425eb3 100644 --- a/docs/测试说明/主机单测覆盖率口径.md +++ b/docs/测试说明/主机单测覆盖率口径.md @@ -196,5 +196,5 @@ | 日期 | 说明 | |---|---| -| 2026-10-23 | 新建。落地 `scripts/check_test_coverage.py` 与 `scripts/test_coverage_baseline.json`:首次实测 16 套全通过,并集 2552/2893 行(88.2%)、函数 193/207(93.2%),在测 19 文件 / 未触及 125 文件。同步销项 `docs/问题记录/项目审查与优化建议报告.md` §4.8 空白 ①(覆盖率口径)与 §5.5 第 5 条前半、§0.1 / §0.4 / §7 对应行;`malloc(` 正则那条仍开放。**同日补 §2.1**:用户问「为什么不用 GCC」,实测本机 WSL2 Ubuntu 有 gcc/gcov 15.2.0,16 套单测在 gcc 下**全部编过且全绿**、`gcov -b -c` 确实给出分支覆盖(`nand_ftl.c` 分支 41.40%)——但换编译器=换分母(`sha256.c` gcc 105 行 vs MSVC 114 行),故默认通道不变。 | +| 2026-10-23 | 新建。落地 `scripts/check_test_coverage.py` 与 `scripts/test_coverage_baseline.json`:首次实测 16 套全通过,并集 2552/2893 行(88.2%)、函数 193/207(93.2%),在测 19 文件 / 未触及 125 文件。同步销项 `docs/问题记录/项目审查与优化建议报告.md` §4.8 空白 ①(覆盖率口径)与 §5.5 第 5 条前半、§0.1 / §0.4 / §7 对应行;`malloc(` 正则那条**同日也落地**(`coding-standard` CODE-RULE-20,见 `docs/门禁说明/coding-standard.md`)。**同日补 §2.1**:用户问「为什么不用 GCC」,实测本机 WSL2 Ubuntu 有 gcc/gcov 15.2.0,16 套单测在 gcc 下**全部编过且全绿**、`gcov -b -c` 确实给出分支覆盖(`nand_ftl.c` 分支 41.40%)——但换编译器=换分母(`sha256.c` gcc 105 行 vs MSVC 114 行),故默认通道不变。 | | 2026-10-23 | **用户裁定:本仓只留 MSVC 一条覆盖率通道**,不建 gcc/WSL 第二条("后面可能引入更多问题")。实测数据与三条代价保留在 §2.1 当决策依据,§7 第 1 条把分支覆盖改记为**已接受缺口**,§8 下一步删掉"建通道"并写明重开该裁定的前提(必须先答"两份分母怎么对齐")。脚本与基线**不受影响**,仍是 `mscoverage` 一条路 | diff --git a/docs/门禁说明/coding-standard.md b/docs/门禁说明/coding-standard.md index 7f55e36a..c254671f 100644 --- a/docs/门禁说明/coding-standard.md +++ b/docs/门禁说明/coding-standard.md @@ -1,8 +1,10 @@ # `coding-standard` — 编码规范 V1.0 的可机械化子集 -> 一句话:它只查规范里**能用形状判定的 19 条**(命名、宏、注释块、括号、禁用函数、goto、 +> 一句话:它只查规范里**能用形状判定的 20 条**(命名、宏、注释块、括号、禁用函数、goto、 > 递归、可变参数……)。规范里"名字要达意、函数要短、职责要清"这些它一条都管不了。 -> 规范正文 = `docs/代码规范/嵌入式C语言代码规范_V1.0.md`。 +> 规范正文 = `docs/代码规范/嵌入式C语言代码规范_V1.0.md`。**第 20 条(禁 C 库堆分配)出处不在规范正文** +> —— 规范 §6.1 的示例自己就写着 `malloc`/`free`(通用 C 模板遗留,已同日在示例下加批注), +> 它的出处是平台约束:MicroLIB 的 C 堆只有 512 B。 - 配置:`entry: python scripts/check_coding_standard.py`、`pass_filenames: true`、 排除 `test/`、CubeMX 生成的 `Src/`、`Inc/`、第三方与 `Lib/`。 @@ -29,6 +31,7 @@ | CODE-RULE-17 | 非 static 函数必须在配对 `.h` 声明(无配对头则跳过) | 2.2 | | CODE-RULE-18 | 禁止 Yoda 条件(字面量必须在运算符右侧) | 6.3.1 | | CODE-RULE-19 | 函数定义左大括号与右括号同行 | 4.4.2 | +| CODE-RULE-20 | **禁止 C 库堆分配** `malloc/calloc/realloc/strdup/strndup/free/aligned_alloc` | 平台约束(**非规范 V1.0 条款**) | 规则号里 **CODE-RULE-14 是缺的**:条款作废后编号不回收,留着是为了让历史文档里的"14"不指向别的东西。 @@ -37,6 +40,11 @@ - 这 19 条如果靠人看,评审成本极高且必然漏;机械化之后评审可以把注意力留给机器管不了的部分。 - 有硬约束背景的两条特别值:**禁止递归**与**禁止可变参数**——本仓 `DBG_*` 每个调用点内联约 90 B Flash + 216 B 栈,栈与 Flash 都是红线资源;`g_` 前缀和指针初始化则是最容易埋雷的静态状态。 +- **CODE-RULE-20 有真事垫背**(2026-10-23 落地):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 + `Heap_Size EQU 0x200` = **512 B**,而它的踩坑方式是**模拟器全对、真机必炸**:配置引擎 2026-09-18 + 那次堆分配纠偏(4 处 KB 级 `malloc` 落在 C 堆上、真机返 NULL,见 + `docs/子系统设计/配置文件设计说明.md` v1.11 记录)与 lftpd 的 `malloc` 隐患(`docs/内存管理/RAM使用分析与优化方案.md` §4.2.1)都是 + **上线后才发现**的那类。宿主照不出来的缺陷,靠评审等于没管。 ## 3. 触发面 / 看不见什么 @@ -45,6 +53,10 @@ - **后果**:同一份规范对 `test/board/`(编进固件的 6300+ 行)不生效,对 CubeMX 生成区也不生效。 `test/` 的静态检查由 `test-cppcheck` 兜一半,形状规范没人管。 - 逐文件判定:跨文件的事实(声明在 A、定义在 B、调用在 C)它只看到眼前这一个文件。 +- CODE-RULE-20 跑在**掩码后**的文本上(注释与字符串里写 `malloc` 不报);换来的代价是: + 把分配藏进**宏展开**里它也看不见。 +- CODE-RULE-20 有一处**点名豁免**(`simulator/sim/cfg_file_store_sim.c` 的 `malloc`/`free`): + 那是平台分配后端的**宿主侧实现**(MCU 侧同名后端用 `pvPortMalloc`),不是漏网。 ## 4. 报错怎么读 @@ -72,6 +84,10 @@ Drivers/BSP/X/x.c:42 [CODE-RULE-09] 函数 foo_bar 缺少函数注释(应含 5. `test/` 与生成区整目录不查(§3),这三类目录的规范只靠人。 6. 逐文件 + 正则形状:把违规写进字符串字面量、注释、宏展开里就看不见;反过来说,宏里真正的 违规代码也可能看不见。 +7. CODE-RULE-20 **大小写敏感**:这是特性也是盲区——`pvPortMalloc`/`vPortFree` 因此不误报, + 但写成 `Malloc(` 就绕过了(同形后缀也一样:`buf_free(` 因词边界不成立而不算违规)。 +8. CODE-RULE-20 **只管堆分配函数名**:`alloca` 与变长数组(VLA)是**栈**上的坑,不归它; + 分配不释放(泄漏)、释放后继续用(UAF)也不归它——本条只判"别碰 C 堆"这一件事。 补录(判据细节,迁移自原手册): @@ -86,15 +102,25 @@ Drivers/BSP/X/x.c:42 [CODE-RULE-09] 函数 foo_bar 缺少函数注释(应含 - **没有基线**:硬拦截,新写的代码必须合规;因此**放宽任何一条都会立刻降低约束**,需谨慎。 - 豁免方式只有两种:脚本里的显式豁免名单(按名字形状,如 `HAL_` 回调)、或改代码。 +- CODE-RULE-20 的豁免是脚本里的 `HEAP_ALLOC_ALLOWLIST`,按**文件路径 + 具体函数名**双条件放行, + 现仅一条:`simulator/sim/cfg_file_store_sim.c` → `malloc`/`free`(平台分配后端的宿主侧实现, + 设计依据见 `docs/子系统设计/配置文件设计说明.md` 的 MCU 端/宿主端两段式后端)。 + ⚠ 刻意**不做目录级豁免**:下次谁要 `malloc` 就得往这里加一行,评审看得见。 ## 8. 改这个门禁本身 - 新增规则前先在**全库跑一次**统计违规数;有存量的规则要么先清存量,要么走"基线只减不增" 的模式(参考 `bsp-err`、`layer-deps`),不要直接挂上硬拦截卡住所有人。 - 规则作废**不回收编号**(见 CODE-RULE-14)。 +- 新增规则前先全库普查违规数(本节上一条已写)。CODE-RULE-20 落地时的普查结果:钩子范围内 + C/H 共 **151 个文件**,命中只有 `cfg_file_store_sim.c` 那两行(即 §7 那条豁免)→ 直接**硬拦截、 + 零基线**;`pre-commit run coding-standard --all-files` 实测 Passed。自证:临时样例里 + `malloc/calloc/free` 各报一处,`pvPortMalloc/vPortFree/buf_alloc(`、注释、字符串 全部不误报。 ## 9. 变更记录 | 日期 | 变更 | |---|---| | 2026-10-23 | 首篇逐项文档(19 条规则逐条列出,含 CODE-RULE-14 缺号说明) | +| 2026-10-23 | 新增 **CODE-RULE-20 禁 C 库堆分配**(硬拦截、零基线;唯一豁免 = 平台分配后端的 + 宿主侧实现文件)。出处是 AGENTS.md 平台约束而非规范 V1.0;规范 §6.1 示例已同日加"本仓禁止"批注 | diff --git a/docs/门禁说明/mem-budget.md b/docs/门禁说明/mem-budget.md index d99aeb56..4ff03987 100644 --- a/docs/门禁说明/mem-budget.md +++ b/docs/门禁说明/mem-budget.md @@ -34,7 +34,7 @@ ## 3. 触发面 / 看不见什么 - 只看 `RW_IRAM1` 与 `RW_CCM` 两个**静态区域**;不看:栈运行时水位、FreeRTOS 堆运行时余量、 - 堆碎片、MicroLIB 的 512 B C 堆(那条纪律是"自研代码禁用 `malloc/free`",靠 `AGENTS.md` 铁律与评审)。 + 堆碎片、MicroLIB 的 512 B C 堆(那条纪律是"自研代码禁用 `malloc/free`",**2026-10-23 起由 `coding-standard` 的 CODE-RULE-20 机器拦截**(见 `coding-standard.md` §1/§7),不再只靠评审)。 - `.map` 缺失即跳过 → 无构建的机器(含远端 CI)这一门不产生信息。 ## 4. 报错怎么读 diff --git a/docs/问题记录/项目审查与优化建议报告.md b/docs/问题记录/项目审查与优化建议报告.md index fcff79e4..979878d0 100644 --- a/docs/问题记录/项目审查与优化建议报告.md +++ b/docs/问题记录/项目审查与优化建议报告.md @@ -19,7 +19,7 @@ | 文档与治理 | 🟢 良好 | 39 个提交钩子 + 16 套主机单测;工程 md 91 份;`doc-links` 全库 0 问题 | 新增 `编号门禁增量拦截` 与内存快照回填常态化 | | 内存与 Flash | 🟡 可控但已换位置 | SRAM1 **57.4%**(余 55,808 B)、Flash **22.1%**、**CCM 87.9%(余 7,904 B)** | **红线从 SRAM1 迁到 CCM**(堆 2026-10-11 迁 CCM + 池 40→16 KB 后 SRAM1 已宽松) | | 可靠性与自恢复 | 🟢 良好 | 4 个 Fault 全采集现场;IWDG 五面 `required_mask`;复位原因与 `[STACK]` 水位常开 | 台架复跑持续 PASS(今日真机 708 ms 起网、无 `[ERR]`/Fault) | -| 测试与验证 | 🟡 半办 | 16 套主机单测 + `ui-golden`/`font-charset` 入门禁;TC 双向追溯 593∶271 交集 265;**覆盖率口径已落地**(`../测试说明/主机单测覆盖率口径.md`:16 套并集 2552/2893 行) | `coding-standard` **仍无 `malloc` 正则**;覆盖率**未挂 pre-commit**(口径文档 §8 三条理由)、**没有分支覆盖** | +| 测试与验证 | 🟡 半办 | 16 套主机单测 + `ui-golden`/`font-charset` 入门禁;TC 双向追溯 593∶271 交集 265; **§5.5 两条补强已全部落地**(2026-10-23:覆盖率口径 `../测试说明/主机单测覆盖率口径.md` + `malloc(` 正则 `coding-standard` CODE-RULE-20) | 覆盖率**未挂 pre-commit**(口径文档 §8 三条理由);**分支覆盖已裁定不做** (§2.1 决策记录)→ 现在真正的半办是**覆盖面**:首登照出 **125 个生产源文件从未进过任何主机单测** | | UI 标准化 | 🟢 基本落地 | `ui_tokens.h`(L0 令牌)+ `ui_page_registry.c`(描述符注册表)+ 7 个内容页 + 两枚 UI 钩子 | **U1/U3/U4 从"未做"变为已落地**;U2 只有 `ui_w_records` 一个组件 | ### 0.3 主线建议(2026-10-22) @@ -30,8 +30,10 @@ 92% 钩子会先报警。SRAM1 与 Flash 都已不是卡点。 3. **结构性内存优化整簇暂缓**(R-2 后半 / R-3 / R-4 / R-6,用户裁定 2026-10-15),治理面常开; 重开条件见 §5.3。今天又收口一项:**F-4 判定不做、F-6 已落地、F-5 降级观察**(§5.4)。 -4. **验证的下一步不是加用例而是补面**:覆盖率口径 + `malloc(` 门禁正则(§5.5 第 5 条),把"读代码 - 才发现"的缺陷类型(N-12/N-13 那类)变成机器拦截。 +4. **补面两条已全部落地(2026-10-23)**:覆盖率口径(`../测试说明/主机单测覆盖率口径.md`)+ + 堆禁令机器拦截(`coding-standard` **CODE-RULE-20**,§5.5 第 5 条)。**剩下的量在覆盖面**:覆盖率首登 + 照出 **125 个生产源文件从未进过任何主机单测**(约 11,839 行,`net_socket.c` 1,251 行最大)——给它们建桩 + 比把在测的 19 个文件从 88% 推到 95% 值钱得多(N-12/N-13 那类"读代码才发现"的缺陷就是这么照出来的)。 5. **仓库体积重心在外部资料**(`docs/芯片手册` 717 文件 / 39.5 MB,占 docs 的 87%;`.git` 248 MB), 见 §3.3 D-1 与登记簿 N-29——已决策暂不处理,但**推送/对外协作前必须重评**。 @@ -67,7 +69,9 @@ B0h 验 `ECC_EN`**——原先那两行的注释自己写着"否则后续 I/O | 存储/日志 | P2-10 ❌(N-25) | 卷锁现有 5 个使用者(配置原子写 / 日志 / SOE / lftpd_io / defaultTask),容量轮转删除与 UI 读并发仍未全覆盖 | 登记簿 S-5 面(卷锁覆盖面) | | 仓库 | D-1 ❌(N-29) | jpg 707 个 / 37.3 MB;`docs` 45.3 MB 里芯片手册占 39.5 MB;`.git` **248 MB** | 降采样 / 拆 submodule / LFS 三选一 + 历史重写;已决策暂不做 | | UI | U2 残余(U 簇) | 组件库只有 `ui_w_records.c/h` 一个;其余仍是页内自绘 | 按产品需求决定要不要继续抽组件 | -| 测试 | ~~覆盖率~~ ✅ **2026-10-23 落地** / `malloc` 门禁 ❌ | 覆盖率:`scripts/check_test_coverage.py` + 基线 `scripts/test_coverage_baseline.json`(口径与**盲区**见 `../测试说明/主机单测覆盖率口径.md`)——首登 16 套并集 2552/2893 行(88.2%)、**未触及 125 个生产源文件 / 约 11.8k 行**;`check_coding_standard.py` 仍无 `malloc(` 正则 | 剩 `malloc(` 正则 + 覆盖率挂钩子(先定「缺 VS 组件时 skip 还是 FAIL」);见 §5.5 与 §4.8 | +| ~~测试~~ ✅ **两条全部闭环 2026-10-23**(覆盖率口径 + 堆禁令 CODE-RULE-20) | 覆盖率:`scripts/check_test_coverage.py` + 基线 `scripts/test_coverage_baseline.json`(口径与**盲区**见 `../测试说明/主机单测覆盖率口径.md`)——首登 16 套并集 2552/2893 行(88.2%)、**未触及 125 个生产源文件 / 约 11.8k 行**。堆禁令:`check_coding_standard.py` **CODE-RULE-20**(硬拦截、零基线;唯一豁免 = `simulator/sim/cfg_file_store_sim.c` +| 的平台分配后端宿主侧实现,见 `../门禁说明/coding-standard.md` §7) | 无(残余三项**已归口或已裁定**:覆盖率挂 +| pre-commit 的前置写在口径文档 §8;分支覆盖**不做**(§2.1 决策);扩大覆盖面按 §0.3 第 4 条) | | CI | T-7 ❌ | 远程 CI 只跑本机一台 runner,且不跑 `@build` | 保持现状即可;扩机器前重读 `docs/CI/README.md` | ## 1. 审查方法与证据基线 @@ -226,8 +230,15 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 - 主机侧 **16 套**单测(提交门禁实跑);板级 `test/board/` 19 文件、套件开关互斥受 `test-suite-switch` 管 - TC 双向追溯:代码侧 593 号 / 文档侧 271 号 / 交集 265,差异 13 处(当前仅告警,未升门禁) -- **覆盖率口径已落地(2026-10-23)**:`scripts/check_test_coverage.py`(mscoverage 动态插桩 + Cobertura 并集) - 这条铁律目前只靠人读 + 模拟器照不出来(MicroLIB C 堆仅 512 B) +- **两处空白已全部闭合(2026-10-23)**:① 覆盖率口径 → `scripts/check_test_coverage.py`(mscoverage 动态 + 插桩 + Cobertura 并集)+ 基线 `scripts/test_coverage_baseline.json`;口径 / 工具链实测 / **九条盲区**见 + `../测试说明/主机单测覆盖率口径.md`(首登 2552/2893 行、19 文件在测、**未触及 125 文件**)。 + ② C 库堆禁令 → `check_coding_standard.py` **CODE-RULE-20**(拦 `malloc/calloc/realloc/strdup/strndup/ + free/aligned_alloc(`;硬拦截、零基线;唯一豁免 = 平台分配后端的宿主侧实现 + `simulator/sim/cfg_file_store_sim.c`,见 `../门禁说明/coding-standard.md` §7)。禁令本身的事实源仍是 + AGENTS.md 平台约束与 `../内存管理/RAM使用分析与优化方案.md` §4.2.1——**为什么非得机器拦**:MicroLIB + 的 C 堆只有 512 B,而**模拟器(宿主 CRT 堆)完全正常、照不出来**——配置引擎 2026-09-18 的堆分配纠偏 + (4 处 KB 级 `malloc` 落错堆)与 lftpd 的 `malloc` 隐患都是**先上线才发现**的那类 - 破坏性套件(`ENABLE_DESTRUCTIVE_TESTS`)有**持久代价**(NAND 坏块记账不可逆),默认关闭是正确设计 ## 5. 内存与 Flash(2026-10-22 实测收口) @@ -285,7 +296,8 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 2. `check_mem_numbers.py --scan/--write`:文档里的内存数字一律由 `.map` 反查,禁止手填 3. 改栈 / 加大局部变量前后各跑一次 `stack_report.py`(静态 × 真机 `[STACK]`) 4. 子系统配额与 CCM 台账 → `../内存管理/RAM预算台账.md`;趋势 → `scripts/ram_baseline.tsv` -5. **仍未落地的一条补强**:`check_coding_standard.py` 加 `malloc(` 正则(把 C 库堆禁令变成机器拦截)。 +5. **两条补强已全部落地(2026-10-23)**:`malloc(` 正则 = `check_coding_standard.py` **CODE-RULE-20** + (硬拦截、零基线,判据与豁免见 `../门禁说明/coding-standard.md` §1/§7)。 另一条「覆盖率口径」✅ **已落地 2026-10-23** → `../测试说明/主机单测覆盖率口径.md`(口径 / 基线 / 盲区 一处写完,此处不复述)。⚠ 它落地的是**行与函数**覆盖,**分支覆盖本机拿不到**(无 gcc,MSVC 14.51 已无 `/coverage`)——「覆盖到哪些分支」这一半**已裁定不补**(不为它引入第二套工具链,见 @@ -334,7 +346,7 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止 |---|---|---| | ~~W3 预算与内存~~ ⏸ | R-9 ✅ / R-2 后半、R-3、R-4、R-6 暂缓 | 暂缓期间无验收动作,观测照常 | | W4 结构债 | N-14 ⏸、F-5 🟡、S-1/S-2/S-4/S-5、N-25 | `--strict` 已默认;S-5 契约/门禁已落地并收编三模块,`consume-result` 已把"丢弃返回值"纳入机械检查(基线 19→1→38;剩 GD5F 码集收编、C4 快照、37 条丢弃点、标坏策略) | -| W5 验证常态 | ~~覆盖率~~ ✅ 2026-10-23、`malloc` 正则、TC 差异 13 处升门禁 | 逢窗口顺手做 | +| W5 验证常态 | ~~覆盖率~~ ✅、~~`malloc` 正则~~ ✅(均 2026-10-23)、TC 差异 13 处升门禁 | 剩 TC 一条 | | W6 UI 扩展 | U2 残余、U5 页量 | 随产品需求,不再单列专项 | | W7 仓库决策 | D-1(N-29) | 已决策暂不处理;对外交付前重评 | @@ -451,10 +463,25 @@ admin/admin(SHA-256 `pwd_hash`)→ 该脚本**必然登录失败**;它是 | | 数字联动照做:两份 `data/` 快照重跑、`ram_baseline --update/--write`、`check_mem_numbers --write`、FLASH 与 任务栈两张配图重算重生成(逐文本复查越界 = 0)、本文 §1.3/§5.1/§5.4 与部署文档 §3.3 同步;§0.4 的 S-2 行按 「完成即销项」改写、§9 拆出 S-2 行标 ✅。 | +| **2026-10-23** | **执行 §5.5 第 5 条后半:C 库堆禁令变成机器拦截**(§4.8 空白 ②,CODE-RULE-20) | 按本文件 §8 的既有纪律**先全库普查再动手**:`coding-standard` 钩子范围内 C/H 共 **151 个文件**,`malloc` 族只命中 +| | | `simulator/sim/cfg_file_store_sim.c` 两行(`cfg_store_alloc`/`cfg_store_free` = 平台分配后端的**宿主侧实现**, +| | | 设计依据写在 `../子系统设计/配置文件设计说明.md` 的"MCU 端 = pvPortMalloc、模拟器端 = 宿主 CRT")——**不是漏网**, +| | | 所以零基线、直接硬拦截;豁免走脚本里的 `HEAP_ALLOC_ALLOWLIST`(**文件路径 + 函数名**双条件),刻意不做目录级豁免: +| | | 下次谁要碰 C 堆就得往那张表加一行,评审看得见。判据 = 掩码后文本上匹配 `\b(malloc|calloc|realloc|strdup|strndup| +| | | free|aligned_alloc)\s*(\(`:大小写敏感 ⇒ `pvPortMalloc`/`vPortFree` 不误报;词边界 ⇒ `dhara_nand_is_free(`、`buf_free(` +| | | 不误报;注释与字符串里写 `malloc` 不报。**自证**:临时样例三处(`malloc/calloc/free`)各报一次、五种陷阱形态全部不报; +| | | `pre-commit run coding-standard --all-files` 实测 **Passed**(= 全库零存量)。 +| | | **顺带消掉一处自相矛盾**:规范 V1.0 §6.1 的示例自己写着 `malloc`/`free`(通用 C 模板遗留),已在其下方加 +| | | 「本仓例外,别照抄」批注并把事实源指向 AGENTS.md 平台约束;`docs/门禁说明/mem-budget.md` 里"这条纪律只靠评审" +| | | 的旧说法同步更正。AGENTS.md 平台约束条加"已机器拦截"一句。**本轮零 `.c/.h` 改动**(只动门禁脚本与文档), +| | | 未跑 `@build`、未占 build/bench 锁;详解归 `../门禁说明/coding-standard.md`(本处不复述判据) | | **2026-10-23** | **执行 §5.5 第 5 条前半:覆盖率口径落地并销项**(§4.8 空白 ①) | 新增 `scripts/check_test_coverage.py`(纯标准库)+ 基线台账 `scripts/test_coverage_baseline.json`。**工具链是实测出来的、不是查到的**:本机 PATH 上 gcc/gcov/clang/llvm-cov/lcov/gcovr **全无**;`cl /coverage`、`/fsanitize=coverage` 在 MSVC 14.51 已被砍——**只报 `warning D9002 忽略未知选项`,然后照常编译成功**(拿它做覆盖率=静默假数据,比报错更坏);`Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe`(17.0)的 `collect`/`analyze`/`instrument` 一律只打印「用法: CodeCoverage 」。最终走 **mscoverage(`Microsoft.CodeCoverage.Console.exe` 18.x)`collect -f cobertura`** 的**加载期动态插桩**,并实测它**原样透传子进程退出码**(子进程 `return 3` → `collect` 也返回 3)→ 于是「插桩运行下仍必须 PASS」可以当硬条件用。 | | | | **口径**:只算 `App/**` 与 `Drivers/BSP/**` 的行/函数、多套单测取**并集**、生成物(文件头写「自动生成 / 请勿手改」)不进分母、分母一律来自插桩报告**不猜行数**;配置零复制——脚本 import 各运行器的 `TestBuild`、开关取 `unit_test_harness._BASE_FLAGS`,只追加 `/Zi /Od` 与 `/link /INCREMENTAL:NO`,发现规则与 L0.5 同一条 `test/host/**/run_unit_test.py`,**不会漂出第二套口径**。 | | | | **首登数字(2026-10-23)**:16 套全通过,并集 2552/2893 行(88.2%)、193/207 函数(93.2%)、19 个生产文件在测,**未触及 125 个生产源文件 / 约 11,839 行**(`net_socket.c` 1,251 行最大)——最后一栏才是这套口径真正照出来的东西。**门禁自证**:人为抬高基线后,行 / 函数 / 总计三条回退路径都如期报 REGRESS 且退出码 1;`--module` 子集运行不会假报总计回退(首版就会,已修)。**刻意未挂 pre-commit**:三条理由与可直接粘的 YAML 片段写在 `../测试说明/主机单测覆盖率口径.md` §8,接线者照 §8 落地即可。 | -| | | **仍开放**:§5.5 第 5 条的另一半(`check_coding_standard.py` 加 `malloc(` 正则)与**分支覆盖**。分支覆盖**不是拿不到**:同日被用户问住后复测,本机 WSL2 Ubuntu 有 gcc/gcov 15.2.0,16 套单测在 gcc 下**全部编过且全绿**、`gcov -b -c` 出分支数据(`nand_ftl.c` 分支至少走过一次 41.40%);但换编译器就是换分母(`sha256.c` gcc 105 行 vs MSVC 114 行)→ **用户裁定只保留 MSVC 一条通道**(多一套工具链=多一套分母 +多一本台账 + 多一个"这台机器有没有 WSL"的未决项),分支覆盖**记为已接受缺口**、不是待办。实测表与决策依据见 `../测试说明/主机单测覆盖率口径.md` §2.1。本轮只加 `.py`/`.json`/`.md`,无 `.c/.h` 改动,不重跑 `@build`;文档面动 §0.1 / §0.4 / §4.8 / §5.5 / §7 与 `docs/README.md` 索引各一处。 | +| | | **同日另一半也落地**(§4.8 空白 ②):`check_coding_standard.py` 加 **CODE-RULE-20** 禁 C 库堆分配, +| | | 硬拦截零基线(全库普查 151 个 C/H 只有 `cfg_file_store_sim.c` 两行命中 = 平台后端的宿主侧实现, +| | | 点名进 `HEAP_ALLOC_ALLOWLIST`,刻意不做目录级豁免);规范 §6.1 示例自己写着 `malloc/free` 的 +| | | 自相矛盾也同日加了"本仓禁止"批注。**只剩分支覆盖**一条。分支覆盖**不是拿不到**:同日被用户问住后复测,本机 WSL2 Ubuntu 有 gcc/gcov 15.2.0,16 套单测在 gcc 下**全部编过且全绿**、`gcov -b -c` 出分支数据(`nand_ftl.c` 分支至少走过一次 41.40%);但换编译器就是换分母(`sha256.c` gcc 105 行 vs MSVC 114 行)→ **用户裁定只保留 MSVC 一条通道**(多一套工具链=多一套分母 +多一本台账 + 多一个"这台机器有没有 WSL"的未决项),分支覆盖**记为已接受缺口**、不是待办。实测表与决策依据见 `../测试说明/主机单测覆盖率口径.md` §2.1。本轮只加 `.py`/`.json`/`.md`,无 `.c/.h` 改动,不重跑 `@build`;文档面动 §0.1 / §0.4 / §4.8 / §5.5 / §7 与 `docs/README.md` 索引各一处。 | | **2026-10-23** | **术语迁移**:`bsp-err` 的四项检查从 `R1/R2/R3/R4` 改成语义标签 | 用户一句「R4 | | 到底是什么」问出来的。这四个号**是我自己造的**,而 `R1~R8` 在本仓是"专项风险"的裸形编号 | | (`id_registry.json` 已登记那一族,保护规划 `R1~R8`、看门狗 `R1~R7` 的风险表都在用),`R-2` @@ -664,4 +691,8 @@ USER CODE 区内只留「读 RCC_CSR → 写 RMVF 清除」两句、顺序约束 | **R-9(`LV_MEM_SIZE` 40→16 KB,登记簿 N-27 原 W3 的第一步,2026-10-15 已执行;该簇其余部分同日整簇暂缓)** | 取值依据与再降 8 KB 的前置写在 `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 的池注释;实测水位 → `../内存管理/RAM使用分析与优化方案.md` §2.6.2 与 §阶段 1.1(已执行);解锁链第 ① 步 ✅(后续步骤暂缓)→ `../内存管理/RAM预算台账.md` §3;池容量口径 → `../UI设计/UI_设计架构.md` §1.2、`../UI设计/UI_界面制作流程.md` C4 | | **N-12 / N-13**(dhara 移植层 `is_free` 精度 + HAL 越界守卫,登记簿 **N-21**,2026-10-15 已销项) | `../架构设计/STM32F4-Base存储架构说明.md` §5.3(移植层两条硬约束:整页精确的原因链 + 错误码取舍与"为什么不会静默清卷")+ `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 的 `ftl_page_in_range` / `ftl_block_in_range` / `dhara_nand_is_free` 注释 + 主机侧单测 `test/host/nand_port/`(含"旧算法只看页首 64 B"的对照用例,防止将来被改回去) | | **N-16**(日志读写放大,登记簿 **N-24**,2026-10-15 已销项) | `../子系统设计/运行与自检记录设计说明.md` §4.4(写侧批量合并 + **掉电语义不变**的论证 + 失败回投不丢条目 + 容量轮转只在慢路径)与 §4.5(读侧 8 条窗口 + 写者代数失效 + API 不变)+ `App/log/sys_log_mcu.c` 的 `s_format_rec` / `s_append_rec` / `s_append_many` / `s_read_raw` 注释 | -| **§4.8 空白 ①「无覆盖率口径」+ §5.5 第 5 条前半(2026-10-23 销项;§5.5 后半的 `malloc(` 正则仍开放)** | `../测试说明/主机单测覆盖率口径.md`(口径 / 工具链实测 / 统计面 / 门禁姿态 / **九条限制与盲区** / 未挂钩子的三条理由与待粘 YAML)+ `scripts/check_test_coverage.py`(`--list` / `--module` / `--update-baseline` / `--md` / `--json`)+ 基线台账 `scripts/test_coverage_baseline.json`(首登 2552/2893 行、未触及 125 文件) | +| **§4.8 两处空白(①无覆盖率口径 ②无 `malloc(` 正则)+ §5.5 第 5 条整条(2026-10-23 全部销项)** | `../测试说明/主机单测覆盖率口径.md`(口径 / 工具链实测 / 统计面 / 门禁姿态 / **九条限制与盲区** / 未挂钩子的三条理由与待粘 YAML)+ `scripts/check_test_coverage.py`(`--list` / `--module` / `--update-baseline` / `--md` / `--json`)+ 基线台账 `scripts/test_coverage_baseline.json`(首登 2552/2893 行、未触及 125 文件)。 +空白 ② → `docs/门禁说明/coding-standard.md`(CODE-RULE-20 判据 / 唯一豁免 / 盲区 / 落地前普查数)+ +`scripts/check_coding_standard.py`;禁令本身的事实源仍是 AGENTS.md 平台约束与 +`../内存管理/RAM使用分析与优化方案.md` §4.2.1;另 `../代码规范/嵌入式C语言代码规范_V1.0.md` §6.1 的示例 + 自己写着 `malloc/free`(通用 C 模板遗留),已同日在示例下加"本仓禁止"批注 | diff --git a/scripts/README.md b/scripts/README.md index 723ac11a..703e6cdf 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -42,6 +42,7 @@ | 脚本 / 文件 | 作用 | |---|---| | `ci_local.py` | **本地跑与 CI 等价的门禁**(L0 pre-commit + L0b 提交信息 + L0.5 主机侧单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 | +| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 16 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;**不在 pre-commit 里**,入口是 `ci_local.py --coverage`(口径见 `docs/测试说明/主机单测覆盖率口径.md`) | | `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) | | `ci_find_python.ps1` | CI 侧 Python 探测(候选顺序须与 `ci_local.py` 同序同步,否则 CI 与本地判定会分叉) | | `ci_runner_bootstrap.ps1` | **本机 runner 运维脚本**(体检 `-Status` / 干跑 `-DryRun` / 部署 `-SetEnv -StartNow`):装"等 Gitea 就绪 + 崩溃自拉起"的看门狗 + 开机计划任务 + 机器级 `DTU_*` | diff --git a/scripts/check_coding_standard.py b/scripts/check_coding_standard.py index 62b6b415..ec67f565 100644 --- a/scripts/check_coding_standard.py +++ b/scripts/check_coding_standard.py @@ -23,6 +23,7 @@ C 语言代码规范检查(《嵌入式C语言代码规范_V1.0》可机械化 CODE-RULE-17 (2.2) 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过) CODE-RULE-18 (6.3.1) 禁止 Yoda 条件(字面量/常量必须在运算符右侧) CODE-RULE-19 (4.4.2) 函数定义左大括号必须与右括号同行 + CODE-RULE-20 (平台约束) 禁止 C 库堆分配 malloc/calloc/realloc/strdup/free 语义类规则(函数长度、单一职责、错误处理等)无法机械可靠判定,不在此脚本范围。 """ @@ -41,6 +42,13 @@ C_KEYWORDS = {'if', 'for', 'while', 'switch', 'do', 'else', 'return', NAME_PAREN_RE = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\s*\(') +# CODE-RULE-20 的例外:平台分配后端的**宿主侧实现**(AGENTS.md「只用 pvPortMalloc/vPortFree, +# 或把分配收进平台后端」说的就是这种收口点)。只点名到文件 + 只放行这两个函数名, +# 不做目录级豁免——新增一个想 malloc 的文件必须显式加进这里,评审时看得见。 +HEAP_ALLOC_ALLOWLIST = { + 'simulator/sim/cfg_file_store_sim.c': frozenset({'malloc', 'free'}), +} + def _mask(text): """注释与字符串字面量替换为等长空白(保留换行,行号不变),防止误报。 @@ -281,9 +289,29 @@ def check_file(path): 'Yoda 条件(常量应在右): %s' % ' '.join(m.group(0).split()))) # CODE-RULE-19 (4.4.2) 函数定义左大括号必须与右括号同行 - for name, ln, _body, _pre, bl, pl in defs: - if bl != pl: - v.append((ln, 'CODE-RULE-19 4.4.2', '函数定义左大括号须与右括号同行: %s' % name)) + # CODE-RULE-20 (平台约束,2026-10-23) 禁止 C 库堆分配 + # 出处**不在规范 V1.0**(其 §6.1 示例本身就写着 malloc/free,属通用 C 模板遗留,已同日在示例 + # 下方加"本仓禁止"批注),而在 AGENTS.md「平台约束」+ docs/内存管理/RAM使用分析与优化方案.md: + # MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 限定为 **512 B**,KB 级分配 + # 必然返回 NULL,而**模拟器(宿主 CRT 堆)完全正常、照不出来** —— 典型"只有真机才炸",必须机器拦。 + # 判据细节(三条都是踩过坑之后定的): + # - 大小写敏感:pvPortMalloc / vPortFree(唯一允许的配对)不会中招; + # - 词边界 \b:dhara_nand_is_free(、buf_alloc( 这类"同形后缀"的自有函数名不会误报; + # - 跑在 masked 上:注释与字符串字面量里提到 malloc 不报(规范示例就是这种命中)。 + # 覆盖名比 AGENTS 列的五枚多带上 strndup / aligned_alloc —— 同族同类,漏一个就等于留个后门。 + norm = path.replace('\\', '/') + allow = frozenset() + for suffix, names in HEAP_ALLOC_ALLOWLIST.items(): + if norm == suffix or norm.endswith('/' + suffix): + allow = names + break + for m in re.finditer(r'\b(malloc|calloc|realloc|strdup|strndup|free|aligned_alloc)\s*\(', masked): + name = m.group(1) + if name in allow: + continue + v.append((_line(masked, m.start()), 'CODE-RULE-20 平台约束', + '禁止 C 库堆分配 %s()(MicroLIB C 堆仅 512 B;用 pvPortMalloc/vPortFree,' + '或把分配收进平台后端)' % name)) return v