新增审计: 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
17 KiB
17 KiB
bot4cj 模块代码审计报告
审查日期:2026-08-08 (Asia/Shanghai) 审查人:Automated Review 审查范围:
bot4cj/全模块(仓颉 Cangjie 机器人硬件脚本解析器) 参考文档:深度分析报告(D1-D29)、actpred 与 bot4cj 研究报告
一、模块概述
| 属性 | 值 |
|---|---|
| 定位 | 机器人硬件脚本解析器(.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。
// 当前代码(第 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,报错信息不可读 - 修复建议: 改用
!=运算符:
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。
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(),扬声器无实际声音输出。
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。
// 第 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 引脚拉高时的地址)。
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)可能出错。
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,不支持相对路径或环境变量查找。
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)表明已修复
- D6 "Softmax 雅可比矩阵实现错误" — 当前
-
影响: 文档误导用户认为激活函数有 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 | 一致 |
四、修复优先级建议
第一阶段(紧急)
- S01: 修复
submitLine运算符优先级 —!=替代! == - S02: 删除
parse()中的print(s)调试行 - S03: 决策扬声器方案 — 接入 C++ 实现或明确标注占位
第二阶段(重要)
- S04: 统一
r'\n'转义 - S05: 修正 MPU6050
ADDR_1 = 0x69 - S06:
readI2cReg返回UInt8 - S08: 更新 README.robot.md,D6/D8 标注"已修复"
第三阶段(优化)
- S07: Lua 脚本路径从环境变量查找
- 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 深度分析报告(28 章,1407 行,D1-D29)
- actpred 与 bot4cj 研究报告
- 仓颉编程语言
审查人
- 自动化审计助手
- 审查时间:2026-08-08
本报告由自动化代码审计生成,供开发团队参考。修复建议需结合实际硬件环境评估。