bot4cj: 代码审计报告(15项发现,待修复)

新增审计: docs/reviews/bot4cj_CODE_AUDIT_20260808.md
- P0(3项): submitLine运算符优先级bug(DoesNotSync从不触发)、
  print(s)调试残留、扬声器仅sleep未发声
- P1(5项): r"\n"转义不一致、MPU6050地址重复、readI2cReg返回Int8、
  Lua路径硬编码、README D6/D8滞后
- P2(7项): 无测试、注释代码、I2C锁O(n)扫描、Makefile警告关闭等
- 交叉引用旧分析报告D1-D29,新发现S01根因(D11仅说信息不清晰)
更新: docs/INDEX.md reviews计数3→4
This commit is contained in:
qing committed 2026-08-08 16:40:04 +00:00
1 parent 397970edc3
commit 37206daf13
2 files changed
+399 -1

No files matched your search

+2 -1
View File
@@ -8,7 +8,7 @@
docs/
├── INDEX.md # 本文件(文档总索引)
├── security/ # 安全审计与加固(25 篇)
├── reviews/ # 代码审查日志(3 篇)
├── reviews/ # 代码审查日志(4 篇)
└── reports/ # 分析报告与开发规划(8 篇)
```
@@ -67,6 +67,7 @@ docs/
| [core_CODE_REVIEW_LOG_20260730.md](reviews/core_CODE_REVIEW_LOG_20260730.md) | Core 模块代码审查日志(2026-07-30,自动化审查) |
| [DEPLOY_DEFECT_AUDIT_20260806.md](reviews/DEPLOY_DEFECT_AUDIT_20260806.md) | 部署缺陷审查(2026-08-06,首次原生部署实录,含修复记录) |
| [actpred_CODE_AUDIT_20260808.md](reviews/actpred_CODE_AUDIT_20260808.md) | actpred 模块代码审计(2026-08-08,PyTorch 动作预测,16 项发现,全部修复) |
| [bot4cj_CODE_AUDIT_20260808.md](reviews/bot4cj_CODE_AUDIT_20260808.md) | bot4cj 模块代码审计(2026-08-08,仓颉机器人硬件脚本解析器,15 项发现,待修复) |
---
+397
View File
@@ -0,0 +1,397 @@
# bot4cj 模块代码审计报告
> 审查日期:2026-08-08 (Asia/Shanghai)
> 审查人:Automated Review
> 审查范围:`bot4cj/` 全模块(仓颉 Cangjie 机器人硬件脚本解析器)
> 参考文档:[深度分析报告](../reports/bot4cj_analysis_20260525_00.md)(D1-D29)、[actpred 与 bot4cj 研究报告](../reports/actpred_bot4cj_research_20260809.md)
---
## 一、模块概述
| 属性 | 值 |
|------|-----|
| 定位 | 机器人硬件脚本解析器(.mt 矩阵脚本 → 驱动舵机/IMU/扬声器) |
| 语言 | 仓颉 Cangjie + C/C++(系统库)+ Lua(硬件扩展) |
| 架构 | MatrixParser(FSM) → MatrixObject → RobotStruct → 硬件变量工厂 → hd/*.cj + modules/*.lua |
| 目标平台 | Linux aarch64(树莓派)/ x86_64 |
| 入口 | `main.cj`(stdin 管道 / --test 分支 / -i/-o DRL 预留) |
| 构建 | Makefile(C/C++ .so)+ cjpm.toml(仓颉包管理) |
### 依赖项
| 库 | 用途 |
|----|------|
| libc / libpthread | I2C glibc 封装、线程 |
| liblua-5.4 | Lua 硬件模块扩展(可选) |
| ctype4cj / pthread4cj / spi4cj / lua4cj | 仓颉官方扩展包 |
### 目录结构
```
bot4cj/
├── src/
│ ├── main.cj # 入口:CLI 参数解析 + stdin 管道执行
│ ├── P.cj # 矩阵脚本解析器(FSM 逐字符解析)+ MatrixObject
│ ├── r.cj # RobotStruct + MatrixVars_Hardware 基类 + 硬件变量工厂
│ ├── hd/
│ │ ├── I2C.cj # I2C 封装 + 20 槽锁数组
│ │ ├── PCA9685.cj # PCA9685 舵机控制板(已由 Lua 替代)
│ │ ├── Servo.cj # 舵机:max/min/offset 校准
│ │ ├── MPU6050.cj # 6 轴传感器 + 卡尔曼滤波 + 后台线程
│ │ ├── snd.cj # 扬声器(仅 sleep 占位)
│ │ └── fb.cj # 外部硬件扩展
│ ├── math/
│ │ ├── Matrix.cj # 二维矩阵(+运算符重载)
│ │ ├── Af.cj # 6 种激活函数 + 雅可比
│ │ ├── nn.cj # 全连接层 Nn1(前向/反向/权重更新)
│ │ ├── Adam.cj # Adam 优化器
│ │ ├── Ini.cj # Xavier 初始化
│ │ ├── kalman.cj # 卡尔曼滤波
│ │ └── ... # Tensor / cnn / fft / pooling / flatten
│ ├── cpp/ # C/C++ 系统库源
│ └── cuda/ # CUDA GPU 加速
├── modules/ # Lua 硬件模块
├── scripts/ # .mt 动作脚本 + .bot 旧版脚本
├── Makefile # C/C++ 共享库构建
├── cjpm.toml # 仓颉包配置
├── README.md # 旧版说明
└── README.robot.md # 技术文档(含已知问题表 D1-D11)
```
---
## 二、审查发现
### 🔴 严重问题 (P0)
#### [BOT-S01] submitLine 列数校验恒假,DoesNotSync 从不触发
- **文件**: `src/P.cj:93`
- **描述**: `MatrixObject.submitLine()` 中的行列数一致性检查使用了错误的运算符写法。仓颉中 `!`(逻辑非)优先级高于 `==`,表达式实际解析为 `(!values.size) == 0 && (!grp.size) == values[...].size`。
```cangjie
// 当前代码(第 93 行):
if (!values.size == 0 && !grp.size == values[values.size - 1].size)
{ throw DoesNotSync(count) }
// 实际解析为:
if ((!values.size) == 0 && (!grp.size) == values[values.size - 1].size)
{ throw DoesNotSync(count) }
```
`!grp.size` 对整数取逻辑非(C 语义:`!0=1, !非零=0`),结果为 0 或 1。对于 12 列的 `@Action` 矩阵,`0 == 12` 恒为 false,整个条件恒假,`DoesNotSync` 异常**从不触发**。
| `grp.size` | `!grp.size` | `values[...].size` | `==` 结果 |
|------------|------------|---------------------|----------|
| 12(正常) | 0 | 12 | `0 == 12` → false |
| 10(少 2 列) | 0 | 12 | `0 == 12` → false |
| 14(多 2 列) | 0 | 12 | `0 == 12` → false |
- **触发条件**: 任何 `.mt` 脚本的矩阵块中某行列数与表头不一致
- **影响**: 列数不一致时不报错,静默存入 `MatrixObject`。消费端 `run_drl_action()` 用表头列数遍历,数据行列数不足时抛出通用 `IndexOutOfBoundsException`,报错信息不可读
- **修复建议**: 改用 `!=` 运算符:
```cangjie
if (values.size != 0 && grp.size != values[values.size - 1].size)
{ throw DoesNotSync(count) }
```
- **关联**: 旧分析报告 D11 仅指出"错误信息不清晰",未发现根因是运算符优先级导致检查完全失效
#### [BOT-S02] parse() 主循环 print(s) 调试残留
- **文件**: `src/P.cj:147`
- **描述**: `MatrixParser.parse()` 主循环中有一行 `print(s)`,逐字符将整个脚本内容打到 stdout。
```cangjie
while (!flag && hasValue(so)) {
try {
let s = so.getOrThrow()
print(s) // ← debug:逐字符打印整个脚本
count = _next(s, count)
so = input.read()
}
...
}
```
- **影响**: 每次运行脚本时 stdout 被解析器输出污染。若未来用 stdout 与上层通信(如 actpred 的管道集成),会造成数据混淆
- **修复建议**: 删除 `print(s)` 行,或改为可选的 debug 开关
- **关联**: 旧分析报告 D25 已记录
#### [BOT-S03] 扬声器 play() 仅 sleep,实际发声未接入
- **文件**: `src/hd/snd.cj:17-21`
- **描述**: `Spk.play()` 的实际发声 C++ 函数 `play_frequency()` 被注释掉,方法体仅执行 `sleep()`,扬声器无实际声音输出。
```cangjie
public func play(frequency: Float32, volume: Float64, durations_ms: Int32): Bool {
//unsafe { play_frequency(frequency, durations_ms, volume, channels, sample_rate) }
sleep(Duration.millisecond * Int64(durations_ms)) // ← 仅延时
return true
}
```
- **影响**: `.mt` 脚本中 `spk.F`(频率)参数被接受但无实际效果。机器人不会发声
- **修复建议**: 取消注释并确保 C++ `libsnd4cj.so` 正确链接;或在 README 明确标注"扬声器仅用于延时占位"
- **关联**: 旧分析报告 D2
---
### 🟡 中等问题 (P1)
#### [BOT-S04] r"\n" 与 r'\n' 转义不一致
- **文件**: `src/P.cj:200, 202`
- **描述**: 同一文件中换行判断混用 `r'\n'`(Rune,换行符 U+000A)与 `r"\n"`(String,两字符 `\` + `n`)。Rune 与 String 比较永远为 false。
```cangjie
// 第 195 行 — 正确:r'\n' 是 Rune
else if (!nl && nm && (c == r' ' || c == r'\t' || c == r'\n')) {
// 第 200 行 — 错误:r"\n" 是 String(两字符)
else if (!nl && !nm && (c == r' ' || c == r'\t' || c == r"\n")) {
obj.submitItem()
if (c == r"\n") { count += 1 } // 第 202 行:永远为 false
}
```
- **影响**: 当 `!nl && !nm` 状态下遇到换行符,不触发 `submitItem()` 也不递增行号,而是落入第 209 行 `obj.addRune(c)` 将换行符吞入当前 item 文本。现有脚本每行都以 `;` 结尾所以不触发,但漏写 `;` 时两行内容会被合并为一行
- **修复建议**: 将第 200、202 行的 `r"\n"` 统一改为 `r'\n'`
#### [BOT-S05] MPU6050 ADDR_0 与 ADDR_1 均为 0x68
- **文件**: `src/hd/MPU6050.cj:14-15`
- **描述**: MPU6050 的两个地址常量都是 `0x68`,第二个应为 `0x69`(AD0 引脚拉高时的地址)。
```cangjie
const ADDR_0: UInt8 = 0x68 /* 默认地址 */
const ADDR_1: UInt8 = 0x68 /* 地址2 — 应为 0x69 */
```
- **影响**: 无法同时使用两个 MPU6050(I2C 地址冲突)。当前脚本中 MPU6050_BIND 块全部被注释,不触发
- **修复建议**: `const ADDR_1: UInt8 = 0x69`
- **关联**: 旧分析报告 D10
#### [BOT-S06] readI2cReg 返回 Int8,负值寄存器数据出错
- **文件**: `src/hd/I2C.cj:97`
- **描述**: `readI2cReg()` 返回 `Int8`(有符号),但 I2C 寄存器值为无符号字节。寄存器值 ≥ 128 时被解释为负数,后续计算(如 `read16BitReg` 的 `(hh << 8) | ll`)可能出错。
```cangjie
public func readI2cReg(fd: Int32, reg: UInt8): Int8 { // ← 应为 UInt8
...
var value: Int8 = 0
...
return value
}
```
- **影响**: MPU6050 传感器数据的高字节 ≥ 128 时,符号扩展导致 16 位拼接结果错误
- **修复建议**: 返回类型改为 `UInt8`;或调用处做 `UInt8(Int16(value) & 0xFF)` 显式转换
- **关联**: 旧分析报告 D9
#### [BOT-S07] Lua 脚本路径硬编码
- **文件**: `src/r.cj:998`
- **描述**: 未知 `@Block` 路由到 Lua 扩展时,脚本路径写死为 `./modules/${MtName}.lua`,不支持相对路径或环境变量查找。
```cangjie
M[name] = MtVars_Lua.Factory("./modules/${MtName}.lua", obj)
```
- **影响**: 仅当工作目录为 `bot4cj/` 时可找到 Lua 模块。从其他目录运行时 Lua 脚本加载失败
- **修复建议**: 从可执行文件路径、环境变量(如 `BOT4CJ_MODULES_PATH`)或仓颉包资源中查找
- **关联**: 代码内 FIXME 注释(r.cj:996)
#### [BOT-S08] README.robot.md 已知问题表 D6/D8 已在代码中修复但文档未更新
- **文件**: `README.robot.md:367, 369`
- **描述**: README 已知问题表中:
- **D6** "Softmax 雅可比矩阵实现错误" — 当前 `Af.cj:246-271` 的 `softmax_jacobian` 实现正确(对角线 `y_i(1-y_i)`、非对角 `-y_i*y_j`),且 `nn.cj:93-141` 的 `softmax_backward` 正确处理 n×n 雅可比矩阵
- **D8** "Tanh 导数实现错误" — 当前 `Af.cj:194-206` 的 `tanh_dot` 使用 `1.0f32 - y_val * y_val`(`y` 即 tanh 输出),这是正确的。代码注释 `NOTE(20260525-qing): 修复命名(x => y)` 表明已修复
- **影响**: 文档误导用户认为激活函数有 bug,可能误用或绕过
- **修复建议**: 更新 README.robot.md 已知问题表,将 D6/D8 标注为"已修复"
---
### 🟢 轻微问题 (P2)
#### [BOT-Q01] 无单元测试
- **文件**: 全局
- **描述**: 模块无任何自动化测试。解析器 FSM、矩阵对象、激活函数导数、硬件变量工厂等关键路径完全依赖手工验证
- **修复建议**: 添加测试框架,至少覆盖:解析器(正常/异常输入)、激活函数(前向/反向数值验证)、Matrix1 运算
- **关联**: 旧分析报告 D20
#### [BOT-Q02] 大量注释掉的旧代码
- **文件**: `src/math/nn.cj:164-332`(~170 行)、`src/main.cj:39-49,89-95`(~15 行)
- **描述**: 旧版 `Nn` 类和 `ActivationFuncs` 枚举全部注释保留,`main.cj` 的 DRL 代码也是注释状态
- **修复建议**: 移入版本历史(git),保持源码整洁
#### [BOT-Q03] I2C 锁线性扫描 O(n)
- **文件**: `src/hd/I2C.cj:145, 175, 220, 246`
- **描述**: `I2cLock_create/destroy/lock/unlock` 每次遍历 20 个元素的锁数组,O(n) 复杂度
- **影响**: 20 个槽位时影响可忽略,但多 I2C 设备高频通信时累积开销
- **修复建议**: 用 `HashMap<Int32, I2C_Lock>` 替代线性扫描
- **关联**: 旧分析报告 D4
#### [BOT-Q04] Makefile -Wall -Werror 被注释
- **文件**: `Makefile:8`
- **描述**: `FLAGS := -O3 -pipe #-Wall -Werror`,编译警告被关闭,潜在类型不匹配和未使用变量无法在编译期发现
- **修复建议**: 开启 `-Wall`,逐步修复警告后开启 `-Werror`
#### [BOT-Q05] 消费端列数不匹配时报错不可读
- **文件**: `src/r.cj:1044, 1051`
- **描述**: `run_drl_action()` 用 `ActionDefs.col`(表头列数)遍历,若数据行列数不足,`values[row][col]` 抛出通用 `IndexOutOfBoundsException`,不报行号和期望列数
- **影响**: 与 S01 叠加——解析器不拦截不一致,消费端崩在不可读的越界错误
- **修复建议**: `parse_action_matrixObject` 入口增加列数校验,或在 `MatrixObject.[]` 中增加越界检查并抛出含行号列号的异常
#### [BOT-Q06] MPU6050 后台线程无法安全退出
- **文件**: `src/hd/MPU6050.cj:123-161`
- **描述**: `mpu6050_main()` 是无限 `while(true)` 循环,`stopDaemon()` 通过 `pthread_cancel` 终止,可能在非取消点中断
- **影响**: 传感器数据可能不一致;长时间运行后线程资源泄漏
- **修复建议**: 用原子标志位(`volatile bool running`)替代 `pthread_cancel`,循环条件改为 `while(running)`
- **关联**: 旧分析报告 D3
#### [BOT-Q07] 仓颉 GC 析构时机不确定,I2C fd 可能泄漏
- **文件**: `src/r.cj:28`(MatrixVars_Hardware 基类注释)
- **描述**: 仓颉 GC 不保证在确定性时机调用析构器,`MatrixVars_Hardware` 的 `free()` 回调依赖 `main.cj:82-87` 的 `try/finally` 显式调用 `deactive()` 来释放
- **现状**: 已有 `try/finally` 保底,但若执行路径异常跳出 `try` 块(如 `active()` 抛异常),`finally` 仍会执行,问题不严重
- **修复建议**: 确保所有异常路径都经过 `finally`;考虑 RAII wrapper
- **关联**: 旧分析报告 D1
---
## 三、统计汇总
| 等级 | 数量 | 问题 ID | 状态 |
|------|------|---------|------|
| 🔴 Critical | 3 | S01, S02, S03 | 待修复 |
| 🟡 Medium | 5 | S04-S08 | 待修复 |
| 🟢 Low | 7 | Q01-Q07 | 待修复 |
| **合计** | **15** | | **待修复** |
### 按类别统计
| 类别 | 问题数 | 占比 |
|------|--------|------|
| 解析器正确性(运算符 bug、转义不一致) | 3 | 20% |
| 硬件实现缺陷(扬声器、I2C、MPU6050) | 3 | 20% |
| 文档滞后(README 已知问题表) | 1 | 7% |
| 代码质量(调试残留、注释代码、无测试) | 3 | 20% |
| 设计限制(路径硬编码、锁扫描、线程退出) | 3 | 20% |
| 消费端健壮性 | 2 | 13% |
### 与已有文档的交叉引用
| 本报告 ID | 旧分析报告 ID | 关系 |
|-----------|--------------|------|
| S01 | D11(仅说"错误信息不清晰") | **新发现根因**:运算符优先级导致检查完全失效 |
| S02 | D25 | 一致 |
| S03 | D2 | 一致 |
| S04 | — | **新发现** |
| S05 | D10 | 一致 |
| S06 | D9 | 一致 |
| S07 | — | **新发现** |
| S08 | D6, D8 | **新发现**:代码已修复,README 未更新 |
| Q01 | D20 | 一致 |
| Q02 | D24 | 一致 |
| Q03 | D4 | 一致 |
| Q05 | D3 | 一致 |
| Q07 | D1 | 一致 |
---
## 四、修复优先级建议
### 第一阶段(紧急)
1. **S01**: 修复 `submitLine` 运算符优先级 — `!=` 替代 `! ==`
2. **S02**: 删除 `parse()` 中的 `print(s)` 调试行
3. **S03**: 决策扬声器方案 — 接入 C++ 实现或明确标注占位
### 第二阶段(重要)
4. **S04**: 统一 `r'\n'` 转义
5. **S05**: 修正 MPU6050 `ADDR_1 = 0x69`
6. **S06**: `readI2cReg` 返回 `UInt8`
7. **S08**: 更新 README.robot.md,D6/D8 标注"已修复"
### 第三阶段(优化)
8. **S07**: Lua 脚本路径从环境变量查找
9. **Q01-Q07**: 添加测试、清理注释代码、优化锁扫描、开启编译警告
---
## 五、亮点
| 亮点 | 说明 |
|------|------|
| 矩阵式硬件 DSL | `.mt` 脚本统一描述硬件定义与动作序列,设计独特 |
| Lua 扩展口 | 未知 `@Block` 自动路由到 `modules/<Name>.lua`,C 层注册 I2C/SPI 函数给 Lua,实现通用硬件扩展 |
| 闭包回调架构 | `MatrixVars_Hardware` 基类用 build/play/free 三闭包统一硬件接口,JsonValue 解耦 |
| I2C 总线保护 | 20 槽锁数组,多硬件共享总线时互斥 |
| try/finally 资源释放 | 规避仓颉 GC 析构时机不确定的风险 |
| 纯仓颉 NN 库 | 不依赖 PyTorch,自实现 Nn1/Matrix1/Af/Adam/Kalman/CNN,为 DRL 闭环做准备 |
| CPU/CUDA 可切换 | Makefile `MODE=cpu/cuda` 切换矩阵运算后端 |
---
## 六、与 actpred 的集成现状
```
actpred(大脑) bot4cj(身体)
摄像头图像 + 历史动作 .mt 矩阵脚本
↓ ↓
神经网络预测动作向量 解析器 → 硬件变量
↓ ↓
action_dim=8 的连续向量 10 舵机 + 2 云台角度
↓ ↓
└────── 尚未打通的闭环 ──────────┘
```
- `r.cj:838` 注释明确意图:`TODO: 使用神经网络RNN直接根据动作+传感器脚本生成下一步动作`
- `main.cj` 预留 DRL 分支(`-i/-o` 模型加载/保存,`run_action_drl` 注释状态)
- **目前没有共享数据格式**:actpred 输出 numpy 向量,bot4cj 消费 `.mt` 矩阵
### 集成前需修复的阻塞项
| 问题 ID | 阻塞原因 |
|---------|---------|
| S01 | 解析器不校验列数,集成时自定义脚本容易出错且无报错 |
| S02 | `print(s)` 污染 stdout,管道通信时数据混淆 |
| S07 | 路径硬编码,部署时找不到 Lua 模块 |
---
## 七、附录
### 审查工具
- 人工静态分析
- 代码路径追踪
- 运算符优先级推演
- 全部 `.mt` / `.bot` 脚本列数统计验证
### 参考文档
- [bot4cj 深度分析报告](../reports/bot4cj_analysis_20260525_00.md)(28 章,1407 行,D1-D29)
- [actpred 与 bot4cj 研究报告](../reports/actpred_bot4cj_research_20260809.md)
- [仓颉编程语言](https://cangjie-lang.org/)
### 审查人
- 自动化审计助手
- 审查时间:2026-08-08
---
*本报告由自动化代码审计生成,供开发团队参考。修复建议需结合实际硬件环境评估。*