新增代码规范检查钩子(规范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:
+23
-1
@@ -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/
|
||||
)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 *
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
|
||||
@@ -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
|
||||
|
||||
/*
|
||||
|
||||
@@ -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,拦截)
|
||||
|
||||
|
||||
@@ -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 数据流分析未覆盖该分支。已用行内抑制处理,不影响该规则在其他文件上的正常检查。
|
||||
|
||||
@@ -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` | 本文档 | 部署与使用说明 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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。
|
||||
|
||||
Reference in New Issue
Block a user