feat(lint): eleven new rules over directives, data and addressing
Assisted-by: GLM 5.3
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package lint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
)
|
||||
|
||||
// checkTextDirectives holds the two directive-hygiene checks that read the
|
||||
// TEXT line against the layer it lives in: the NOFRAME flag against the
|
||||
// declared frame, and a missing argument area where the // func signature
|
||||
// implies one.
|
||||
func checkTextDirectives(t *ast.Text, cfg Config) []Diagnostic {
|
||||
var out []Diagnostic
|
||||
if t.Frame == nil || !t.Frame.Imm.HasVal {
|
||||
return out
|
||||
}
|
||||
|
||||
// NOFRAME is only valid with a frame size of zero (textflag.h); the
|
||||
// toolchain accepts a non-zero combination silently and still allocates
|
||||
// the frame, so the flag states an arrangement the directive does not
|
||||
// describe. Negative frames are the ABI-wrapper spelling and are left
|
||||
// alone: the arm64 BSD syscall stubs deliberately pair NOFRAME with $-8.
|
||||
if !cfg.Disable[CodeNoFrameFrameSize] && hasTextFlag(t, "NOFRAME") && t.Frame.Imm.Val > 0 {
|
||||
out = append(out, Diagnostic{
|
||||
Pos: t.Keyword.Pos,
|
||||
Severity: Warning,
|
||||
Code: CodeNoFrameFrameSize,
|
||||
Message: fmt.Sprintf("NOFRAME suppresses frame setup and is only valid with a zero frame size, "+
|
||||
"but this TEXT declares $%d", t.Frame.Imm.Val),
|
||||
})
|
||||
}
|
||||
|
||||
// Without NOSPLIT the stack-growth preamble runs, and the argument area
|
||||
// the wrapper laid out should be stated explicitly: an omitted area
|
||||
// records ArgsSizeUnknown (funcdata.h) in the object and go vet then
|
||||
// skips the size check against the prototype. Only functions whose
|
||||
// // func signature implies a non-zero area are flagged; with no
|
||||
// signature or a zero area, $frame alone is the normal spelling.
|
||||
if !cfg.Disable[CodeMissingArgSize] && t.Args == nil && !hasTextFlag(t, "NOSPLIT") {
|
||||
if want, ok := abiExpectedArgSize(t.Doc); ok && want > 0 {
|
||||
out = append(out, Diagnostic{
|
||||
Pos: t.Keyword.Pos,
|
||||
Severity: Warning,
|
||||
Code: CodeMissingArgSize,
|
||||
Message: fmt.Sprintf("TEXT omits the argument area; the // func signature implies %d bytes, "+
|
||||
"so the frame should read $%d-%d (an omitted area records ArgsSizeUnknown)", want, t.Frame.Imm.Val, want),
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// checkResultNaming flags result slots addressed with the generic ret
|
||||
// spelling although the // func signature names its results. The name is
|
||||
// documentation the toolchain enforces for FP references, and go vet checks
|
||||
// it against the prototype, so a signature that names a result and a body
|
||||
// that writes ret+N(FP) disagree about what the bytes mean.
|
||||
func checkResultNaming(t *ast.Text) []Diagnostic {
|
||||
results, ok := abiResultSlots(t.Doc)
|
||||
if !ok || len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
// A parameter named ret makes the spelling a parameter reference, not
|
||||
// the generic result name; the rule cannot tell them apart and stays
|
||||
// silent.
|
||||
params, pok := abiParamNames(t.Doc)
|
||||
if pok {
|
||||
for _, p := range params {
|
||||
if strings.EqualFold(p, "ret") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
var out []Diagnostic
|
||||
for _, s := range t.Body {
|
||||
in, ok := s.(*ast.Instr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, op := range in.Operands {
|
||||
sym := op.Addr.Sym
|
||||
if op.Kind != ast.OpAddr || sym == nil || sym.Pseudo != "FP" {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(sym.Name, "ret") || !sym.HasOff {
|
||||
continue
|
||||
}
|
||||
for _, r := range results {
|
||||
// A result the signature itself names ret coincides with
|
||||
// the generic spelling; there is no better name to suggest.
|
||||
if strings.EqualFold(r.name, "ret") {
|
||||
break
|
||||
}
|
||||
if sym.Offset >= r.off && sym.Offset < r.off+r.size {
|
||||
out = append(out, Diagnostic{
|
||||
Pos: op.Pos,
|
||||
Severity: Hint,
|
||||
Code: CodeUnnamedResult,
|
||||
Message: fmt.Sprintf("the // func signature names this result %q; "+
|
||||
"reference it as %q+%d(FP) rather than ret+%d(FP)", r.name, r.name, r.off, sym.Offset),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user