118 lines
3.5 KiB
Go
118 lines
3.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package lint
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
|
)
|
|
|
|
// TestUnnamedFPReference pins the toolchain contract "cannot reference FP
|
|
// without a symbol": a numeric offset against FP parses as a memory operand
|
|
// with FP in the base, and both assemblers reject it at assembly time.
|
|
func TestUnnamedFPReference(t *testing.T) {
|
|
diags := lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $0-8
|
|
MOVQ 0(FP), AX
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeUnnamedFPRef] != 1 {
|
|
t.Fatalf("want one unnamed-fp-reference, got %+v", diags)
|
|
}
|
|
for _, d := range diags {
|
|
if d.Code == CodeUnnamedFPRef && d.Severity != Error {
|
|
t.Fatalf("unnamed FP reference must be error severity: %+v", d)
|
|
}
|
|
}
|
|
|
|
// The named spelling is the language's requirement.
|
|
diags = lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $0-8
|
|
MOVQ x+0(FP), AX
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeUnnamedFPRef] != 0 {
|
|
t.Fatalf("named FP reference must not be flagged: %+v", diags)
|
|
}
|
|
|
|
// -disable silences the rule.
|
|
f, _ := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0-8\n\tMOVQ 0(FP), AX\n\tRET\n")
|
|
diags = File(f, Config{Arch: arch.AMD64, Disable: map[string]bool{CodeUnnamedFPRef: true}})
|
|
if codes(diags)[CodeUnnamedFPRef] != 0 {
|
|
t.Fatalf("disabled rule must stay silent: %+v", diags)
|
|
}
|
|
}
|
|
|
|
// TestHardwareSPAddressing pins the one-character edge between the virtual
|
|
// frame and the real stack pointer: a negative unnamed offset against (SP)
|
|
// is the local-frame spelling with the name missing, while the runtime's
|
|
// deliberate hardware-SP references all carry positive offsets.
|
|
func TestHardwareSPAddressing(t *testing.T) {
|
|
diags := lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $16
|
|
MOVQ $1, -8(SP)
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeHardwareSP] != 1 {
|
|
t.Fatalf("want one hardware-sp-addressing, got %+v", diags)
|
|
}
|
|
|
|
// The named local in the virtual frame, the intended spelling.
|
|
diags = lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $16
|
|
MOVQ $1, x-8(SP)
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeHardwareSP] != 0 {
|
|
t.Fatalf("named SP local must not be flagged: %+v", diags)
|
|
}
|
|
|
|
// Positive hardware-SP offsets are the runtime's caller-frame idiom.
|
|
diags = lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $0
|
|
MOVQ 8(SP), AX
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeHardwareSP] != 0 {
|
|
t.Fatalf("positive hardware-SP offset must not be flagged: %+v", diags)
|
|
}
|
|
|
|
// The bare register is not a memory reference.
|
|
diags = lintSrc(t, `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $0
|
|
MOVQ SP, AX
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeHardwareSP] != 0 {
|
|
t.Fatalf("bare SP register must not be flagged: %+v", diags)
|
|
}
|
|
|
|
// The rule is architecture-neutral: riscv64 numeric (SP) parses the
|
|
// same way (the parser documents 0(SP) as a RISC-V shape).
|
|
diags = lintSrcArch(t, "f_riscv64.s", `
|
|
#include "textflag.h"
|
|
TEXT ·f(SB), NOSPLIT, $16
|
|
MOV $1, -8(SP)
|
|
RET
|
|
`)
|
|
if codes(diags)[CodeHardwareSP] != 1 {
|
|
t.Fatalf("want one hardware-sp-addressing on riscv64, got %+v", diags)
|
|
}
|
|
|
|
// -disable silences the rule.
|
|
f, _ := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $16\n\tMOVQ $1, -8(SP)\n\tRET\n")
|
|
diags = File(f, Config{Arch: arch.AMD64, Disable: map[string]bool{CodeHardwareSP: true}})
|
|
if codes(diags)[CodeHardwareSP] != 0 {
|
|
t.Fatalf("disabled rule must stay silent: %+v", diags)
|
|
}
|
|
}
|