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.
|
||||
const (
|
||||
CodeUnknownInstr = "unknown-instruction"
|
||||
CodeOperandCount = "operand-count"
|
||||
CodeUndefinedLabel = "undefined-label"
|
||||
CodeDuplicateLabel = "duplicate-label"
|
||||
CodeMissingRet = "missing-ret"
|
||||
CodeMissingTextflag = "missing-textflag-include"
|
||||
CodeUnreachable = "unreachable-code"
|
||||
CodeABIArgSize = "abi-argsize"
|
||||
CodeRegisterClobber = "register-clobber"
|
||||
CodeFuncdata = "funcdata-pcdata"
|
||||
CodeUnusedLabel = "unused-label"
|
||||
CodeInvalidFlag = "invalid-textflag"
|
||||
CodeStackImbalance = "stack-imbalance"
|
||||
CodeUnknownInstr = "unknown-instruction"
|
||||
CodeOperandCount = "operand-count"
|
||||
CodeUndefinedLabel = "undefined-label"
|
||||
CodeDuplicateLabel = "duplicate-label"
|
||||
CodeMissingRet = "missing-ret"
|
||||
CodeMissingTextflag = "missing-textflag-include"
|
||||
CodeUnreachable = "unreachable-code"
|
||||
CodeABIArgSize = "abi-argsize"
|
||||
CodeRegisterClobber = "register-clobber"
|
||||
CodeFuncdata = "funcdata-pcdata"
|
||||
CodeUnusedLabel = "unused-label"
|
||||
CodeInvalidFlag = "invalid-textflag"
|
||||
CodeStackImbalance = "stack-imbalance"
|
||||
CodeRegisterWidthMismatch = "register-width-mismatch"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
for _, op := range st.Operands {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if min == max {
|
||||
return fmt.Sprintf("%d operand(s)", min)
|
||||
|
||||
Reference in New Issue
Block a user