From 2b66f96d01810784aa41461ab772cd9db6a89a8b Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 1 Sep 2026 15:41:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=A3=E7=A0=81=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E6=A3=80=E6=9F=A5=E9=92=A9=E5=AD=90=EF=BC=88=E8=A7=84?= =?UTF-8?q?=E8=8C=83V1.0=20=E5=8F=AF=E6=9C=BA=E6=A2=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E9=A1=B9=EF=BC=89=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=AD=98=E9=87=8F?= =?UTF-8?q?=E8=BF=9D=E8=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 scripts/check_coding_standard.py + coding-standard 钩子(S1~S9: 头文件保护宏、文件头注释、宏大写、_t 后缀、switch default、 禁 strcpy/sprintf/goto/Tab、函数注释) 修复存量违规:NET_* 字节序宏大写化、net_fd_set 等类型加 _t 后缀、 net_inet_ntoa 的 sprintf 改 snprintf、补 4 处函数注释与 ui_task.c 文件头 docs/代码规范/pre-commit使用说明.md:新增 2.8 节,修正失效文档路径 --- .pre-commit-config.yaml | 24 ++++- App/sys_clock.c | 8 ++ App/task/ui_task.c | 26 +++++- Drivers/BSP/NET/net_select.c | 24 ++--- Drivers/BSP/NET/net_select.h | 14 +-- Drivers/BSP/NET/net_socket.c | 36 +++++--- Drivers/BSP/NET/net_types.h | 16 ++-- README.md | 1 + docs/代码规范/cppcheck使用说明.md | 17 ---- docs/代码规范/pre-commit使用说明.md | 48 ++++++++-- scripts/check_coding_standard.py | 132 ++++++++++++++++++++++++++++ test/net_test_task.c | 4 +- 12 files changed, 278 insertions(+), 72 deletions(-) create mode 100644 scripts/check_coding_standard.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83a4af1c..c83f71e7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ # pre-commit 自动化代码审查配置 -# 详见 docs/测试说明/自动化代码审查实施.md +# 详见 docs/代码规范/pre-commit使用说明.md default_install_hook_types: [pre-commit, commit-msg] exclude: | @@ -74,3 +74,25 @@ repos: entry: python scripts/check_mojibake.py language: system types: [c] + - id: coding-standard + name: C coding standard (规范V1.0) + entry: python scripts/check_coding_standard.py + language: system + types: [c] + # 范围与 cppcheck 钩子一致;另排除 CubeMX 生成文件(Src/、Inc/,重新生成即覆盖)、 + # dhara/FatFS 接口适配层(nand_ftl,函数名由外部 API 固定) + exclude: | + (?x)^( + Drivers/CMSIS/| + Drivers/STM32F4xx_HAL_Driver/| + Drivers/BSP/UI/| + Drivers/BSP/NET/lftpd/| + Drivers/BSP/GD5F2GQ5UE/nand_ftl\.| + Drivers/BSP/dhara/| + Lib/| + Middlewares/| + MDK-ARM/| + test/| + Src/| + Inc/ + ) diff --git a/App/sys_clock.c b/App/sys_clock.c index e34af45e..e758b61c 100644 --- a/App/sys_clock.c +++ b/App/sys_clock.c @@ -358,6 +358,14 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) return 0; } +/* + * 函数功能:设置系统时间为指定 Unix 秒数并写回 RTC + * 入口参数:unix_secs - Unix 时间戳(秒) uint32_t + * 返回值:0 - 成功 int + * 限定条件:无 + * 函数说明:将 Unix 秒数转换为 SD2506 时间结构,再调用 sys_clock_set() + * 更新运行期基准并写回 RTC,实现掉电保持 + */ int sys_clock_set_unix(uint32_t unix_secs) { sd2506_time_t sd; diff --git a/App/task/ui_task.c b/App/task/ui_task.c index 6ce44d53..672e8665 100644 --- a/App/task/ui_task.c +++ b/App/task/ui_task.c @@ -1,7 +1,10 @@ -/** - * @file ui_task.c - * @brief UI 主任务:等待网络就绪 -> 初始化显示/菜单/推流 -> 周期刷新 - * @note 单任务内同时驱动 LVGL 刷新与网络推流轮询,避免任一方阻塞导致界面不刷新。 +/* + * 模块名称:UI Task + * 模块功能:UI 主任务,等待网络就绪后初始化显示/菜单/推流并周期刷新 + * 适用平台:STM32F407 系列(LVGL + CH395F 以太网) + * 作者:王建锋 + * 创建日期:2026-08-30 + * 修改记录: */ #include "ui_task.h" @@ -15,6 +18,13 @@ #define DBG_TAG "[UI_TASK]" #include "dbg_log.h" +/* + * 函数功能:等待网络就绪事件 + * 入口参数:无 + * 返回值:无 + * 限定条件:sys_evtHandle 为 NULL 时直接返回 + * 函数说明:无限期等待 SYS_EVT_NET_READY 标志 + */ static void ui_task_wait_net_ready(void) { if (sys_evtHandle != NULL) { @@ -23,6 +33,14 @@ static void ui_task_wait_net_ready(void) } } +/* + * 函数功能:UI 主任务入口,初始化显示/菜单/推流并进入刷新主循环 + * 入口参数:argument - FreeRTOS 任务参数(未使用) void * + * 返回值:无 + * 限定条件:FreeRTOS 已启动 + * 函数说明:1. 等待 SYS_EVT_NET_READY 后依次初始化 ui_port/ui_menu/ui_net_sink + * 2. 主循环周期调用 ui_port_step 与 ui_net_sink_poll 刷新界面 + */ void ui_task_func(void *argument) { (void)argument; diff --git a/Drivers/BSP/NET/net_select.c b/Drivers/BSP/NET/net_select.c index 56cebb5b..666bad0d 100644 --- a/Drivers/BSP/NET/net_select.c +++ b/Drivers/BSP/NET/net_select.c @@ -35,11 +35,11 @@ static int check_socket_error(int sockfd); /* * 函数功能:I/O 多路复用(select 模式),等待多个 Socket 的读写事件就绪 * 入口参数:nfds - 最大文件描述符数 +1 int > 0 - * readfds - 读事件集合指针 net_fd_set *(可为 NULL) - * writefds - 写事件集合指针 net_fd_set *(可为 NULL) - * exceptfds- 异常事件集合指针 net_fd_set *(可为 NULL) - * timeout - 超时时间结构体指针 net_timeval *(NULL=无限等待) - * 出口参数:readfds/writefds/exceptfds - 仅保留就绪的 Socket 位 net_fd_set * + * readfds - 读事件集合指针 net_fd_set_t *(可为 NULL) + * writefds - 写事件集合指针 net_fd_set_t *(可为 NULL) + * exceptfds- 异常事件集合指针 net_fd_set_t *(可为 NULL) + * timeout - 超时时间结构体指针 net_timeval_t *(NULL=无限等待) + * 出口参数:readfds/writefds/exceptfds - 仅保留就绪的 Socket 位 net_fd_set_t * * 返回值:就绪的 Socket 数量(>= 0);-1 表示参数无效 int * 限定条件:net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用 * 函数说明:1. 轮询 Socket 控制块(RAM)中的读/写/异常状态,判断事件是否就绪 @@ -49,13 +49,13 @@ static int check_socket_error(int sockfd); * 3. 阻塞等待直到有事件就绪或超时 * 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms) */ -int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, - net_fd_set *exceptfds, net_timeval *timeout) { +int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds, + net_fd_set_t *exceptfds, net_timeval_t *timeout) { uint32_t start_tick; uint32_t timeout_ms = 0; - net_fd_set result_read = {0}; - net_fd_set result_write = {0}; - net_fd_set result_except = {0}; + net_fd_set_t result_read = {0}; + net_fd_set_t result_write = {0}; + net_fd_set_t result_except = {0}; int ready_count = 0; int i; @@ -142,7 +142,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, /* * 函数功能:I/O 多路复用(poll 模式),等待多个 Socket 的读写事件就绪 - * 入口参数:fds - pollfd 数组指针 net_pollfd * + * 入口参数:fds - pollfd 数组指针 net_pollfd_t * * nfds - 数组元素数量 int > 0 * timeout - 超时时间(毫秒) int >= 0(-1=无限等待,但本实现按 >=0 处理) * 出口参数:fds[i].revents - 填充实际发生的事件掩码 short @@ -153,7 +153,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, * 3. 阻塞等待直到有事件就绪或超时 * 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms) */ -int net_poll_events(net_pollfd *fds, int nfds, int timeout) { +int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) { uint32_t start_tick; int ready = 0; int i; diff --git a/Drivers/BSP/NET/net_select.h b/Drivers/BSP/NET/net_select.h index 494396cf..fe31ca5d 100644 --- a/Drivers/BSP/NET/net_select.h +++ b/Drivers/BSP/NET/net_select.h @@ -24,7 +24,7 @@ extern "C" { */ typedef struct { uint32_t fd_bits; /* 位掩码 */ -} net_fd_set; +} net_fd_set_t; /* * 时间结构体 @@ -32,7 +32,7 @@ typedef struct { typedef struct { long tv_sec; /* 秒 */ long tv_usec; /* 微秒 */ -} net_timeval; +} net_timeval_t; /* * poll 事件结构体 @@ -41,7 +41,7 @@ typedef struct { int fd; /* socket 描述符 */ short events; /* 请求的事件 */ short revents; /* 实际发生的事件 */ -} net_pollfd; +} net_pollfd_t; /* * poll 事件掩码 @@ -92,12 +92,12 @@ typedef struct { * 函数说明:此函数会调用 net_poll() 轮询所有 Socket, * 并检查哪些 Socket 有指定的事件发生。 */ -int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, - net_fd_set *exceptfds, net_timeval *timeout); +int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds, + net_fd_set_t *exceptfds, net_timeval_t *timeout); /* * 函数功能:I/O 多路复用(poll 模式) - * 入口参数:fds - net_pollfd 数组 + * 入口参数:fds - net_pollfd_t 数组 * nfds - 数组元素数量 * timeout - 超时时间(毫秒),-1 表示无限等待 * 返回值:就绪的 fd 数量,0=超时,-1=错误 @@ -105,7 +105,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, * 函数说明:与 select 类似,但使用 pollfd 结构体数组, * 更灵活,支持更多事件类型。 */ -int net_poll_events(net_pollfd *fds, int nfds, int timeout); +int net_poll_events(net_pollfd_t *fds, int nfds, int timeout); #ifdef __cplusplus } diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 0d3f5bf8..0c53077e 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -1049,6 +1049,15 @@ static int net_relisten_locked(net_sock_t *p_sock) { return 0; } +/* + * 函数功能:关闭 Socket(内部版本,不经过消息队列) + * 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t * + * 出口参数:无 + * 返回值:0 - 关闭成功 int + * 限定条件:必须在 netTask 上下文中调用;p_sock 有效 + * 函数说明:1. 关闭 CH395F Socket,排空上一条连接残留的 DISCONNECT 中断 + * 2. 等待芯片回到 CLOSED 状态(超时 500ms),再清零控制块 + */ static int net_close_locked(net_sock_t *p_sock) { int sockfd = (int)(p_sock - s_net_socks); uint32_t tick = HAL_GetTick(); @@ -1515,7 +1524,7 @@ int net_get_errno(void) { * 返回值:网络字节序的 32 位 IP 地址(0 表示参数无效) uint32_t * 限定条件:无 * 函数说明:1. 逐段解析十进制数与并小端组装为 32 位值 - * 2. 转换结果经 NET_htonl 转为网络字节序 + * 2. 转换结果经 NET_HTONL 转为网络字节序 */ uint32_t net_inet_addr(const char *cp) { uint32_t addr = 0; @@ -1537,7 +1546,7 @@ uint32_t net_inet_addr(const char *cp) { } } - return NET_htonl(addr); + return NET_HTONL(addr); } /* @@ -1547,16 +1556,16 @@ uint32_t net_inet_addr(const char *cp) { * 出口参数:buf - 填充点分十进制字符串 * 返回值:成功返回 buf 指针;buf 为 NULL 时返回 NULL char * * 限定条件:buf 非空且长度足够(>= 16 字节) - * 函数说明:1. 先经 NET_ntohl 转为主机字节序再格式化输出 + * 函数说明:1. 先经 NET_NTOHL 转为主机字节序再格式化输出 */ char *net_inet_ntoa(uint32_t addr, char *buf) { - uint32_t naddr = NET_ntohl(addr); + uint32_t naddr = NET_NTOHL(addr); if (NULL == buf) { return NULL; } - sprintf(buf, "%lu.%lu.%lu.%lu", + snprintf(buf, 16, "%lu.%lu.%lu.%lu", (unsigned long)(naddr & 0xFF), (unsigned long)((naddr >> 8) & 0xFF), (unsigned long)((naddr >> 16) & 0xFF), @@ -1572,10 +1581,10 @@ char *net_inet_ntoa(uint32_t addr, char *buf) { * 出口参数:无 * 返回值:网络字节序的 16 位端口 uint16_t * 限定条件:无 - * 函数说明:直接调用 NET_htons 完成转换 + * 函数说明:直接调用 NET_HTONS 完成转换 */ uint16_t net_htons(uint16_t hostshort) { - return NET_htons(hostshort); + return NET_HTONS(hostshort); } @@ -1585,10 +1594,10 @@ uint16_t net_htons(uint16_t hostshort) { * 出口参数:无 * 返回值:主机字节序的 16 位端口 uint16_t * 限定条件:无 - * 函数说明:直接调用 NET_ntohs 完成转换 + * 函数说明:直接调用 NET_NTOHS 完成转换 */ uint16_t net_ntohs(uint16_t netshort) { - return NET_ntohs(netshort); + return NET_NTOHS(netshort); } /* @@ -1597,10 +1606,10 @@ uint16_t net_ntohs(uint16_t netshort) { * 出口参数:无 * 返回值:网络字节序的 32 位 IP 地址 uint32_t * 限定条件:无 - * 函数说明:直接调用 NET_htonl 完成转换 + * 函数说明:直接调用 NET_HTONL 完成转换 */ uint32_t net_htonl(uint32_t hostlong) { - return NET_htonl(hostlong); + return NET_HTONL(hostlong); } /* @@ -1609,10 +1618,10 @@ uint32_t net_htonl(uint32_t hostlong) { * 出口参数:无 * 返回值:主机字节序的 32 位 IP 地址 uint32_t * 限定条件:无 - * 函数说明:直接调用 NET_ntohl 完成转换 + * 函数说明:直接调用 NET_NTOHL 完成转换 */ uint32_t net_ntohl(uint32_t netlong) { - return NET_ntohl(netlong); + return NET_NTOHL(netlong); } /* @@ -1637,6 +1646,7 @@ static int alloc_socket(void) { } /* + * 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功) * 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t * * 出口参数:无 * 返回值:无 diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h index 35992b2d..7af37f89 100644 --- a/Drivers/BSP/NET/net_types.h +++ b/Drivers/BSP/NET/net_types.h @@ -53,18 +53,18 @@ extern "C" { * 网络字节序转换宏 */ #ifdef __BIG_ENDIAN -#define NET_htonl(x) (x) -#define NET_htons(x) (x) -#define NET_ntohl(x) (x) -#define NET_ntohs(x) (x) +#define NET_HTONL(x) (x) +#define NET_HTONS(x) (x) +#define NET_NTOHL(x) (x) +#define NET_NTOHS(x) (x) #else -#define NET_htonl(x) (((uint32_t)(x) << 24) | \ +#define NET_HTONL(x) (((uint32_t)(x) << 24) | \ (((uint32_t)(x) & 0xFF00) << 8) | \ (((uint32_t)(x) & 0xFF0000) >> 8) | \ ((uint32_t)(x) >> 24)) -#define NET_htons(x) (((uint16_t)(x) << 8) | ((uint16_t)(x) >> 8)) -#define NET_ntohl(x) NET_htonl(x) -#define NET_ntohs(x) NET_htons(x) +#define NET_HTONS(x) (((uint16_t)(x) << 8) | ((uint16_t)(x) >> 8)) +#define NET_NTOHL(x) NET_HTONL(x) +#define NET_NTOHS(x) NET_HTONS(x) #endif /* diff --git a/README.md b/README.md index bb83e9d8..e8197a72 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ powershell -ExecutionPolicy Bypass -File test\run_boot_test.ps1 - 行尾空格 / 文件尾换行(自动修复) - 大文件 / 冲突标记 / YAML 语法(拦截) - **cppcheck 静态分析**:warning / performance / portability / style 四类,发现问题即拦截 +- **代码规范检查**:头文件保护宏、命名(宏大写/`_t` 后缀)、函数注释、default 分支、禁用 strcpy/sprintf/goto/Tab 等(规范 V1.0 可机械校验项) - U+FFFD 乱码(拦截,不可逆损坏) - commit message 格式(gitlint,拦截) diff --git a/docs/代码规范/cppcheck使用说明.md b/docs/代码规范/cppcheck使用说明.md index d4faeb8a..d4c3646b 100644 --- a/docs/代码规范/cppcheck使用说明.md +++ b/docs/代码规范/cppcheck使用说明.md @@ -216,20 +216,3 @@ if (day_count < diy) break; 某条规则在整个工程中都是噪音/不适用时,在 `.pre-commit-config.yaml` 的 args 中添加 `--suppress=规则名`(同样遵守 4.2 节引号规则),现有清单见 4.1 节。 --- - -## 6. 问题与误报处理记录 - -### style 检查发现的问题(已处理完毕) - -开启 style 后发现的有价值问题已全部处理: - -| 规则 | 问题 | 涉及文件 | 处理结果 | -|---|---|---|---| -| `compareValueOutOfTypeRangeError` | `uint16_t > 65535` 恒假 | net_socket.c | ✅ 已修复(改为 `== 0` 检测溢出绕回) | -| `redundantAssignment` | 重复赋值覆盖旧值 | ch395f.c | ✅ 已抑制(HAL 清标志宏读寄存器即目的,误报) | -| `unreadVariable` | 变量赋值后未读取 | gd5f2gq5ue.c, time_sync.c, ringbuf.c | ✅ 已修复(移除冗余初始化) | -| `variableScope` | 变量作用域可缩小 | sys_clock.c, net_socket.c 等 18 处 | ✅ 已修复(声明移入实际使用块) | -| `constVariablePointer` | 指针可加 const 防误改 | net_select.c, net_socket.c 共 6 处 | ✅ 已修复(`p_sock` 改为 `const net_sock_t *`) | -| `knownConditionTrueFalse` | `day_count < diy` 被判恒假(闰年 `diy=366` 分支未跟踪,误报) | sys_clock.c:108 | ✅ 已行内抑制(`// cppcheck-suppress`) | - -> 注:`knownConditionTrueFalse` 属真误报——闰年分支 `diy=366` 时 `day_count==365` 的 break 有意义,cppcheck 数据流分析未覆盖该分支。已用行内抑制处理,不影响该规则在其他文件上的正常检查。 diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index c6a01ac2..594077bc 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -15,8 +15,9 @@ git commit ├─ check-added-large-files 阻止 >1MB 大文件 ├─ check-merge-conflict 阻止含冲突标记的文件 ├─ check-yaml 校验 YAML 语法 - ├─ cppcheck C 静态分析(自有代码) - └─ no-mojibake 阻止 U+FFFD 乱码文件 + ├─ cppcheck C 静态分析(自有代码) + ├─ no-mojibake 阻止 U+FFFD 乱码文件 + └─ coding-standard C 代码规范检查(规范V1.0 可机械校验项) └─ commit-msg(.git/hooks/commit-msg) └─ gitlint 校验 commit message 格式 ``` @@ -30,6 +31,7 @@ git commit | pre-commit 4.6.2 | `uv tool install pre-commit` | 钩子框架 | | cppcheck 2.21.0 | `winget install cppcheck` | C 静态分析引擎 | | scripts/check_mojibake.py | 随仓库提交 | U+FFFD 乱码检测(纯 Python,无依赖) | +| scripts/check_coding_standard.py | 随仓库提交 | 代码规范检查(纯 Python,无依赖) | > **PATH 要求**:cppcheck 安装后需在系统 PATH 中(`C:\Program Files\Cppcheck`)。winget 自动写入 User PATH,但当前终端不会立即生效,需**重开终端**。 @@ -96,7 +98,7 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法 | **典型报错** | `[path/to/file.c:123]: (style) variableScope The scope of the variable ... can be reduced` | | **增量速度** | 单文件约 1~2 秒;全库约 5 秒 | -> **完整规则清单(style 类别 95 项详解)、配置参数、YAML 引号陷阱、误报抑制方法、历史问题处理记录见 `docs/cppcheck使用说明.md`。** +> **完整规则清单(style 类别 95 项详解)、配置参数、YAML 引号陷阱、误报抑制方法见 `docs/代码规范/cppcheck使用说明.md`。** ### 2.7 no-mojibake — U+FFFD 乱码拦截 @@ -110,7 +112,35 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法 > ⚠️ **编码铁律**:U+FFFD 是不可逆损坏(无法从替换符反推原始中文字符)。若发现新文件出现 U+FFFD,必须排查写入方式(禁用 Get-Content / Set-Content 重写中文文件)。 -### 2.8 gitlint — Commit Message 格式 +### 2.8 coding-standard — C 代码规范检查 + +依据 `docs/代码规范/嵌入式C语言代码规范(V1.0).md`,用 `scripts/check_coding_standard.py` 对**可机械化校验**的规则做提交前拦截(纯 Python 正则扫描,无依赖)。 + +| 项 | 说明 | +|---|---| +| **触发条件** | 自有代码 .c/.h 文件(App/、Drivers/BSP/,第三方与 CubeMX 生成文件除外)违反下列规则 | +| **处理方式** | **阻止提交**(必须修复) | +| **典型报错** | `L123 [S3 3.4.1] 宏名须全大写+下划线: net_htonl` | + +**检查项(S 编号与脚本输出一致)**: + +| 编号 | 规范条款 | 规则 | +|---|---|---| +| S1 | 2.3.1 | .h 文件必须有 `__XXX_H` 格式头文件保护宏 | +| S2 | 5.2 | 文件开头必须有文件头注释(含 模块名称/作者) | +| S3 | 3.4.1 | `#define` 宏名必须全大写+下划线+数字 | +| S4 | 3.5.1 | typedef 类型名必须以 `_t` 结尾 | +| S5 | 6.3.2 | switch 必须包含 default 分支 | +| S6 | 7.5 | 禁用 strcpy/strcat/sprintf/gets(改用 strncpy/strncat/snprintf) | +| S7 | 6.3.3 | 禁止 goto | +| S8 | 4.1.1 | 禁止 Tab 缩进(4 空格) | +| S9 | 5.3 | 函数定义必须有函数注释(含「函数功能」) | + +**不在检查范围**(语义类规则,无法机械可靠判定):函数长度/单一职责、参数数量、错误处理、递归、注释掉的代码等,仍需人工评审把关。 + +**排除文件**(与 cppcheck 钩子范围一致,另加):`Src/`、`Inc/`(CubeMX 生成,重新生成即覆盖)、`Drivers/BSP/GD5F2GQ5UE/nand_ftl.*`(dhara/FatFS 接口适配层,函数名由外部 API 固定)。 + +### 2.9 gitlint — Commit Message 格式 | 项 | 说明 | |---|---| @@ -150,6 +180,7 @@ git commit -m "修复网络超时重连逻辑" | end-of-file-fixer | 同上 | 同上 | | cppcheck | 报 warning/performance/portability/style | 根据报告修复代码;误报用行内抑制或 `--suppress`(详见 `docs/cppcheck使用说明.md`)| | no-mojibake | `U+FFFD replacement chars found` | 从 git 历史恢复干净版本(不可逆!) | +| coding-standard | `L行号 [Sx x.x.x] 规则说明` | 按违规项修复(命名加前缀/大写化、补函数注释、sprintf 改 snprintf 等) | | gitlint | `Title exceeds max length` | 精简 commit message 标题 | ### 3.3 全库扫描(配置变更后) @@ -232,12 +263,13 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env | 文件 | 位置 | 说明 | |---|---|---| -| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(7 个钩子 + 1 个预留) | +| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(8 个钩子 + 1 个预留) | | `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) | | `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 | -| `docs/自动化代码审查实施.md` | docs/ | 实施规划文档(Route A/B 设计) | -| `docs/cppcheck使用说明.md` | docs/ | cppcheck 钩子详解(规则/配置/抑制) | -| `docs/pre-commit使用说明.md` | 本文档 | 部署与使用说明 | +| `scripts/check_coding_standard.py` | scripts/ | 代码规范检查脚本(规范 V1.0 可机械校验项) | +| `docs/代码规范/嵌入式C语言代码规范(V1.0).md` | docs/代码规范/ | 代码规范原文(S1~S9 规则来源) | +| `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) | +| `docs/代码规范/pre-commit使用说明.md` | 本文档 | 部署与使用说明 | --- diff --git a/scripts/check_coding_standard.py b/scripts/check_coding_standard.py new file mode 100644 index 00000000..71f9eb33 --- /dev/null +++ b/scripts/check_coding_standard.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +C 语言代码规范检查(《嵌入式C语言代码规范(V1.0)》可机械化校验的规则) +由 pre-commit 调用:python scripts/check_coding_standard.py ,发现违规退出码 1。 + +检查项(括号内为规范条款号): + S1 (2.3.1) .h 文件必须有 __XXX_H 格式的头文件保护宏 + S2 (5.2) 文件开头必须有文件头注释(含 模块名称 / 作者) + S3 (3.4.1) #define 宏名必须全大写 + 下划线 + 数字 + S4 (3.5.1) typedef 类型名必须以 _t 结尾 + S5 (6.3.2) switch 必须包含 default 分支 + S6 (7.5) 禁用不安全字符串函数 strcpy/strcat/sprintf/gets + S7 (6.3.3) 禁止使用 goto + S8 (4.1.1) 禁止 Tab 缩进(使用 4 个空格) + S9 (5.3) 函数定义必须有函数注释(含「函数功能」) + +语义类规则(函数长度、单一职责、错误处理、递归等)无法机械可靠判定,不在此脚本范围。 +""" +import re +import sys + +# 顶层函数定义行:类型 名称 ( 参数 ),可选同行左大括号 +FUNC_DEF_RE = re.compile( + r'^[A-Za-z_][A-Za-z0-9_\s\*]*\s+[a-z_][a-z0-9_]*\s*\([^;{]*\)\s*\{?\s*$' +) +CTRL_RE = re.compile(r'^\s*(if|for|while|switch|return|do|else)\b') + + +def check_file(path): + with open(path, 'r', encoding='utf-8', errors='replace') as f: + text = f.read() + lines = text.splitlines() + v = [] # (行号或'-', 规则, 说明) + + # S1 (2.3.1) .h 头文件保护宏 + if path.endswith('.h') and \ + not re.search(r'#\s*ifndef\s+__[A-Z0-9_]+_H\b', text): + v.append(('-', 'S1 2.3.1', '缺少 __XXX_H 格式的头文件保护宏')) + + # S2 (5.2) 文件头注释 + head = '\n'.join(lines[:60]) + if '模块名称' not in head and '作者' not in head: + v.append(('-', 'S2 5.2', '缺少文件头注释(模块名称 / 作者)')) + + # S3 (3.4.1) 宏名全大写 + for m in re.finditer(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)', text, re.M): + if re.search(r'[a-z]', m.group(1)): + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S3 3.4.1', '宏名须全大写+下划线: %s' % m.group(1))) + + # S4 (3.5.1) typedef 类型名 _t 后缀 + for m in re.finditer(r'typedef\s+(?:struct|enum|union)\s*\{.*?\}\s' + r'([A-Za-z_][A-Za-z0-9_]*)\s*;', text, re.S): + if not m.group(1).endswith('_t'): + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S4 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1))) + for m in re.finditer(r'typedef\s+(?:unsigned\s+|signed\s+)?' + r'(?:char|short|int|long|float|double)\s+\*?\s*' + r'([A-Za-z_][A-Za-z0-9_]*)\s*;', text): + if not m.group(1).endswith('_t'): + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S4 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1))) + + # S5 (6.3.2) switch 必须有 default + for m in re.finditer(r'\bswitch\s*\(', text): + i = text.find('{', m.start()) + if i == -1: + continue + j, d = i, 0 + while j < len(text): + if text[j] == '{': + d += 1 + elif text[j] == '}': + d -= 1 + if d == 0: + break + j += 1 + if not re.search(r'\bdefault\s*:', text[i:j + 1]): + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S5 6.3.2', 'switch 缺少 default 分支')) + + # S6 (7.5) 不安全字符串函数 + for m in re.finditer(r'\b(strcpy|strcat|sprintf|gets)\s*\(', text): + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S6 7.5', '不安全字符串函数: %s (改用 strncpy/strncat/snprintf)' + % m.group(1))) + + # S7 (6.3.3) 禁止 goto + m = re.search(r'\bgoto\s+[A-Za-z_]\w*', text) + if m: + ln = text[:m.start()].count('\n') + 1 + v.append((ln, 'S7 6.3.3', '禁止使用 goto')) + + # S8 (4.1.1) 禁止 Tab + if '\t' in text: + for idx, line in enumerate(lines, 1): + if '\t' in line: + v.append((idx, 'S8 4.1.1', '禁止 Tab 缩进(使用 4 个空格)')) + break + + # S9 (5.3) 函数定义必须有函数注释 + for idx, line in enumerate(lines, 1): + if (FUNC_DEF_RE.match(line) and '=' not in line and not CTRL_RE.match(line)): + window = '\n'.join(lines[max(0, idx - 26):idx - 1]) + if '函数功能' not in window: + v.append((idx, 'S9 5.3', '函数定义缺少函数注释(函数功能): %s' + % line.strip()[:50])) + + return v + + +def main(): + failed = False + for path in sys.argv[1:]: + try: + v = check_file(path) + except OSError as e: + print('cannot read %s: %s' % (path, e)) + failed = True + continue + if v: + failed = True + print('%s: %d 处规范违规' % (path, len(v))) + for ln, rule, msg in v: + print(' L%-4s [%s] %s' % (ln, rule, msg)) + if failed: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/test/net_test_task.c b/test/net_test_task.c index 93d1a86f..389cb7ae 100644 --- a/test/net_test_task.c +++ b/test/net_test_task.c @@ -596,8 +596,8 @@ static void phase8_run(int32_t sockfd) { static uint32_t s_phase9_count = 0; static void phase9_run(int32_t sockfd) { - net_fd_set readfds; - net_timeval tv; + net_fd_set_t readfds; + net_timeval_t tv; /* * 配置 select:只监测 Socket 0 的可读状态,超时 50ms。