diff --git a/parser/parser.go b/parser/parser.go index 3d32be9..b615f5a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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:] diff --git a/parser/parser_test.go b/parser/parser_test.go index cb4212e..fd1b510 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -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(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) + } +}