Files
HomeAgent/csrc/include/ha_abi.h
JianFeeeee 8070844fe9 build(csrc): C 基础设施门禁(ABI 版本 / 告警 / sanitizer / 交叉编译 / 模糊测试)
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 公开接口。
2026-09-26 09:44:17 +08:00

81 lines
4.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 */