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 decodemeta
6
7// This package contains APIs and helpers for reading and decoding
8// meta-data output files emitted by the runtime when a
9// coverage-instrumented binary executes. A meta-data file contains
10// top-level info (counter mode, number of packages) and then a
11// separate self-contained meta-data section for each Go package.
12
13import (
14	"bufio"
15	"crypto/md5"
16	"encoding/binary"
17	"fmt"
18	"internal/coverage"
19	"internal/coverage/slicereader"
20	"internal/coverage/stringtab"
21	"io"
22	"os"
23)
24
25// CoverageMetaFileReader provides state and methods for reading
26// a meta-data file from a code coverage run.
27type CoverageMetaFileReader struct {
28	f          *os.File
29	hdr        coverage.MetaFileHeader
30	tmp        []byte
31	pkgOffsets []uint64
32	pkgLengths []uint64
33	strtab     *stringtab.Reader
34	fileRdr    *bufio.Reader
35	fileView   []byte
36	debug      bool
37}
38
39// NewCoverageMetaFileReader returns a new helper object for reading
40// the coverage meta-data output file 'f'. The param 'fileView' is a
41// read-only slice containing the contents of 'f' obtained by mmap'ing
42// the file read-only; 'fileView' may be nil, in which case the helper
43// will read the contents of the file using regular file Read
44// operations.
45func NewCoverageMetaFileReader(f *os.File, fileView []byte) (*CoverageMetaFileReader, error) {
46	r := &CoverageMetaFileReader{
47		f:        f,
48		fileView: fileView,
49		tmp:      make([]byte, 256),
50	}
51
52	if err := r.readFileHeader(); err != nil {
53		return nil, err
54	}
55	return r, nil
56}
57
58func (r *CoverageMetaFileReader) readFileHeader() error {
59	var err error
60
61	r.fileRdr = bufio.NewReader(r.f)
62
63	// Read file header.
64	if err := binary.Read(r.fileRdr, binary.LittleEndian, &r.hdr); err != nil {
65		return err
66	}
67
68	// Verify magic string
69	m := r.hdr.Magic
70	g := coverage.CovMetaMagic
71	if m[0] != g[0] || m[1] != g[1] || m[2] != g[2] || m[3] != g[3] {
72		return fmt.Errorf("invalid meta-data file magic string")
73	}
74
75	// Vet the version. If this is a meta-data file from the future,
76	// we won't be able to read it.
77	if r.hdr.Version > coverage.MetaFileVersion {
78		return fmt.Errorf("meta-data file withn unknown version %d (expected %d)", r.hdr.Version, coverage.MetaFileVersion)
79	}
80
81	// Read package offsets for good measure
82	r.pkgOffsets = make([]uint64, r.hdr.Entries)
83	for i := uint64(0); i < r.hdr.Entries; i++ {
84		if r.pkgOffsets[i], err = r.rdUint64(); err != nil {
85			return err
86		}
87		if r.pkgOffsets[i] > r.hdr.TotalLength {
88			return fmt.Errorf("insane pkg offset %d: %d > totlen %d",
89				i, r.pkgOffsets[i], r.hdr.TotalLength)
90		}
91	}
92	r.pkgLengths = make([]uint64, r.hdr.Entries)
93	for i := uint64(0); i < r.hdr.Entries; i++ {
94		if r.pkgLengths[i], err = r.rdUint64(); err != nil {
95			return err
96		}
97		if r.pkgLengths[i] > r.hdr.TotalLength {
98			return fmt.Errorf("insane pkg length %d: %d > totlen %d",
99				i, r.pkgLengths[i], r.hdr.TotalLength)
100		}
101	}
102
103	// Read string table.
104	b := make([]byte, r.hdr.StrTabLength)
105	nr, err := r.fileRdr.Read(b)
106	if err != nil {
107		return err
108	}
109	if nr != int(r.hdr.StrTabLength) {
110		return fmt.Errorf("error: short read on string table")
111	}
112	slr := slicereader.NewReader(b, false /* not readonly */)
113	r.strtab = stringtab.NewReader(slr)
114	r.strtab.Read()
115
116	if r.debug {
117		fmt.Fprintf(os.Stderr, "=-= read-in header is: %+v\n", *r)
118	}
119
120	return nil
121}
122
123func (r *CoverageMetaFileReader) rdUint64() (uint64, error) {
124	r.tmp = r.tmp[:0]
125	r.tmp = append(r.tmp, make([]byte, 8)...)
126	n, err := r.fileRdr.Read(r.tmp)
127	if err != nil {
128		return 0, err
129	}
130	if n != 8 {
131		return 0, fmt.Errorf("premature end of file on read")
132	}
133	v := binary.LittleEndian.Uint64(r.tmp)
134	return v, nil
135}
136
137// NumPackages returns the number of packages for which this file
138// contains meta-data.
139func (r *CoverageMetaFileReader) NumPackages() uint64 {
140	return r.hdr.Entries
141}
142
143// CounterMode returns the counter mode (set, count, atomic) used
144// when building for coverage for the program that produce this
145// meta-data file.
146func (r *CoverageMetaFileReader) CounterMode() coverage.CounterMode {
147	return r.hdr.CMode
148}
149
150// CounterGranularity returns the counter granularity (single counter per
151// function, or counter per block) selected when building for coverage
152// for the program that produce this meta-data file.
153func (r *CoverageMetaFileReader) CounterGranularity() coverage.CounterGranularity {
154	return r.hdr.CGranularity
155}
156
157// FileHash returns the hash computed for all of the package meta-data
158// blobs. Coverage counter data files refer to this hash, and the
159// hash will be encoded into the meta-data file name.
160func (r *CoverageMetaFileReader) FileHash() [16]byte {
161	return r.hdr.MetaFileHash
162}
163
164// GetPackageDecoder requests a decoder object for the package within
165// the meta-data file whose index is 'pkIdx'. If the
166// CoverageMetaFileReader was set up with a read-only file view, a
167// pointer into that file view will be returned, otherwise the buffer
168// 'payloadbuf' will be written to (or if it is not of sufficient
169// size, a new buffer will be allocated). Return value is the decoder,
170// a byte slice with the encoded meta-data, and an error.
171func (r *CoverageMetaFileReader) GetPackageDecoder(pkIdx uint32, payloadbuf []byte) (*CoverageMetaDataDecoder, []byte, error) {
172	pp, err := r.GetPackagePayload(pkIdx, payloadbuf)
173	if r.debug {
174		fmt.Fprintf(os.Stderr, "=-= pkidx=%d payload length is %d hash=%s\n",
175			pkIdx, len(pp), fmt.Sprintf("%x", md5.Sum(pp)))
176	}
177	if err != nil {
178		return nil, nil, err
179	}
180	mdd, err := NewCoverageMetaDataDecoder(pp, r.fileView != nil)
181	if err != nil {
182		return nil, nil, err
183	}
184	return mdd, pp, nil
185}
186
187// GetPackagePayload returns the raw (encoded) meta-data payload for the
188// package with index 'pkIdx'. As with GetPackageDecoder, if the
189// CoverageMetaFileReader was set up with a read-only file view, a
190// pointer into that file view will be returned, otherwise the buffer
191// 'payloadbuf' will be written to (or if it is not of sufficient
192// size, a new buffer will be allocated). Return value is the decoder,
193// a byte slice with the encoded meta-data, and an error.
194func (r *CoverageMetaFileReader) GetPackagePayload(pkIdx uint32, payloadbuf []byte) ([]byte, error) {
195
196	// Determine correct offset/length.
197	if uint64(pkIdx) >= r.hdr.Entries {
198		return nil, fmt.Errorf("GetPackagePayload: illegal pkg index %d", pkIdx)
199	}
200	off := r.pkgOffsets[pkIdx]
201	len := r.pkgLengths[pkIdx]
202
203	if r.debug {
204		fmt.Fprintf(os.Stderr, "=-= for pk %d, off=%d len=%d\n", pkIdx, off, len)
205	}
206
207	if r.fileView != nil {
208		return r.fileView[off : off+len], nil
209	}
210
211	payload := payloadbuf[:0]
212	if cap(payload) < int(len) {
213		payload = make([]byte, 0, len)
214	}
215	payload = append(payload, make([]byte, len)...)
216	if _, err := r.f.Seek(int64(off), io.SeekStart); err != nil {
217		return nil, err
218	}
219	if _, err := io.ReadFull(r.f, payload); err != nil {
220		return nil, err
221	}
222	return payload, nil
223}
224