// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package lint import "testing" // TestABIExpectedArgSize checks the Go ABI0 argument-area size computation // against hand-verified signatures (the same layouts the go-flac kernels use). func TestABIExpectedArgSize(t *testing.T) { cases := []struct { sig string want int64 }{ {"func f()", 0}, {"func f(a int, b int)", 16}, {"func f(a int32)", 4}, // no results: no word-boundary padding {"func f(a int32) (r int32)", 12}, // results begin on a word boundary: 4 -> 8, +4 {"func f(a int) int", 16}, {"func f(s []int32, p *[32]uint16) (x uint64, ok bool)", 41}, {"func f(left, right []int32, sums *[4]uint64)", 56}, {"func f(src []byte, dst []int32)", 48}, {"func f(a bool, b int64)", 16}, // bool at 0, int64 aligned to 8 {"func f(x struct{ a int32; b int64 })", 16}, } for _, c := range cases { got, ok := abiExpectedArgSize(c.sig) if !ok { t.Errorf("%s: could not compute size", c.sig) continue } if got != c.want { t.Errorf("%s: size = %d, want %d", c.sig, got, c.want) } } } func TestABIUnknownTypeSkipped(t *testing.T) { // A bare named type of unknown size must abort the check rather than guess. // (A *pointer* to a named type is still 8 bytes and is fine.) if _, ok := abiExpectedArgSize("func f(s Stream)"); ok { t.Error("bare named type should make the size undecidable") } if _, ok := abiExpectedArgSize("func f(s *Stream)"); !ok { t.Error("pointer to a named type is decidable (8 bytes)") } } // TestABIArgSizeRule checks the lint rule end to end. func TestABIArgSizeRule(t *testing.T) { // Matching: the declared arg size agrees with the signature. clean := lintSrc(t, "#include \"textflag.h\"\n"+ "// func f(a int, b int)\n"+ "TEXT ·f(SB), NOSPLIT, $0-16\n"+ "\tMOVQ a+0(FP), AX\n"+ "\tRET\n") if codes(clean)[CodeABIArgSize] != 0 { t.Fatalf("matching arg size should not warn: %+v", clean) } // Mismatching: declared 8, signature implies 16. bad := lintSrc(t, "#include \"textflag.h\"\n"+ "// func f(a int, b int)\n"+ "TEXT ·f(SB), NOSPLIT, $0-8\n"+ "\tMOVQ a+0(FP), AX\n"+ "\tRET\n") if codes(bad)[CodeABIArgSize] != 1 { t.Fatalf("mismatching arg size should warn once: %+v", bad) } } // TestABIArgSizeSkipsRegisterABI verifies the check does not fire for functions // that declare a zero arg area (register ABI) or never touch FP. func TestABIArgSizeSkipsRegisterABI(t *testing.T) { diags := lintSrc(t, "#include \"textflag.h\"\n"+ "// func f(a int, b int)\n"+ "TEXT ·f(SB), NOSPLIT, $0-0\n"+ "\tMOVQ AX, BX\n"+ "\tRET\n") if codes(diags)[CodeABIArgSize] != 0 { t.Fatalf("register-ABI function must not be checked: %+v", diags) } } // TestUnreachableCode exercises the dead-code detection and its guard rails. func TestUnreachableCode(t *testing.T) { // Code after a RET is unreachable. dead := lintSrc(t, "#include \"textflag.h\"\n"+ "TEXT ·f(SB), NOSPLIT, $0\n"+ "\tRET\n"+ "\tMOVQ AX, BX\n") if codes(dead)[CodeUnreachable] != 1 { t.Fatalf("code after RET should be unreachable: %+v", dead) } // A label after the RET makes the following code reachable again. live := lintSrc(t, "#include \"textflag.h\"\n"+ "TEXT ·f(SB), NOSPLIT, $0\n"+ "\tRET\n"+ "again:\n"+ "\tJMP again\n") if codes(live)[CodeUnreachable] != 0 { t.Fatalf("code after a label is reachable: %+v", live) } } // TestUnreachableGuards verifies the analysis is suppressed where reachability // cannot be determined statically. func TestUnreachableGuards(t *testing.T) { // A PC-relative jump defeats the analysis for the whole function. pcrel := lintSrcArch(t, "f_amd64.s", "#include \"textflag.h\"\n"+ "TEXT ·f(SB), NOSPLIT, $0\n"+ "\tJCC 2(PC)\n"+ "\tRET\n"+ "\tMOVQ AX, BX\n") if codes(pcrel)[CodeUnreachable] != 0 { t.Fatalf("PC-relative functions must be skipped: %+v", pcrel) } // A register-indirect branch (riscv JALR) defeats the analysis too. indirect := lintSrcArch(t, "f_riscv64.s", "#include \"textflag.h\"\n"+ "TEXT ·f(SB), NOSPLIT, $0\n"+ "\tJALR X1, X5\n"+ "\tRET\n"+ "\tMOV X1, X2\n") if codes(indirect)[CodeUnreachable] != 0 { t.Fatalf("indirect-branch functions must be skipped: %+v", indirect) } }