feat(lint): add unused-label and invalid-textflag rules

Assisted-by: MiMo V2.5 Pro
This commit is contained in:
2026-08-21 00:35:21 +02:00
parent ae550cc05a
commit f1960febdc
2 changed files with 106 additions and 0 deletions
+57
View File
@@ -74,8 +74,17 @@ const (
CodeNosplitFrame = "nosplit-frame"
CodeRegisterClobber = "register-clobber"
CodeFuncdata = "funcdata-pcdata"
CodeUnusedLabel = "unused-label"
CodeInvalidFlag = "invalid-textflag"
)
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
var knownTextFlags = map[string]bool{
"NOSPLIT": true, "DUPOK": true, "RODATA": true, "NOPROF": true,
"WRIT": true, "TLSBSS": true, "NOFRAME": true, "REFLECTED": true,
"TOPFRAME": true, "ABIWRAPPER": true,
}
// pseudoOps are assembler pseudo-operations that are valid instruction-position
// tokens but are not machine instructions and so absent from the arch tables.
var pseudoOps = map[string]bool{
@@ -141,6 +150,38 @@ func File(f *ast.File, cfg Config) []Diagnostic {
})
}
// Invalid TEXT/GLOBL flags.
if !cfg.Disable[CodeInvalidFlag] {
for _, d := range f.Decls {
var flags []string
var pos token.Position
switch dd := d.(type) {
case *ast.Text:
flags = dd.Flags
pos = dd.Keyword.Pos
case *ast.Globl:
flags = dd.Flags
if dd.Name != nil {
pos = dd.Name.Pos
}
}
for _, fl := range flags {
// Numeric flags (1, 8, 9) are legacy Go toolchain constants.
if fl >= "0" && fl <= "9" {
continue
}
if !knownTextFlags[fl] {
out = append(out, Diagnostic{
Pos: pos,
Severity: Warning,
Code: CodeInvalidFlag,
Message: fmt.Sprintf("unknown TEXT/GLOBL flag %q", fl),
})
}
}
}
}
sortDiagnostics(out)
return out
}
@@ -282,6 +323,22 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
}
}
// Unused labels: defined but never referenced. Suppressed when macros
// or indirect branches are present (the reference may be invisible).
if doLabelChecks && !cfg.Disable[CodeUnusedLabel] && !hasIndirect {
for name, pos := range defined {
if _, ok := referenced[name]; !ok {
out = append(out, Diagnostic{
Pos: pos,
End: token.Position{Line: pos.Line, Column: pos.Column + len(name)},
Severity: Hint,
Code: CodeUnusedLabel,
Message: fmt.Sprintf("label %q is defined but never referenced", name),
})
}
}
}
// Missing RET heuristic. Functions that invoke a macro are skipped: the
// macro body (opaque to us) may supply the RET.
if doLabelChecks && !cfg.Disable[CodeMissingRet] && instrCount > 0 && !hasRet && !lastTerminal && !hasMacro {
+49
View File
@@ -233,3 +233,52 @@ TEXT ·f(SB), NOSPLIT, $0
t.Fatalf("label rules should be suppressed in macro files: %+v", diags)
}
}
func TestUnusedLabel(t *testing.T) {
diags := lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT, $0
unused:
ADDQ $1, AX
RET
`)
if codes(diags)[CodeUnusedLabel] != 1 {
t.Fatalf("want one unused-label, got %+v", diags)
}
}
func TestUsedLabelNotFlagged(t *testing.T) {
diags := lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT, $0
loop:
ADDQ $1, AX
JMP loop
RET
`)
if codes(diags)[CodeUnusedLabel] != 0 {
t.Fatalf("used label must not be flagged: %+v", diags)
}
}
func TestInvalidTextflag(t *testing.T) {
diags := lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT|BOGUS, $0
RET
`)
if codes(diags)[CodeInvalidFlag] != 1 {
t.Fatalf("want one invalid-textflag, got %+v", diags)
}
}
func TestValidTextflags(t *testing.T) {
diags := lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT|NOFRAME|DUPOK, $0
RET
`)
if codes(diags)[CodeInvalidFlag] != 0 {
t.Fatalf("valid flags must not be flagged: %+v", diags)
}
}