1// Copyright 2021 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 decodecounter
6
7import (
8	"encoding/binary"
9	"fmt"
10	"internal/coverage"
11	"internal/coverage/slicereader"
12	"internal/coverage/stringtab"
13	"io"
14	"os"
15	"strconv"
16	"unsafe"
17)
18
19// This file contains helpers for reading counter data files created
20// during the executions of a coverage-instrumented binary.
21
22type CounterDataReader struct {
23	stab     *stringtab.Reader
24	args     map[string]string
25	osargs   []string
26	goarch   string // GOARCH setting from run that produced counter data
27	goos     string // GOOS setting from run that produced counter data
28	mr       io.ReadSeeker
29	hdr      coverage.CounterFileHeader
30	ftr      coverage.CounterFileFooter
31	shdr     coverage.CounterSegmentHeader
32	u32b     []byte
33	u8b      []byte
34	fcnCount uint32
35	segCount uint32
36	debug    bool
37}
38
39func NewCounterDataReader(fn string, rs io.ReadSeeker) (*CounterDataReader, error) {
40	cdr := &CounterDataReader{
41		mr:   rs,
42		u32b: make([]byte, 4),
43		u8b:  make([]byte, 1),
44	}
45	// Read header
46	if err := binary.Read(rs, binary.LittleEndian, &cdr.hdr); err != nil {
47		return nil, err
48	}
49	if cdr.debug {
50		fmt.Fprintf(os.Stderr, "=-= counter file header: %+v\n", cdr.hdr)
51	}
52	if !checkMagic(cdr.hdr.Magic) {
53		return nil, fmt.Errorf("invalid magic string: not a counter data file")
54	}
55	if cdr.hdr.Version > coverage.CounterFileVersion {
56		return nil, fmt.Errorf("version data incompatibility: reader is %d data is %d", coverage.CounterFileVersion, cdr.hdr.Version)
57	}
58
59	// Read footer.
60	if err := cdr.readFooter(); err != nil {
61		return nil, err
62	}
63	// Seek back to just past the file header.
64	hsz := int64(unsafe.Sizeof(cdr.hdr))
65	if _, err := cdr.mr.Seek(hsz, io.SeekStart); err != nil {
66		return nil, err
67	}
68	// Read preamble for first segment.
69	if err := cdr.readSegmentPreamble(); err != nil {
70		return nil, err
71	}
72	return cdr, nil
73}
74
75func checkMagic(v [4]byte) bool {
76	g := coverage.CovCounterMagic
77	return v[0] == g[0] && v[1] == g[1] && v[2] == g[2] && v[3] == g[3]
78}
79
80func (cdr *CounterDataReader) readFooter() error {
81	ftrSize := int64(unsafe.Sizeof(cdr.ftr))
82	if _, err := cdr.mr.Seek(-ftrSize, io.SeekEnd); err != nil {
83		return err
84	}
85	if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.ftr); err != nil {
86		return err
87	}
88	if !checkMagic(cdr.ftr.Magic) {
89		return fmt.Errorf("invalid magic string (not a counter data file)")
90	}
91	if cdr.ftr.NumSegments == 0 {
92		return fmt.Errorf("invalid counter data file (no segments)")
93	}
94	return nil
95}
96
97// readSegmentPreamble reads and consumes the segment header, segment string
98// table, and segment args table.
99func (cdr *CounterDataReader) readSegmentPreamble() error {
100	// Read segment header.
101	if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.shdr); err != nil {
102		return err
103	}
104	if cdr.debug {
105		fmt.Fprintf(os.Stderr, "=-= read counter segment header: %+v", cdr.shdr)
106		fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n",
107			cdr.shdr.FcnEntries, cdr.shdr.StrTabLen, cdr.shdr.ArgsLen)
108	}
109
110	// Read string table and args.
111	if err := cdr.readStringTable(); err != nil {
112		return err
113	}
114	if err := cdr.readArgs(); err != nil {
115		return err
116	}
117	// Seek past any padding to bring us up to a 4-byte boundary.
118	if of, err := cdr.mr.Seek(0, io.SeekCurrent); err != nil {
119		return err
120	} else {
121		rem := of % 4
122		if rem != 0 {
123			pad := 4 - rem
124			if _, err := cdr.mr.Seek(pad, io.SeekCurrent); err != nil {
125				return err
126			}
127		}
128	}
129	return nil
130}
131
132func (cdr *CounterDataReader) readStringTable() error {
133	b := make([]byte, cdr.shdr.StrTabLen)
134	nr, err := cdr.mr.Read(b)
135	if err != nil {
136		return err
137	}
138	if nr != int(cdr.shdr.StrTabLen) {
139		return fmt.Errorf("error: short read on string table")
140	}
141	slr := slicereader.NewReader(b, false /* not readonly */)
142	cdr.stab = stringtab.NewReader(slr)
143	cdr.stab.Read()
144	return nil
145}
146
147func (cdr *CounterDataReader) readArgs() error {
148	b := make([]byte, cdr.shdr.ArgsLen)
149	nr, err := cdr.mr.Read(b)
150	if err != nil {
151		return err
152	}
153	if nr != int(cdr.shdr.ArgsLen) {
154		return fmt.Errorf("error: short read on args table")
155	}
156	slr := slicereader.NewReader(b, false /* not readonly */)
157	sget := func() (string, error) {
158		kidx := slr.ReadULEB128()
159		if int(kidx) >= cdr.stab.Entries() {
160			return "", fmt.Errorf("malformed string table ref")
161		}
162		return cdr.stab.Get(uint32(kidx)), nil
163	}
164	nents := slr.ReadULEB128()
165	cdr.args = make(map[string]string, int(nents))
166	for i := uint64(0); i < nents; i++ {
167		k, errk := sget()
168		if errk != nil {
169			return errk
170		}
171		v, errv := sget()
172		if errv != nil {
173			return errv
174		}
175		if _, ok := cdr.args[k]; ok {
176			return fmt.Errorf("malformed args table")
177		}
178		cdr.args[k] = v
179	}
180	if argcs, ok := cdr.args["argc"]; ok {
181		argc, err := strconv.Atoi(argcs)
182		if err != nil {
183			return fmt.Errorf("malformed argc in counter data file args section")
184		}
185		cdr.osargs = make([]string, 0, argc)
186		for i := 0; i < argc; i++ {
187			arg := cdr.args[fmt.Sprintf("argv%d", i)]
188			cdr.osargs = append(cdr.osargs, arg)
189		}
190	}
191	if goos, ok := cdr.args["GOOS"]; ok {
192		cdr.goos = goos
193	}
194	if goarch, ok := cdr.args["GOARCH"]; ok {
195		cdr.goarch = goarch
196	}
197	return nil
198}
199
200// OsArgs returns the program arguments (saved from os.Args during
201// the run of the instrumented binary) read from the counter
202// data file. Not all coverage data files will have os.Args values;
203// for example, if a data file is produced by merging coverage
204// data from two distinct runs, no os args will be available (an
205// empty list is returned).
206func (cdr *CounterDataReader) OsArgs() []string {
207	return cdr.osargs
208}
209
210// Goos returns the GOOS setting in effect for the "-cover" binary
211// that produced this counter data file. The GOOS value may be
212// empty in the case where the counter data file was produced
213// from a merge in which more than one GOOS value was present.
214func (cdr *CounterDataReader) Goos() string {
215	return cdr.goos
216}
217
218// Goarch returns the GOARCH setting in effect for the "-cover" binary
219// that produced this counter data file. The GOARCH value may be
220// empty in the case where the counter data file was produced
221// from a merge in which more than one GOARCH value was present.
222func (cdr *CounterDataReader) Goarch() string {
223	return cdr.goarch
224}
225
226// FuncPayload encapsulates the counter data payload for a single
227// function as read from a counter data file.
228type FuncPayload struct {
229	PkgIdx   uint32
230	FuncIdx  uint32
231	Counters []uint32
232}
233
234// NumSegments returns the number of execution segments in the file.
235func (cdr *CounterDataReader) NumSegments() uint32 {
236	return cdr.ftr.NumSegments
237}
238
239// BeginNextSegment sets up the reader to read the next segment,
240// returning TRUE if we do have another segment to read, or FALSE
241// if we're done with all the segments (also an error if
242// something went wrong).
243func (cdr *CounterDataReader) BeginNextSegment() (bool, error) {
244	if cdr.segCount >= cdr.ftr.NumSegments {
245		return false, nil
246	}
247	cdr.segCount++
248	cdr.fcnCount = 0
249	// Seek past footer from last segment.
250	ftrSize := int64(unsafe.Sizeof(cdr.ftr))
251	if _, err := cdr.mr.Seek(ftrSize, io.SeekCurrent); err != nil {
252		return false, err
253	}
254	// Read preamble for this segment.
255	if err := cdr.readSegmentPreamble(); err != nil {
256		return false, err
257	}
258	return true, nil
259}
260
261// NumFunctionsInSegment returns the number of live functions
262// in the currently selected segment.
263func (cdr *CounterDataReader) NumFunctionsInSegment() uint32 {
264	return uint32(cdr.shdr.FcnEntries)
265}
266
267const supportDeadFunctionsInCounterData = false
268
269// NextFunc reads data for the next function in this current segment
270// into "p", returning TRUE if the read was successful or FALSE
271// if we've read all the functions already (also an error if
272// something went wrong with the read or we hit a premature
273// EOF).
274func (cdr *CounterDataReader) NextFunc(p *FuncPayload) (bool, error) {
275	if cdr.fcnCount >= uint32(cdr.shdr.FcnEntries) {
276		return false, nil
277	}
278	cdr.fcnCount++
279	var rdu32 func() (uint32, error)
280	if cdr.hdr.CFlavor == coverage.CtrULeb128 {
281		rdu32 = func() (uint32, error) {
282			var shift uint
283			var value uint64
284			for {
285				_, err := cdr.mr.Read(cdr.u8b)
286				if err != nil {
287					return 0, err
288				}
289				b := cdr.u8b[0]
290				value |= (uint64(b&0x7F) << shift)
291				if b&0x80 == 0 {
292					break
293				}
294				shift += 7
295			}
296			return uint32(value), nil
297		}
298	} else if cdr.hdr.CFlavor == coverage.CtrRaw {
299		if cdr.hdr.BigEndian {
300			rdu32 = func() (uint32, error) {
301				n, err := cdr.mr.Read(cdr.u32b)
302				if err != nil {
303					return 0, err
304				}
305				if n != 4 {
306					return 0, io.EOF
307				}
308				return binary.BigEndian.Uint32(cdr.u32b), nil
309			}
310		} else {
311			rdu32 = func() (uint32, error) {
312				n, err := cdr.mr.Read(cdr.u32b)
313				if err != nil {
314					return 0, err
315				}
316				if n != 4 {
317					return 0, io.EOF
318				}
319				return binary.LittleEndian.Uint32(cdr.u32b), nil
320			}
321		}
322	} else {
323		panic("internal error: unknown counter flavor")
324	}
325
326	// Alternative/experimental path: one way we could handling writing
327	// out counter data would be to just memcpy the counter segment
328	// out to a file, meaning that a region in the counter memory
329	// corresponding to a dead (never-executed) function would just be
330	// zeroes. The code path below handles this case.
331	var nc uint32
332	var err error
333	if supportDeadFunctionsInCounterData {
334		for {
335			nc, err = rdu32()
336			if err == io.EOF {
337				return false, io.EOF
338			} else if err != nil {
339				break
340			}
341			if nc != 0 {
342				break
343			}
344		}
345	} else {
346		nc, err = rdu32()
347	}
348	if err != nil {
349		return false, err
350	}
351
352	// Read package and func indices.
353	p.PkgIdx, err = rdu32()
354	if err != nil {
355		return false, err
356	}
357	p.FuncIdx, err = rdu32()
358	if err != nil {
359		return false, err
360	}
361	if cap(p.Counters) < 1024 {
362		p.Counters = make([]uint32, 0, 1024)
363	}
364	p.Counters = p.Counters[:0]
365	for i := uint32(0); i < nc; i++ {
366		v, err := rdu32()
367		if err != nil {
368			return false, err
369		}
370		p.Counters = append(p.Counters, v)
371	}
372	return true, nil
373}
374