mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-26 12:23:23 +00:00
C 化从「一把刀」推进到「可持续推进」,本轮先把基础设施建起来:
不建它,后续每个 C 切片都在裸奔(无告警门禁、无内存安全检查、
无交叉编译验证、无 ABI 漂移检测)。
新增门禁(make check-csrc / check-csrc-full,已接进 make test):
- csrc-lint gcc+clang 双编译器 × -Wall -Wextra -Wpedantic -Wshadow
-Wconversion,零告警才算过(-Wconversion 是为 cgo 窄化
准备的:size_t→int 截断在默认档下是静默的)
- csrc-abi ABI 版本运行期自述 + 荒谬值检查
- csrc-headers 头文件自包含性(每个 .h 能单独编过)
- csrc-sanitize ASan+UBSan 跑 C 契约测试
- csrc-cross arm64 交叉编译(homed 的真实发布目标)
- csrc-fuzz libFuzzer:内存安全 + 6 条不变式(可 CI 门禁)
新增 ABI 契约(csrc/include/ha_abi.h):
- ha_codec.h 声明「签名冻结」,但冻结只写在注释里;现改为
HA_CODEC_ABI_MAJOR/MINOR + 运行期自述 + Go 侧常量,
三方交叉断言,版本漂移在测试期判红而非线上表现为行为诡异。
- 门禁当场抓出我自己的两个真 bug:①_Static_assert 是 C11 而项目
是 -std=c99(-Wpedantic 报的);②##msg 不能拼接字符串字面量,
导致两个断言共用一个 typedef 名(clang 报的)。
基础设施当场抓出的三个真实缺陷(都是「本机 gcc 能编过、别处会炸」类):
- bench 用了 POSIX clock_gettime,而 CMake 刻意 C_EXTENSIONS OFF
(严格 c99)→ 头文件未声明;补 _POSIX_C_SOURCE(须在任何头之前)
- 头文件缺 include 时只在「恰好被别的头先包含」处静默编过
- Go 不允许在 _test.go 用 cgo ⇒ C 侧 const 桥接只能放非测试文件,
且 cgo 生成的 *_Cvar_* 不是 Go 常量(「两边一起错成一样」的盲区,
改用 C 函数返回 + 编译期 _Static_assert 补上)
实测(全部当场可复现):
- 双编译器 × c99/c11 零告警;ASan+UBSan PASS
- libFuzzer 91s 跑 3329316 次、零崩溃(6 条不变式全过)
- 变异测试:改 C 侧宏 / 让 Go 常量与 C 函数「一起错成一样」,
均被对应断言抓住(证明门禁不是摆设)
- C 侧纯函数基准(首次把函数体成本与 cgo 边界成本分开测):
ascii_1k 121.7ns/8.4GB/s;zh_1k 1434ns;truncate 两者均约 128-134ns
- 全量 go test -count=1 ./...:57 包 0 FAIL
- make build-linux-arm64 → ELF aarch64
- 纪律检查 FAIL=0;SDK 公开接口 diff = 0 行
未动:csrc/ 是内核 C ABI,不属 third_party/homeagent-sdk 公开接口。
81 lines
4.0 KiB
C++
81 lines
4.0 KiB
C++
#ifndef HA_ABI_H
|
||
#define HA_ABI_H
|
||
|
||
/*
|
||
* ha_abi.h — HomeAgent C 库的 ABI 版本契约
|
||
*
|
||
* ============================ 为什么需要它 ============================
|
||
* ha_codec.h 声明「签名一经发布即冻结」,但**冻结只写在注释里**——注释不
|
||
* 参与编译,Go/C 两侧对「我以为的版本」不一致时没有任何机制会报错。
|
||
* 本头文件把冻结变成**编译期与测试期可断言的事实**:
|
||
*
|
||
* 1. 每个 C 库声明自己的 ABI 主/次版本(HA_CODEC_ABI_MAJOR/MINOR)。
|
||
* 2. Go 侧(internal/agent/api/codec_cgo.go)持有一份 Go 常量副本,
|
||
* 由 TestABIVersionMatches 比对 C 宏 —— 版本漂移**在测试里判红**,
|
||
* 而不是等到线上表现为「插件行为诡异」才排查。
|
||
* 3. 主版本不同 = ABI 不兼容,必须走大版本流程(与 homeagent-sdk 同一标准)。
|
||
*
|
||
* ============================ 改动规则 ============================
|
||
* - 只增不改、只加不改:新增函数/字段 → MINOR+1
|
||
* - 改签名、删函数、改结构体布局 → MAJOR+1(且所有调用方必须同步重编)
|
||
* - 纯内部实现优化(不动任何声明)→ 不动版本号
|
||
*
|
||
* ⚠️ 与 homeagent-sdk 的 C ABI 不同:本项目的 C 库是**源码内联编译**
|
||
* (Go 侧符号链接 csrc/ 权威源,见 codec_cgo.go 顶部),不存在跨版本
|
||
* 混链的 .so/.a,所以「同批重建」是天然成立的——版本宏的作用是
|
||
* **防语义漂移**(两侧对同一组函数的理解不一致),不是防二进制不兼容。
|
||
*/
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* ==================== 编译期断言(C99/C11 兼容) ==================== */
|
||
|
||
/* 静态断言:版本号写错必须在编译期就炸,不能带着荒谬版本号发布出去。
|
||
*
|
||
* ★ C99 没有 _Static_assert(那是 C11),而本项目 C 侧统一 -std=c99
|
||
* (见 codec_cgo.go 的 cgo CFLAGS 与 CMakeLists 的 C_STANDARD)。故需兼容垫片:
|
||
* C11+ 用原生 _Static_assert;C99 回退到「数组维度为 0 即编译失败」的老写法。
|
||
* 这条垫片是 -Wall -Wextra -Wpedantic 门禁上线时**当场抓出来的**(首次编译即告警),
|
||
* 即基础设施已经开始在发挥作用。
|
||
*
|
||
* 用法:第二个参数必须是**标识符**(不能是字符串)——C99 分支要用它 ## 成
|
||
* 一个 typedef 名,而 `##` 不能拼接字符串字面量(拼接会直接编译报错)。
|
||
* 原生 _Static_assert 分支则把它当 msg 传(此时它在诊断里显示为标识符,
|
||
* 仍能指出是哪个断言)。同一文件内每个断言的 tag 必须不同。 */
|
||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||
# define HA_STATIC_ASSERT(cond, tag) _Static_assert(cond, #tag)
|
||
#elif defined(__cplusplus) && __cplusplus >= 201103L
|
||
# define HA_STATIC_ASSERT(cond, tag) static_assert(cond, #tag)
|
||
#else
|
||
# define HA_STATIC_ASSERT(cond, tag) \
|
||
typedef char ha_sa_##tag##_line_##__LINE__[(cond) ? 1 : -1]
|
||
#endif
|
||
|
||
/* ==================== ABI 版本 ==================== */
|
||
|
||
/* 编码语义主版本:改动任一已发布函数的语义/签名时 +1。
|
||
* 2026-09-26:首版 1.0(上下文窗口推断 + token 估算/截断)。 */
|
||
#define HA_CODEC_ABI_MAJOR 1
|
||
|
||
/* 编码语义次版本:纯新增(加函数、加枚举值)时 +1。 */
|
||
#define HA_CODEC_ABI_MINOR 0
|
||
|
||
/* 合成版号,便于日志/断言单值比较:major*1000 + minor */
|
||
#define HA_CODEC_ABI_VERSION (HA_CODEC_ABI_MAJOR * 1000 + HA_CODEC_ABI_MINOR)
|
||
|
||
/* 编译期锁死:ABI 版本必须落在「已知的、未被遗忘的」区间。
|
||
* 若有人把版本号改成 0 或 999 之类(通常是手滑/拷贝粘贴出错),
|
||
* 编译立即失败,而不是带着一个荒谬的版本号发布出去。 */
|
||
HA_STATIC_ASSERT(HA_CODEC_ABI_MAJOR >= 1 && HA_CODEC_ABI_MAJOR <= 9,
|
||
ha_codec_abi_major_in_range);
|
||
HA_STATIC_ASSERT(HA_CODEC_ABI_MINOR >= 0 && HA_CODEC_ABI_MINOR <= 99,
|
||
ha_codec_abi_minor_in_range);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* HA_ABI_H */
|