feat(lint): add register-width-mismatch rule
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
+82
-13
@@ -63,19 +63,20 @@ type Config struct {
|
|||||||
|
|
||||||
// Rule codes.
|
// Rule codes.
|
||||||
const (
|
const (
|
||||||
CodeUnknownInstr = "unknown-instruction"
|
CodeUnknownInstr = "unknown-instruction"
|
||||||
CodeOperandCount = "operand-count"
|
CodeOperandCount = "operand-count"
|
||||||
CodeUndefinedLabel = "undefined-label"
|
CodeUndefinedLabel = "undefined-label"
|
||||||
CodeDuplicateLabel = "duplicate-label"
|
CodeDuplicateLabel = "duplicate-label"
|
||||||
CodeMissingRet = "missing-ret"
|
CodeMissingRet = "missing-ret"
|
||||||
CodeMissingTextflag = "missing-textflag-include"
|
CodeMissingTextflag = "missing-textflag-include"
|
||||||
CodeUnreachable = "unreachable-code"
|
CodeUnreachable = "unreachable-code"
|
||||||
CodeABIArgSize = "abi-argsize"
|
CodeABIArgSize = "abi-argsize"
|
||||||
CodeRegisterClobber = "register-clobber"
|
CodeRegisterClobber = "register-clobber"
|
||||||
CodeFuncdata = "funcdata-pcdata"
|
CodeFuncdata = "funcdata-pcdata"
|
||||||
CodeUnusedLabel = "unused-label"
|
CodeUnusedLabel = "unused-label"
|
||||||
CodeInvalidFlag = "invalid-textflag"
|
CodeInvalidFlag = "invalid-textflag"
|
||||||
CodeStackImbalance = "stack-imbalance"
|
CodeStackImbalance = "stack-imbalance"
|
||||||
|
CodeRegisterWidthMismatch = "register-width-mismatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
|
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
|
||||||
@@ -299,6 +300,21 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register-width mismatch: amd64 instructions with Q suffix
|
||||||
|
// should use 64-bit registers, L/W/B suffix should use
|
||||||
|
// 32/16/8-bit registers.
|
||||||
|
if cfg.Arch == arch.AMD64 && !cfg.Disable[CodeRegisterWidthMismatch] && !isMacroInvocation(mnem, macros) {
|
||||||
|
if msg := checkRegisterWidth(upper, st.Operands); msg != "" {
|
||||||
|
out = append(out, Diagnostic{
|
||||||
|
Pos: st.Mnemonic.Pos,
|
||||||
|
End: st.Mnemonic.End,
|
||||||
|
Severity: Warning,
|
||||||
|
Code: CodeRegisterWidthMismatch,
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if isJump(cfg.Arch, upper) {
|
if isJump(cfg.Arch, upper) {
|
||||||
for _, op := range st.Operands {
|
for _, op := range st.Operands {
|
||||||
if name, pos, ok := localLabelRef(op); ok && !tab.IsRegister(name) && !arch.IsPseudoReg(name) {
|
if name, pos, ok := localLabelRef(op); ok && !tab.IsRegister(name) && !arch.IsPseudoReg(name) {
|
||||||
@@ -714,6 +730,59 @@ func isSPReg(op *ast.Operand, a arch.Arch) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkRegisterWidth detects amd64 register-width mismatches: a Q-suffix
|
||||||
|
// instruction (64-bit) using a 32-bit register, or an L/W/B-suffix
|
||||||
|
// instruction using a 64-bit register.
|
||||||
|
func checkRegisterWidth(mnem string, ops []*ast.Operand) string {
|
||||||
|
// Determine expected width from mnemonic suffix.
|
||||||
|
var expected int // 0=unknown, 8/4/2/1=bytes
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(mnem, "Q"):
|
||||||
|
expected = 8
|
||||||
|
case strings.HasSuffix(mnem, "L"):
|
||||||
|
expected = 4
|
||||||
|
case strings.HasSuffix(mnem, "W"):
|
||||||
|
expected = 2
|
||||||
|
case strings.HasSuffix(mnem, "B"):
|
||||||
|
expected = 1
|
||||||
|
default:
|
||||||
|
return "" // no suffix, can't determine width
|
||||||
|
}
|
||||||
|
for _, op := range ops {
|
||||||
|
if op.Kind != ast.OpAddr || op.Addr.Sym == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.ToLower(op.Addr.Sym.Name)
|
||||||
|
regWidth := amd64RegWidth(name)
|
||||||
|
if regWidth == 0 {
|
||||||
|
continue // not a register or unknown
|
||||||
|
}
|
||||||
|
if expected == 8 && regWidth == 4 {
|
||||||
|
return fmt.Sprintf("%s uses 32-bit register %s (expected 64-bit)", mnem, op.Addr.Sym.Name)
|
||||||
|
}
|
||||||
|
if expected == 4 && regWidth == 8 {
|
||||||
|
return fmt.Sprintf("%s uses 64-bit register %s (expected 32-bit)", mnem, op.Addr.Sym.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// amd64RegWidth returns the width in bytes of an amd64 register name.
|
||||||
|
func amd64RegWidth(name string) int {
|
||||||
|
switch name {
|
||||||
|
case "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp",
|
||||||
|
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15":
|
||||||
|
return 8
|
||||||
|
case "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp":
|
||||||
|
return 4
|
||||||
|
case "ax", "bx", "cx", "dx", "si", "di", "bp", "sp":
|
||||||
|
return 2
|
||||||
|
case "al", "bl", "cl", "dl", "ah", "bh", "ch", "dh":
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func countRange(min, max int) string {
|
func countRange(min, max int) string {
|
||||||
if min == max {
|
if min == max {
|
||||||
return fmt.Sprintf("%d operand(s)", min)
|
return fmt.Sprintf("%d operand(s)", min)
|
||||||
|
|||||||
@@ -309,3 +309,29 @@ TEXT ·f(SB), NOSPLIT, $16-0
|
|||||||
t.Fatalf("balanced stack must not be flagged: %+v", diags)
|
t.Fatalf("balanced stack must not be flagged: %+v", diags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterWidthMismatch(t *testing.T) {
|
||||||
|
// MOVQ with 32-bit register — mismatch.
|
||||||
|
diags := lintSrc(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·f(SB), NOSPLIT, $0
|
||||||
|
MOVQ EAX, BX
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if codes(diags)[CodeRegisterWidthMismatch] != 1 {
|
||||||
|
t.Fatalf("want one register-width-mismatch, got %+v", diags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterWidthCorrect(t *testing.T) {
|
||||||
|
// MOVQ with 64-bit registers — correct.
|
||||||
|
diags := lintSrc(t, `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·f(SB), NOSPLIT, $0
|
||||||
|
MOVQ RAX, RBX
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if codes(diags)[CodeRegisterWidthMismatch] != 0 {
|
||||||
|
t.Fatalf("correct width must not be flagged: %+v", diags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user