go fuzz采用覆盖引导变异而非随机生成,依赖代表性seed语料驱动路径探索,需警惕空输入、wasm不支持及fuzz.bytes长度不确定性等问题。

Go fuzz 用的不是随机生成,而是基于覆盖引导的变异
Go 的 fuzz 并不靠纯随机造数据,它启动时先跑一遍你提供的 seed corpus(种子语料),记录函数执行路径的覆盖率信息;之后所有新输入都由已知“有效”输入变异而来——比如翻转字节、截断、插入子串、替换 ASCII 字符等。这种机制能更快撞到边界条件,但前提是你的 seed 要有代表性。
常见错误现象:fuzz 长时间卡在低覆盖率、反复生成无效格式(如对 json.Unmarshal 持续喂乱码字符串却从不触发解析逻辑)。
- seed 必须放在
fuzz目录下,文件名以.go结尾,且内容是合法 Go 字面量(如[]byte{0x7b, 0x22}),不能是注释或空行 - 避免只放一个超长字符串当 seed:fuzz 引擎更倾向局部修改,单一大块数据变异后大概率仍非法
- 多个 seed 比单个好:例如分别提供
{"a":1}、{"a":null}、{"a":[1,2]},能更快覆盖不同分支
fuzz.Intn 和 fuzz.Bytes 的行为差异直接影响变异效果
fuzz.Intn 生成的是带符号整数,且默认范围是 [0, n),而 fuzz.Bytes 返回的是可变长度切片,内容完全由引擎按当前覆盖率反馈动态调整——不是简单填充随机字节。这意味着你不能假设 fuzz.Bytes(10) 总返回 10 字节;它可能返回 0、3、17 字节,取决于哪次变异更能推进覆盖率。
使用场景:如果你测试的是协议解析器,直接用 fuzz.Bytes 更自然;但如果函数明确要求固定长度 buffer(比如 AES 块加密),就得自己截断或补零,否则会 panic 或误判。
立即学习“go语言免费学习笔记(深入)”;
-
fuzz.Intn(100)变异时优先尝试边界值:0、99、100、-1、127 等,不是均匀分布 -
fuzz.Bytes生成的切片底层内存会被复用,多次调用返回的可能是同一底层数组的不同 slice,别做==比较或长期缓存 - 不要在
FuzzXXX函数里重复调用f.Fuzz:每个f.Fuzz是独立 fuzz target,混用会导致统计错乱和覆盖率丢失
panic: runtime error: index out of range 错误常因未校验 fuzz 输入长度导致
这是最典型的 fuzz crash,表面看是数组越界,根源往往是代码假定了输入最小长度(比如取 s[0] 就认为 s 非空),而 fuzz 引擎恰恰热衷生成空或极短输入来试探边界。
性能影响:加一层 len(s) > 0 判断几乎无开销,但能避免 90% 的 trivial crash,让 fuzz 更快聚焦到真正复杂的逻辑路径上。
- 所有从
fuzz拿到的切片/字符串都应视为“任意长度”,包括 0 - 如果函数文档明确要求非空输入,就在 fuzz 测试里提前过滤:
if len(data) == 0 { return } - 注意
strings.Split、bytes.Index等函数在空输入下的行为和你的预期是否一致,它们本身不 panic,但后续索引可能崩
GOOS=js 或 GOARCH=wasm 下 fuzz 不可用,且不报明确错误
Go fuzz 是基于 runtime/coverage 和 runtime/trace 实现的,这两者目前只支持原生平台(linux/amd64、darwin/arm64 等)。当你在 wasm 或 js 构建环境下运行 go test -fuzz=.,它会静默跳过 fuzz target,甚至不提示警告,只跑普通测试。
兼容性影响:如果你的库同时面向 wasm 和 server 端,不能依赖 fuzz 测试来覆盖 wasm 特有路径;得单独写普通测试用例模拟极端输入。
- 检查当前环境是否支持 fuzz:
go env GOOS GOARCH,若输出js或wasm,就别浪费时间配 fuzz 参数 - CI 中建议加一行检查:
go list -f '{{.Fuzzing}}' . | grep -q true || (echo "fuzz not enabled"; exit 1) - 交叉编译时
go build -o bin/app.wasm -gcflags="all=-l" main.go这类命令不会触发 fuzz,哪怕源码里有FuzzXXX函数
internal/fuzz 包里,但你不需要读它——重点始终是 seed 的质量和对输入长度的敬畏。










