diff --git a/lint/lint.go b/lint/lint.go index eccc867..aa5c9eb 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -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) diff --git a/lint/lint_test.go b/lint/lint_test.go index 917fa14..8eb7c50 100644 --- a/lint/lint_test.go +++ b/lint/lint_test.go @@ -309,3 +309,29 @@ TEXT ·f(SB), NOSPLIT, $16-0 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) + } +}