fix(parser): reject malformed TEXT frames and parse signed frame sizes

This commit is contained in:
2026-09-14 18:22:00 +02:00
parent 22226d59a5
commit f5fc22d390
2 changed files with 97 additions and 10 deletions
+34 -10
View File
@@ -210,17 +210,39 @@ func (p *state) parseText(line []token.Token) {
rest = rest[1:]
}
// Frame: $number ; optional args: -number. Whatever remains after the
// header is the body and is parsed by the caller.
// Frame: $[-]number ; optional args: -number. The Go runtime writes
// zero frames with an explicit sign ("$-0-24"), so the number may carry
// one. Whatever remains after the header is the body and is parsed by
// the caller.
if len(rest) > 0 && rest[0].Kind == token.Dollar {
text.Frame = parseOperand(rest[:2]) // "$" "number"
if len(rest) >= 4 && rest[2].Kind == token.Minus && rest[3].Kind == token.Number {
text.Args = &ast.Operand{
Kind: ast.OpImmediate,
Imm: ast.Immediate{Val: parseInt(rest[3].Text), HasVal: true},
Raw: "-" + rest[3].Text,
Pos: rest[2].Pos,
n := 1
neg := false
if n < len(rest) && (rest[n].Kind == token.Minus || rest[n].Kind == token.Plus) {
neg = rest[n].Kind == token.Minus
n++
}
if n < len(rest) && rest[n].Kind == token.Number {
val := parseInt(rest[n].Text)
if neg {
val = -val
}
text.Frame = &ast.Operand{
Kind: ast.OpImmediate,
Imm: ast.Immediate{Val: val, HasVal: true},
Raw: joinRaw(rest[:n+1]),
Pos: rest[0].Pos,
}
// The argument area: a minus sign followed by a number.
if n+2 < len(rest) && rest[n+1].Kind == token.Minus && rest[n+2].Kind == token.Number {
text.Args = &ast.Operand{
Kind: ast.OpImmediate,
Imm: ast.Immediate{Val: parseInt(rest[n+2].Text), HasVal: true},
Raw: "-" + rest[n+2].Text,
Pos: rest[n+1].Pos,
}
}
} else {
p.errorf(rest[0].Pos, "TEXT frame size must be a number after $")
}
}
@@ -234,8 +256,10 @@ func (p *state) parseGlobl(line []token.Token) *ast.Globl {
sym, n := parseSymbolPrefix(rest)
g.Name = sym
rest = skipComma(rest[n:])
// Flags are identifiers (RODATA, DUPOK) or legacy numeric constants
// (2, 8, 9, 10) from runtime/textflag.h.
for len(rest) > 0 && rest[0].Kind != token.Dollar {
if rest[0].Kind == token.Ident {
if rest[0].Kind == token.Ident || rest[0].Kind == token.Number {
g.Flags = append(g.Flags, rest[0].Text)
}
rest = rest[1:]
+63
View File
@@ -253,3 +253,66 @@ func TestDataWidthAndStatic(t *testing.T) {
t.Errorf("mask24 DATA should be static, got %+v", datas[2].Name)
}
}
// TestTruncatedFrameDollar is a regression test for a TEXT directive whose
// frame size is missing after the $: the parser used to slice past the end
// of the token slice and panic. It must report a diagnostic instead.
func TestTruncatedFrameDollar(t *testing.T) {
for _, src := range []string{
"TEXT $\n",
"TEXT \u00b7foo(SB), $\n",
"TEXT \u00b7foo(SB), NOSPLIT, $\n",
} {
var file *ast.File
func() {
defer func() {
if r := recover(); r != nil {
t.Fatalf("Parse(%q) panicked: %v", src, r)
}
}()
file, _ = Parse("t.s", src)
}()
if file == nil {
t.Fatalf("Parse(%q) returned no file", src)
}
if len(file.Decls) != 1 {
t.Fatalf("Parse(%q) decls = %d, want 1", src, len(file.Decls))
}
txt := file.Decls[0].(*ast.Text)
if txt.Frame != nil {
t.Errorf("Parse(%q) frame = %v, want nil", src, txt.Frame)
}
}
}
// TestFrameAndArgs parses a well-formed TEXT header and checks that the
// frame and args operands are picked up.
func TestFrameAndArgs(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7foo(SB), $32-16\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
if txt.Frame == nil || !txt.Frame.Imm.HasVal || txt.Frame.Imm.Val != 32 {
t.Errorf("frame = %+v, want $32", txt.Frame)
}
if txt.Args == nil || !txt.Args.Imm.HasVal || txt.Args.Imm.Val != 16 {
t.Errorf("args = %+v, want -16", txt.Args)
}
}
// TestSignedZeroFrame covers the Go runtime's "$-0-24" spelling: a zero
// frame with an explicit sign plus the argument area.
func TestSignedZeroFrame(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7foo<ABIInternal>(SB), NOSPLIT, $-0-24\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
if txt.Frame == nil || !txt.Frame.Imm.HasVal || txt.Frame.Imm.Val != 0 {
t.Errorf("frame = %+v, want $-0", txt.Frame)
}
if txt.Args == nil || !txt.Args.Imm.HasVal || txt.Args.Imm.Val != 24 {
t.Errorf("args = %+v, want -24", txt.Args)
}
}