1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package raw
6
7import (
8	"bufio"
9	"fmt"
10	"io"
11	"strconv"
12	"strings"
13	"unicode"
14
15	"internal/trace/event"
16	"internal/trace/version"
17)
18
19// TextReader parses a text format trace with only very basic validation
20// into an event stream.
21type TextReader struct {
22	v     version.Version
23	specs []event.Spec
24	names map[string]event.Type
25	s     *bufio.Scanner
26}
27
28// NewTextReader creates a new reader for the trace text format.
29func NewTextReader(r io.Reader) (*TextReader, error) {
30	tr := &TextReader{s: bufio.NewScanner(r)}
31	line, err := tr.nextLine()
32	if err != nil {
33		return nil, err
34	}
35	trace, line := readToken(line)
36	if trace != "Trace" {
37		return nil, fmt.Errorf("failed to parse header")
38	}
39	gover, line := readToken(line)
40	if !strings.HasPrefix(gover, "Go1.") {
41		return nil, fmt.Errorf("failed to parse header Go version")
42	}
43	rawv, err := strconv.ParseUint(gover[len("Go1."):], 10, 64)
44	if err != nil {
45		return nil, fmt.Errorf("failed to parse header Go version: %v", err)
46	}
47	v := version.Version(rawv)
48	if !v.Valid() {
49		return nil, fmt.Errorf("unknown or unsupported Go version 1.%d", v)
50	}
51	tr.v = v
52	tr.specs = v.Specs()
53	tr.names = event.Names(tr.specs)
54	for _, r := range line {
55		if !unicode.IsSpace(r) {
56			return nil, fmt.Errorf("encountered unexpected non-space at the end of the header: %q", line)
57		}
58	}
59	return tr, nil
60}
61
62// Version returns the version of the trace that we're reading.
63func (r *TextReader) Version() version.Version {
64	return r.v
65}
66
67// ReadEvent reads and returns the next trace event in the text stream.
68func (r *TextReader) ReadEvent() (Event, error) {
69	line, err := r.nextLine()
70	if err != nil {
71		return Event{}, err
72	}
73	evStr, line := readToken(line)
74	ev, ok := r.names[evStr]
75	if !ok {
76		return Event{}, fmt.Errorf("unidentified event: %s", evStr)
77	}
78	spec := r.specs[ev]
79	args, err := readArgs(line, spec.Args)
80	if err != nil {
81		return Event{}, fmt.Errorf("reading args for %s: %v", evStr, err)
82	}
83	if spec.IsStack {
84		len := int(args[1])
85		for i := 0; i < len; i++ {
86			line, err := r.nextLine()
87			if err == io.EOF {
88				return Event{}, fmt.Errorf("unexpected EOF while reading stack: args=%v", args)
89			}
90			if err != nil {
91				return Event{}, err
92			}
93			frame, err := readArgs(line, frameFields)
94			if err != nil {
95				return Event{}, err
96			}
97			args = append(args, frame...)
98		}
99	}
100	var data []byte
101	if spec.HasData {
102		line, err := r.nextLine()
103		if err == io.EOF {
104			return Event{}, fmt.Errorf("unexpected EOF while reading data for %s: args=%v", evStr, args)
105		}
106		if err != nil {
107			return Event{}, err
108		}
109		data, err = readData(line)
110		if err != nil {
111			return Event{}, err
112		}
113	}
114	return Event{
115		Version: r.v,
116		Ev:      ev,
117		Args:    args,
118		Data:    data,
119	}, nil
120}
121
122func (r *TextReader) nextLine() (string, error) {
123	for {
124		if !r.s.Scan() {
125			if err := r.s.Err(); err != nil {
126				return "", err
127			}
128			return "", io.EOF
129		}
130		txt := r.s.Text()
131		tok, _ := readToken(txt)
132		if tok == "" {
133			continue // Empty line or comment.
134		}
135		return txt, nil
136	}
137}
138
139var frameFields = []string{"pc", "func", "file", "line"}
140
141func readArgs(s string, names []string) ([]uint64, error) {
142	var args []uint64
143	for _, name := range names {
144		arg, value, rest, err := readArg(s)
145		if err != nil {
146			return nil, err
147		}
148		if arg != name {
149			return nil, fmt.Errorf("expected argument %q, but got %q", name, arg)
150		}
151		args = append(args, value)
152		s = rest
153	}
154	for _, r := range s {
155		if !unicode.IsSpace(r) {
156			return nil, fmt.Errorf("encountered unexpected non-space at the end of an event: %q", s)
157		}
158	}
159	return args, nil
160}
161
162func readArg(s string) (arg string, value uint64, rest string, err error) {
163	var tok string
164	tok, rest = readToken(s)
165	if len(tok) == 0 {
166		return "", 0, s, fmt.Errorf("no argument")
167	}
168	parts := strings.SplitN(tok, "=", 2)
169	if len(parts) < 2 {
170		return "", 0, s, fmt.Errorf("malformed argument: %q", tok)
171	}
172	arg = parts[0]
173	value, err = strconv.ParseUint(parts[1], 10, 64)
174	if err != nil {
175		return arg, value, s, fmt.Errorf("failed to parse argument value %q for arg %q", parts[1], parts[0])
176	}
177	return
178}
179
180func readToken(s string) (token, rest string) {
181	tkStart := -1
182	for i, r := range s {
183		if r == '#' {
184			return "", ""
185		}
186		if !unicode.IsSpace(r) {
187			tkStart = i
188			break
189		}
190	}
191	if tkStart < 0 {
192		return "", ""
193	}
194	tkEnd := -1
195	for i, r := range s[tkStart:] {
196		if unicode.IsSpace(r) || r == '#' {
197			tkEnd = i + tkStart
198			break
199		}
200	}
201	if tkEnd < 0 {
202		return s[tkStart:], ""
203	}
204	return s[tkStart:tkEnd], s[tkEnd:]
205}
206
207func readData(line string) ([]byte, error) {
208	parts := strings.SplitN(line, "=", 2)
209	if len(parts) < 2 || strings.TrimSpace(parts[0]) != "data" {
210		return nil, fmt.Errorf("malformed data: %q", line)
211	}
212	data, err := strconv.Unquote(strings.TrimSpace(parts[1]))
213	if err != nil {
214		return nil, fmt.Errorf("failed to parse data: %q: %v", line, err)
215	}
216	return []byte(data), nil
217}
218