137 lines
3.6 KiB
Go
137 lines
3.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package verify
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// FuncSig is a parsed // func signature from the assembly source.
|
|
type FuncSig struct {
|
|
Name string
|
|
Params []Param
|
|
Results []Param
|
|
}
|
|
|
|
// Param is a single function parameter.
|
|
type Param struct {
|
|
Name string
|
|
Typ string // "[]byte", "[]int32", "int", "*[32]uint16", etc.
|
|
}
|
|
|
|
// IsPointer reports whether the parameter type is a pointer or slice
|
|
// (i.e., it needs a buffer address in the argument block).
|
|
func (p Param) IsPointer() bool {
|
|
return strings.HasPrefix(p.Typ, "[]") || strings.HasPrefix(p.Typ, "*")
|
|
}
|
|
|
|
// ParseFuncSig extracts the function signature from a "// func ..." comment.
|
|
func ParseFuncSig(comment string) (FuncSig, bool) {
|
|
m := funcSigRe.FindStringSubmatch(strings.TrimSpace(comment))
|
|
if m == nil {
|
|
return FuncSig{}, false
|
|
}
|
|
sig := FuncSig{Name: m[1]}
|
|
sig.Params = parseParamsExported(m[2])
|
|
// Results may be "(a int, b int)" or "int" or "(int, error)".
|
|
res := strings.TrimSpace(m[3])
|
|
res = strings.TrimPrefix(res, "(")
|
|
res = strings.TrimSuffix(res, ")")
|
|
if res != "" {
|
|
sig.Results = parseParamsExported(res)
|
|
}
|
|
return sig, true
|
|
}
|
|
|
|
// parseParamsExported splits "a []byte, b []int32" into typed parameters.
|
|
func parseParamsExported(s string) []Param {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
var out []Param
|
|
for _, field := range strings.Split(s, ",") {
|
|
field = strings.TrimSpace(field)
|
|
if field == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(field)
|
|
if len(parts) == 1 {
|
|
// Unnamed: "int" or "[]byte".
|
|
out = append(out, Param{Typ: parts[0]})
|
|
} else {
|
|
// Named: "a []byte".
|
|
out = append(out, Param{Name: parts[0], Typ: parts[1]})
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ExtractFuncSig extracts the // func signature for the named function
|
|
// from the assembly source.
|
|
func ExtractFuncSig(src, funcName string) (FuncSig, bool) {
|
|
lines := strings.Split(src, "\n")
|
|
for i, line := range lines {
|
|
if sig, ok := ParseFuncSig(line); ok && sig.Name == funcName {
|
|
// Verify the next non-comment line is the TEXT directive.
|
|
for j := i + 1; j < len(lines); j++ {
|
|
trimmed := strings.TrimSpace(lines[j])
|
|
if trimmed == "" || strings.HasPrefix(trimmed, "//") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(trimmed, "TEXT") {
|
|
return sig, true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return FuncSig{}, false
|
|
}
|
|
|
|
// ArgLayout computes the ABI0 argument layout for a function signature.
|
|
// Returns the offset of each parameter in the argument block.
|
|
func ArgLayout(sig FuncSig) []ArgOffset {
|
|
var offsets []ArgOffset
|
|
off := 0
|
|
for _, p := range sig.Params {
|
|
size := paramSize(p.Typ)
|
|
offsets = append(offsets, ArgOffset{Name: p.Name, Typ: p.Typ, Offset: off, Size: size, IsPtr: p.IsPointer()})
|
|
off += size
|
|
}
|
|
return offsets
|
|
}
|
|
|
|
// ArgOffset describes one parameter's position in the argument block.
|
|
type ArgOffset struct {
|
|
Name string
|
|
Typ string
|
|
Offset int
|
|
Size int
|
|
IsPtr bool
|
|
}
|
|
|
|
// paramSize returns the size in bytes of a parameter type in the ABI0 layout.
|
|
func paramSize(typ string) int {
|
|
switch {
|
|
case strings.HasPrefix(typ, "[]"):
|
|
// Slice: pointer + length + capacity = 24 bytes.
|
|
return 24
|
|
case strings.HasPrefix(typ, "*"):
|
|
// Pointer: 8 bytes.
|
|
return 8
|
|
case typ == "int", typ == "int64", typ == "uint64", typ == "uintptr":
|
|
return 8
|
|
case typ == "int32", typ == "uint32", typ == "float32":
|
|
return 4
|
|
case typ == "int16", typ == "uint16":
|
|
return 2
|
|
case typ == "int8", typ == "uint8", typ == "byte", typ == "bool":
|
|
return 1
|
|
default:
|
|
// Default to 8 bytes for unknown types.
|
|
return 8
|
|
}
|
|
}
|