80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package lint
|
|
|
|
import "testing"
|
|
|
|
// TestRegisterClobber detects writes to callee-saved registers that are not
|
|
// saved and restored.
|
|
func TestRegisterClobber(t *testing.T) {
|
|
// BX (callee-saved on amd64) is written but never saved → clobbered.
|
|
clob := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tMOVQ CX, BX\n"+
|
|
"\tRET\n")
|
|
if codes(clob)[CodeRegisterClobber] != 1 {
|
|
t.Fatalf("unsaved callee-saved write should be flagged: %+v", clob)
|
|
}
|
|
|
|
// Saved and restored → preserved.
|
|
saved := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $8\n"+
|
|
"\tPUSHQ BX\n"+
|
|
"\tMOVQ CX, BX\n"+
|
|
"\tPOPQ BX\n"+
|
|
"\tRET\n")
|
|
if codes(saved)[CodeRegisterClobber] != 0 {
|
|
t.Fatalf("saved/restored register must not be flagged: %+v", saved)
|
|
}
|
|
|
|
// A caller-saved register (CX) is fine to write.
|
|
caller := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tMOVQ $1, CX\n"+
|
|
"\tRET\n")
|
|
if codes(caller)[CodeRegisterClobber] != 0 {
|
|
t.Fatalf("caller-saved register must not be flagged: %+v", caller)
|
|
}
|
|
}
|
|
|
|
// TestFuncdata validates the FUNCDATA/PCDATA structural checks.
|
|
func TestFuncdata(t *testing.T) {
|
|
// Well formed: no findings.
|
|
good := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tFUNCDATA $0, gclocals·abc(SB)\n"+
|
|
"\tPCDATA $1, $0\n"+
|
|
"\tRET\n")
|
|
if codes(good)[CodeFuncdata] != 0 {
|
|
t.Fatalf("well-formed FUNCDATA/PCDATA must not be flagged: %+v", good)
|
|
}
|
|
|
|
// FUNCDATA with one operand.
|
|
bad1 := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tFUNCDATA $0\n"+
|
|
"\tRET\n")
|
|
if codes(bad1)[CodeFuncdata] == 0 {
|
|
t.Fatal("FUNCDATA with one operand should be flagged")
|
|
}
|
|
|
|
// PCDATA with a non-immediate value.
|
|
bad2 := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tPCDATA $0, AX\n"+
|
|
"\tRET\n")
|
|
if codes(bad2)[CodeFuncdata] == 0 {
|
|
t.Fatal("PCDATA with a register value should be flagged")
|
|
}
|
|
|
|
// FUNCDATA index out of range.
|
|
bad3 := lintSrc(t, "#include \"textflag.h\"\n"+
|
|
"TEXT ·f(SB), NOSPLIT, $0\n"+
|
|
"\tFUNCDATA $99, gclocals·abc(SB)\n"+
|
|
"\tRET\n")
|
|
if codes(bad3)[CodeFuncdata] == 0 {
|
|
t.Fatal("out-of-range FUNCDATA index should be flagged")
|
|
}
|
|
}
|