新增代码规范检查钩子(规范V1.0 可机械校验项)并修复存量违规

新增 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 节,修正失效文档路径
This commit is contained in:
2026-09-01 15:41:25 +08:00
parent 0ccd07ee12
commit b3beec633b
12 changed files with 278 additions and 72 deletions
+23 -1
View File
@@ -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/
)
+8
View File
@@ -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;
+22 -4
View File
@@ -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;
+12 -12
View File
@@ -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;
+7 -7
View File
@@ -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
}
+23 -13
View File
@@ -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 *
* 出口参数:无
* 返回值:无
+8 -8
View File
@@ -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
/*
+1
View File
@@ -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,拦截)
-17
View File
@@ -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 数据流分析未覆盖该分支。已用行内抑制处理,不影响该规则在其他文件上的正常检查。
+40 -8
View File
@@ -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` | 本文档 | 部署与使用说明 |
---
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
C 语言代码规范检查(《嵌入式C语言代码规范(V1.0)》可机械化校验的规则)
由 pre-commit 调用:python scripts/check_coding_standard.py <file...>,发现违规退出码 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()
+2 -2
View File
@@ -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。