feat(lint): eleven new rules over directives, data and addressing

Assisted-by: GLM 5.3
This commit is contained in:
2026-10-02 00:40:54 +02:00
parent f405cea5bc
commit 69dcbec8ef
12 changed files with 1431 additions and 19 deletions
+117
View File
@@ -0,0 +1,117 @@
// 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)
}
}