Files
gasm-sdk/lint/analysis.go
T

97 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lint
import (
"fmt"
"strings"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
)
// checkFuncdata validates the structure of FUNCDATA and PCDATA directives,
// which carry the GC stack-map information. The checks are deliberately
// shallow — they confirm the operands are well formed and that a literal index
// is within the small range the runtime uses — and never try to interpret a
// named index constant such as $PCDATA_StackMapIndex.
func checkFuncdata(t *ast.Text, cfg Config) []Diagnostic {
var out []Diagnostic
for _, s := range t.Body {
in, ok := s.(*ast.Instr)
if !ok {
continue
}
switch strings.ToUpper(in.Mnemonic.Text) {
case "FUNCDATA":
out = append(out, checkFunCDATA(in, cfg)...)
case "PCDATA":
out = append(out, checkPCDATA(in, cfg)...)
}
}
return out
}
// checkFunCDATA validates `FUNCDATA $index, symbol(SB)`.
func checkFunCDATA(in *ast.Instr, cfg Config) []Diagnostic {
if cfg.Disable[CodeFuncdata] {
return nil
}
var out []Diagnostic
if len(in.Operands) != 2 {
return []Diagnostic{{
Pos: in.Mnemonic.Pos, End: in.Mnemonic.End, Severity: Warning, Code: CodeFuncdata,
Message: fmt.Sprintf("FUNCDATA expects 2 operands (index, symbol), got %d", len(in.Operands)),
}}
}
out = append(out, checkIndex(in.Operands[0], "FUNCDATA")...)
if sym := in.Operands[1].Addr.Sym; in.Operands[1].Kind != ast.OpAddr || sym == nil {
out = append(out, Diagnostic{
Pos: in.Operands[1].Pos, Severity: Warning, Code: CodeFuncdata,
Message: "FUNCDATA second operand must be a symbol reference",
})
}
return out
}
// checkPCDATA validates `PCDATA $index, $value`.
func checkPCDATA(in *ast.Instr, cfg Config) []Diagnostic {
if cfg.Disable[CodeFuncdata] {
return nil
}
if len(in.Operands) != 2 {
return []Diagnostic{{
Pos: in.Mnemonic.Pos, End: in.Mnemonic.End, Severity: Warning, Code: CodeFuncdata,
Message: fmt.Sprintf("PCDATA expects 2 operands (index, value), got %d", len(in.Operands)),
}}
}
var out []Diagnostic
out = append(out, checkIndex(in.Operands[0], "PCDATA")...)
if in.Operands[1].Kind != ast.OpImmediate {
out = append(out, Diagnostic{
Pos: in.Operands[1].Pos, Severity: Warning, Code: CodeFuncdata,
Message: "PCDATA value must be an immediate",
})
}
return out
}
// checkIndex validates an immediate index operand. A literal index must lie in
// the small range the runtime uses; a named constant (e.g. $PCDATA_StackMapIndex)
// cannot be evaluated and is accepted without a range check.
func checkIndex(op *ast.Operand, directive string) []Diagnostic {
if op.Kind != ast.OpImmediate {
return []Diagnostic{{
Pos: op.Pos, Severity: Warning, Code: CodeFuncdata,
Message: directive + " index must be an immediate",
}}
}
if op.Imm.HasVal && (op.Imm.Val < 0 || op.Imm.Val > 10) {
return []Diagnostic{{
Pos: op.Pos, Severity: Warning, Code: CodeFuncdata,
Message: fmt.Sprintf("%s index %d is outside the valid range 0–10", directive, op.Imm.Val),
}}
}
return nil
}