feat(lint): add unused-label and invalid-textflag rules
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user