205 lines
5.6 KiB
Go
205 lines
5.6 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"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestDataWidth(t *testing.T) {
|
||
|
|
// Both assemblers reject the integer widths beyond 1, 2, 4 and 8 (go
|
||
|
|
// tool asm: "bad int size for DATA argument: 3").
|
||
|
|
diags := lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/3, $0x010203
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataWidth] != 1 {
|
||
|
|
t.Fatalf("want one data-width, got %+v", diags)
|
||
|
|
}
|
||
|
|
for _, d := range diags {
|
||
|
|
if d.Code == CodeDataWidth && d.Severity != Error {
|
||
|
|
t.Fatalf("bad width must be error severity: %+v", d)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The missing suffix is not a default ("expect /size").
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB), $7
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataWidth] != 1 {
|
||
|
|
t.Fatalf("missing width must be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A float needs its full 4 or 8 IEEE-754 bytes ("bad float size").
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·f+0(SB)/2, $1.5
|
||
|
|
GLOBL ·f(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataWidth] != 1 {
|
||
|
|
t.Fatalf("float in a narrow width must be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A string accepts any width: GOROOT's own messages use /84 and the
|
||
|
|
// bytes are zero-padded.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·s+0(SB)/3, $"hi"
|
||
|
|
GLOBL ·s(SB), RODATA, $3
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataWidth] != 0 {
|
||
|
|
t.Fatalf("string data in width 3 must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The valid integer widths and float widths are clean.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/1, $1
|
||
|
|
DATA ·tab+1(SB)/2, $2
|
||
|
|
DATA ·tab+4(SB)/4, $4
|
||
|
|
DATA ·f+0(SB)/4, $1.5
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
GLOBL ·f(SB), RODATA, $4
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataWidth] != 0 {
|
||
|
|
t.Fatalf("valid widths must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDataValueOverflow(t *testing.T) {
|
||
|
|
// $0x1ff into one byte: both assemblers keep 0xff silently.
|
||
|
|
diags := lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/1, $0x1ff
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataValueOverflow] != 1 {
|
||
|
|
t.Fatalf("want one data-value-overflow, got %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The full signed and unsigned range of the width round-trips.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/1, $255
|
||
|
|
DATA ·tab+1(SB)/1, $-128
|
||
|
|
DATA ·tab+2(SB)/2, $65535
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataValueOverflow] != 0 {
|
||
|
|
t.Fatalf("in-range values must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Width 8 holds every int64 (the regression where the range itself
|
||
|
|
// overflowed and flagged 0).
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/8, $0
|
||
|
|
DATA ·tab+8(SB)/8, $16
|
||
|
|
GLOBL ·tab(SB), RODATA, $16
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataValueOverflow] != 0 {
|
||
|
|
t.Fatalf("width 8 must hold every int64: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// -disable silences the rule.
|
||
|
|
f, _ := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nDATA ·tab+0(SB)/1, $0x1ff\nGLOBL ·tab(SB), RODATA, $8\n")
|
||
|
|
diags = File(f, Config{Arch: arch.AMD64, Disable: map[string]bool{CodeDataValueOverflow: true}})
|
||
|
|
if codes(diags)[CodeDataValueOverflow] != 0 {
|
||
|
|
t.Fatalf("disabled rule must stay silent: %+v", diags)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDataStringWidth(t *testing.T) {
|
||
|
|
// Both assemblers reject a string longer than the field.
|
||
|
|
diags := lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·msg+0(SB)/2, $"hello"
|
||
|
|
GLOBL ·msg(SB), RODATA, $5
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataStringWidth] != 1 {
|
||
|
|
t.Fatalf("want one data-string-width, got %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A shorter string is zero-padded to the width, the documented layout.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·msg+0(SB)/8, $"hi"
|
||
|
|
GLOBL ·msg(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataStringWidth] != 0 {
|
||
|
|
t.Fatalf("padded string must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDataWithoutGlobl(t *testing.T) {
|
||
|
|
// gasm asm rejects an unpaired initialiser ("no matching GLOBL"); go
|
||
|
|
// tool asm sizes the symbol implicitly.
|
||
|
|
diags := lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/8, $0x0102030405060708
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataNoGlobl] != 1 {
|
||
|
|
t.Fatalf("want one data-without-globl, got %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The pairing is order-independent: both assemblers resolve by name.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
DATA ·tab+0(SB)/8, $0x0102030405060708
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataNoGlobl] != 0 {
|
||
|
|
t.Fatalf("GLOBL before DATA must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/8, $0x0102030405060708
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataNoGlobl] != 0 {
|
||
|
|
t.Fatalf("DATA before GLOBL must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A static symbol pairs with its own static GLOBL.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA mask<>+0(SB)/4, $0x80020100
|
||
|
|
GLOBL mask<>(SB), RODATA, $4
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataNoGlobl] != 0 {
|
||
|
|
t.Fatalf("static pairing must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDataExceedsGlobl(t *testing.T) {
|
||
|
|
// gasm asm bounds every initialiser by the declared size; go tool asm
|
||
|
|
// grows the symbol silently, so the declared size stops matching the
|
||
|
|
// data.
|
||
|
|
diags := lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
GLOBL ·tab(SB), RODATA, $4
|
||
|
|
DATA ·tab+0(SB)/8, $0x0102030405060708
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataExceedsGlobl] != 1 {
|
||
|
|
t.Fatalf("want one data-exceeds-globl, got %+v", diags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// An initialiser that fits the declaration is clean, at either order.
|
||
|
|
diags = lintSrc(t, `
|
||
|
|
#include "textflag.h"
|
||
|
|
DATA ·tab+0(SB)/4, $1
|
||
|
|
DATA ·tab+4(SB)/4, $2
|
||
|
|
GLOBL ·tab(SB), RODATA, $8
|
||
|
|
`)
|
||
|
|
if codes(diags)[CodeDataExceedsGlobl] != 0 {
|
||
|
|
t.Fatalf("fitting initialisers must not be flagged: %+v", diags)
|
||
|
|
}
|
||
|
|
}
|