// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package verify import ( "encoding/hex" "encoding/json" "os" "runtime" "strconv" "testing" ) func loadBasicKernel(t *testing.T) *Kernel { t.Helper() requireHost(t, "amd64") k, err := Load("../testdata/verify/basic_amd64.s") if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) return k } func TestReplayEntry(t *testing.T) { k := loadBasicKernel(t) e := CorpusEntry{Func: "add", Args: []CorpusArg{ {Kind: "int", Value: "2"}, {Kind: "int", Value: "3"}, }} out, err := k.ReplayEntry("add", e) if err != nil { t.Fatalf("ReplayEntry: %v", err) } if got := int64(GetUint64(out, 16)); got != 5 { t.Errorf("replay add(2, 3) = %d, want 5", got) } } func TestCorpusRoundTrip(t *testing.T) { k := loadBasicKernel(t) e := CorpusEntry{Func: "add", Args: []CorpusArg{ {Kind: "int", Value: "20"}, {Kind: "int", Value: "22"}, }} data, err := json.Marshal(e) if err != nil { t.Fatalf("marshal: %v", err) } var back CorpusEntry if err := json.Unmarshal(data, &back); err != nil { t.Fatalf("unmarshal: %v", err) } out, err := k.ReplayEntry("add", back) if err != nil { t.Fatalf("ReplayEntry: %v", err) } if got := int64(GetUint64(out, 16)); got != 42 { t.Errorf("round-trip replay = %d, want 42", got) } } // TestGenDualArgsEntryReplayable checks that the entry recorded alongside a // generated input replays to the same observable call. func TestGenDualArgsEntryReplayable(t *testing.T) { k := loadBasicKernel(t) sig, ok := parseFuncSig("// func add(a, b int) int") if !ok { t.Fatal("parseFuncSig failed") } _, _, bufs, entry := genDualArgs(newRNG(1), sig, 24) if len(entry.Args) != 2 || entry.Args[0].Kind != "int" { t.Fatalf("unexpected entry: %+v", entry) } out, err := k.ReplayEntry("add", entry) if err != nil { t.Fatalf("ReplayEntry: %v", err) } want := int64(GetUint64(out, 16)) got := entryInt(t, entry.Args[0]) + entryInt(t, entry.Args[1]) if got != want { t.Errorf("replayed sum = %d, want %d", want, got) } runtime.KeepAlive(bufs) } func entryInt(t *testing.T, a CorpusArg) int64 { t.Helper() v, err := strconv.ParseUint(a.Value, 10, 64) if err != nil { t.Fatalf("entry value %q: %v", a.Value, err) } return int64(v) } // strProbeSrc is a kernel that consumes an ABI0 string header: it // dereferences the data pointer (proving it points at live memory) and // returns len(s) + s[0] when the string is non-empty, len(s) otherwise. const strProbeSrc = `#include "textflag.h" // func strProbe(s string) int64 TEXT ·strProbe(SB), NOSPLIT, $0-24 MOVQ s_base+0(FP), SI MOVQ s_len+8(FP), CX XORQ AX, AX TESTQ CX, CX JZ probe_done MOVB (SI), AL ADDQ AX, CX probe_done: MOVQ CX, ret+16(FP) RET ` func loadStrProbeKernel(t *testing.T) *Kernel { t.Helper() requireHost(t, "amd64") file := t.TempDir() + "/strprobe_amd64.s" if err := os.WriteFile(file, []byte(strProbeSrc), 0o644); err != nil { t.Fatalf("write kernel: %v", err) } k, err := Load(file) if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) return k } // TestStringParamRoundTrip pins the ABI0 string marshalling end to end: // genDualArgs lays a string parameter out as a two-word header pointing at // a live buffer, the corpus entry records it, and ReplayEntry rebuilds an // equivalent header. func TestStringParamRoundTrip(t *testing.T) { k := loadStrProbeKernel(t) sig, ok := parseFuncSig("// func strProbe(s string) int64") if !ok { t.Fatal("parseFuncSig failed") } args, _, bufs, entry := genDualArgs(newRNG(7), sig, 24) if len(entry.Args) != 1 || entry.Args[0].Kind != "string" { t.Fatalf("unexpected entry: %+v", entry) } out, err := k.CallFunc("strProbe", args) if err != nil { t.Fatalf("CallFunc: %v", err) } live := int64(GetUint64(out, 16)) want := int64(entry.Args[0].Len) if d, err := hex.DecodeString(entry.Args[0].Data); err != nil { t.Fatalf("entry data: %v", err) } else if len(d) > 0 { want += int64(d[0]) } if live != want { t.Errorf("live call = %d, want %d (len + first byte)", live, want) } replayed, err := k.ReplayEntry("strProbe", entry) if err != nil { t.Fatalf("ReplayEntry: %v", err) } if got := int64(GetUint64(replayed, 16)); got != want { t.Errorf("replayed call = %d, want %d", got, want) } runtime.KeepAlive(bufs) } // TestFuzzHookSavesFailures fuzzes add against the go-tool-asm build of a // sub kernel with the same signature, so every iteration mismatches (safely: // both kernels read only their own arguments) and the hook must record // replayable entries. func TestFuzzHookSavesFailures(t *testing.T) { requireHost(t, "amd64") src := `#include "textflag.h" // func add(a, b int) int TEXT ·add(SB), NOSPLIT, $0-24 MOVQ a+0(FP), AX ADDQ b+8(FP), AX MOVQ AX, ret+16(FP) RET // func sub(a, b int) int TEXT ·sub(SB), NOSPLIT, $0-24 MOVQ a+0(FP), AX SUBQ b+8(FP), AX MOVQ AX, ret+16(FP) RET ` dir := t.TempDir() file := dir + "/addsub_test_amd64.s" if err := os.WriteFile(file, []byte(src), 0o644); err != nil { t.Fatalf("write kernel: %v", err) } k, err := Load(file) if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) sig, ok := parseFuncSig("// func add(a, b int) int") if !ok { t.Fatal("parseFuncSig failed") } gt, err := GroundTruth(file) if err != nil { t.Skipf("go tool asm unavailable: %v", err) } var saved []CorpusEntry res := k.FuzzFuncHook("add", sig, gt["sub"], 5, 42, func(e CorpusEntry) { saved = append(saved, e) }) if res.Mismatches == 0 { t.Fatal("expected mismatches against the sub reference") } if len(saved) == 0 { t.Fatal("hook saved no entries despite mismatches") } for _, e := range saved { if e.Func != "add" || len(e.Args) != 2 { t.Errorf("bad entry: %+v", e) } if _, err := k.ReplayEntry(e.Func, e); err != nil { t.Errorf("saved entry does not replay: %v", err) } } }