]> Cypherpunks.ru repositories - gostls13.git/blob - src/internal/trace/v2/raw/textreader.go
runtime: add execution tracer v2 behind GOEXPERIMENT=exectracer2
[gostls13.git] / src / internal / trace / v2 / raw / textreader.go
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
5 package raw
6
7 import (
8         "bufio"
9         "fmt"
10         "io"
11         "strconv"
12         "strings"
13         "unicode"
14
15         "internal/trace/v2/event"
16         "internal/trace/v2/version"
17 )
18
19 // TextReader parses a text format trace with only very basic validation
20 // into an event stream.
21 type 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.
29 func 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.
63 func (r *TextReader) Version() version.Version {
64         return r.v
65 }
66
67 // ReadEvent reads and returns the next trace event in the text stream.
68 func (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
122 func (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
139 var frameFields = []string{"pc", "func", "file", "line"}
140
141 func 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
162 func 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
180 func 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
207 func 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 }