mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
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 公开接口。
This commit is contained in:
@ -15,6 +15,19 @@ project(ha_codec VERSION 0.1.0 LANGUAGES C)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build ha_codec as shared library" OFF)
|
||||
option(BUILD_TESTS "Build ha_codec tests" OFF)
|
||||
option(BUILD_FUZZ "Build libFuzzer targets" OFF)
|
||||
option(BUILD_BENCH "Build micro benchmarks" OFF)
|
||||
|
||||
# ★ C99 而非编译器默认档(clang 默认 gnu17)。
|
||||
# 理由:内核 C 侧的编译契约是 C99(cgo CFLAGS 与这里必须一致),
|
||||
# 在更新的默认档下编译会**静默**通过,而 Go 侧用 -std=c99 编不过
|
||||
# —— 两边同时构建、行为却分叉,是最难查的一类问题。
|
||||
# 实测:本轮 -Wpedantic 门禁就当场抓到过 C11 特性(_Static_assert)。
|
||||
# C90/C95 不支持:ha_abi.h 用了 // 注释与 stdint。
|
||||
# C11 开关保留给想验证「未来切到 C11 也不坏」的人。
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF) # 禁用 gnu99 扩展,严格 -std=c99
|
||||
|
||||
set(HA_CODEC_SRC
|
||||
src/ha_codec.c
|
||||
@ -32,6 +45,12 @@ endif()
|
||||
set(HA_CODEC_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(ha_codec PUBLIC ${HA_CODEC_INCLUDE})
|
||||
|
||||
# 告警门禁:零告警才允许通过(与 Makefile 的 csrc-lint 同一标准)。
|
||||
# C 侧没有 Go 的 vet 等价物,告警是唯一的静态信号。
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion)
|
||||
endif()
|
||||
|
||||
# 不链接任何外部库 —— 保持与 ha_remotedevice 同一克制标准
|
||||
target_link_libraries(ha_codec PRIVATE)
|
||||
|
||||
@ -55,3 +74,48 @@ if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_test(NAME ha_codec_test COMMAND ha_codec_test)
|
||||
endif()
|
||||
|
||||
# ============================================================
|
||||
# 模糊测试:编码语义与内存安全的持续检验
|
||||
#
|
||||
# 动机:ha_codec 声称**逐值等价于 Go 参考实现**,其中最关键的一条是
|
||||
# 「对畸形 UTF-8 的解码边界与 Go 的 utf8.DecodeRuneInString 一致」。
|
||||
# 该行为在正常输入下永远测不到 —— 只有随机字节才能覆盖
|
||||
# 截断序列 / 过长编码 / 代理对 / 超 U+10FFFF / 嵌入 NUL。
|
||||
# Go 侧已有 TestGolden_InvalidUTF8(3000 组随机字节)做等价钉死;
|
||||
# C 侧则需要独立验证两件事:
|
||||
# 1. 任何输入都不崩、不越界(内存安全)
|
||||
# 2. 返回值不违反头文件声明的不变式(0 <= keep <= len 等)
|
||||
# 两者在 libFuzzer 上是持续的,而不是等下一次手写用例。
|
||||
#
|
||||
# 需 clang + -fsanitize=fuzzer;无则明确跳过(门禁不能假装通过)。
|
||||
# ============================================================
|
||||
if(BUILD_FUZZ)
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
foreach(fz IN ITEMS test_fuzz_ha_codec)
|
||||
add_executable(${fz} test/${fz}.c)
|
||||
target_link_libraries(${fz} PRIVATE ha_codec)
|
||||
target_compile_options(${fz} PRIVATE
|
||||
-fsanitize=fuzzer,address,undefined
|
||||
-fno-omit-frame-pointer)
|
||||
target_link_options(${fz} PRIVATE
|
||||
-fsanitize=fuzzer,address,undefined)
|
||||
endforeach()
|
||||
else()
|
||||
message(WARNING
|
||||
"BUILD_FUZZ=ON 需要 clang(libFuzzer);当前编译器是 "
|
||||
"${CMAKE_C_COMPILER_ID},已跳过。")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ============================================================
|
||||
# 微基准:C 侧自身的开销(与 Go 侧 codec_bench_test.go 对照)
|
||||
#
|
||||
# 为什么 C 侧也要基准:Go 侧基准里,「C 实现省下的时间」与「cgo 边界成本」
|
||||
# 是混在一起的。若 C 侧本身在某场景很慢,改 Go 绑定无济于事;
|
||||
# 必须在能隔离处(纯 C、无边界)测出函数体成本,才知道该优化谁。
|
||||
# ============================================================
|
||||
if(BUILD_BENCH)
|
||||
add_executable(ha_codec_bench bench/bench_ha_codec.c)
|
||||
target_link_libraries(ha_codec_bench PRIVATE ha_codec)
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user