Files
HomeAgent/csrc/CMakeLists.txt
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

122 lines
5.1 KiB
CMake
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.

cmake_minimum_required(VERSION 3.10)
project(ha_codec VERSION 0.1.0 LANGUAGES C)
# ============================================================
# ha_codec — HomeAgent 内核编解码层(C 实现)
#
# 零外部依赖,纯 C99。产出静态库供 homed 经 cgo 链接,
# 同时可独立用于其他端(鸿蒙 / 嵌入式 / C SDK)。
#
# 使用方式:
# add_subdirectory(path/to/csrc)
# target_link_libraries(my_app ha_codec)
# target_include_directories(my_app PRIVATE ${HA_CODEC_INCLUDE_DIR})
# ============================================================
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
)
if(BUILD_SHARED_LIBS)
add_library(ha_codec SHARED ${HA_CODEC_SRC})
if(WIN32)
set_target_properties(ha_codec PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
else()
add_library(ha_codec STATIC ${HA_CODEC_SRC})
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)
set(HA_CODEC_INCLUDE_DIR ${HA_CODEC_INCLUDE} CACHE INTERNAL "ha_codec include directories")
install(TARGETS ha_codec
EXPORT ha_codec-targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(DIRECTORY include/ DESTINATION include)
# ============================================================
# 测试
# ============================================================
if(BUILD_TESTS)
add_executable(ha_codec_test test/test_ha_codec.c)
target_link_libraries(ha_codec_test PRIVATE ha_codec)
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()