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 encodemeta
6
7// This package contains APIs and helpers for encoding the meta-data
8// "blob" for a single Go package, created when coverage
9// instrumentation is turned on.
10
11import (
12	"bytes"
13	"crypto/md5"
14	"encoding/binary"
15	"fmt"
16	"hash"
17	"internal/coverage"
18	"internal/coverage/stringtab"
19	"internal/coverage/uleb128"
20	"io"
21	"os"
22)
23
24type CoverageMetaDataBuilder struct {
25	stab    stringtab.Writer
26	funcs   []funcDesc
27	tmp     []byte // temp work slice
28	h       hash.Hash
29	pkgpath uint32
30	pkgname uint32
31	modpath uint32
32	debug   bool
33	werr    error
34}
35
36func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
37	if pkgpath == "" {
38		return nil, fmt.Errorf("invalid empty package path")
39	}
40	x := &CoverageMetaDataBuilder{
41		tmp: make([]byte, 0, 256),
42		h:   md5.New(),
43	}
44	x.stab.InitWriter()
45	x.stab.Lookup("")
46	x.pkgpath = x.stab.Lookup(pkgpath)
47	x.pkgname = x.stab.Lookup(pkgname)
48	x.modpath = x.stab.Lookup(modulepath)
49	io.WriteString(x.h, pkgpath)
50	io.WriteString(x.h, pkgname)
51	io.WriteString(x.h, modulepath)
52	return x, nil
53}
54
55func h32(x uint32, h hash.Hash, tmp []byte) {
56	tmp = tmp[:0]
57	tmp = append(tmp, 0, 0, 0, 0)
58	binary.LittleEndian.PutUint32(tmp, x)
59	h.Write(tmp)
60}
61
62type funcDesc struct {
63	encoded []byte
64}
65
66// AddFunc registers a new function with the meta data builder.
67func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
68	hashFuncDesc(b.h, &f, b.tmp)
69	fd := funcDesc{}
70	b.tmp = b.tmp[:0]
71	b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
72	b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
73	b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
74	for _, u := range f.Units {
75		b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
76		b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
77		b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
78		b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
79		b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
80	}
81	lit := uint(0)
82	if f.Lit {
83		lit = 1
84	}
85	b.tmp = uleb128.AppendUleb128(b.tmp, lit)
86	fd.encoded = bytes.Clone(b.tmp)
87	rv := uint(len(b.funcs))
88	b.funcs = append(b.funcs, fd)
89	return rv
90}
91
92func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
93	nFuncs := len(b.funcs)
94	var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
95	for idx := 0; idx < nFuncs; idx++ {
96		b.wrUint32(w, uint32(foff))
97		foff += int64(len(b.funcs[idx].encoded))
98	}
99	return off + (int64(len(b.funcs)) * 4)
100}
101
102func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
103	ew := len(f.encoded)
104	if nw, err := w.Write(f.encoded); err != nil {
105		return 0, err
106	} else if ew != nw {
107		return 0, fmt.Errorf("short write emitting coverage meta-data")
108	}
109	return off + int64(ew), nil
110}
111
112func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
113	if b.werr != nil {
114		b.werr = err
115	}
116}
117
118func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
119	b.tmp = b.tmp[:0]
120	b.tmp = append(b.tmp, 0, 0, 0, 0)
121	binary.LittleEndian.PutUint32(b.tmp, v)
122	if nw, err := w.Write(b.tmp); err != nil {
123		b.reportWriteError(err)
124	} else if nw != 4 {
125		b.reportWriteError(fmt.Errorf("short write"))
126	}
127}
128
129// Emit writes the meta-data accumulated so far in this builder to 'w'.
130// Returns a hash of the meta-data payload and an error.
131func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
132	// Emit header.  Length will initially be zero, we'll
133	// back-patch it later.
134	var digest [16]byte
135	copy(digest[:], b.h.Sum(nil))
136	mh := coverage.MetaSymbolHeader{
137		// hash and length initially zero, will be back-patched
138		PkgPath:    uint32(b.pkgpath),
139		PkgName:    uint32(b.pkgname),
140		ModulePath: uint32(b.modpath),
141		NumFiles:   uint32(b.stab.Nentries()),
142		NumFuncs:   uint32(len(b.funcs)),
143		MetaHash:   digest,
144	}
145	if b.debug {
146		fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
147	}
148	if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
149		return digest, fmt.Errorf("error writing meta-file header: %v", err)
150	}
151	off := int64(coverage.CovMetaHeaderSize)
152
153	// Write function offsets section
154	off = b.emitFuncOffsets(w, off)
155
156	// Check for any errors up to this point.
157	if b.werr != nil {
158		return digest, b.werr
159	}
160
161	// Write string table.
162	if err := b.stab.Write(w); err != nil {
163		return digest, err
164	}
165	off += int64(b.stab.Size())
166
167	// Write functions
168	for _, f := range b.funcs {
169		var err error
170		off, err = b.emitFunc(w, off, f)
171		if err != nil {
172			return digest, err
173		}
174	}
175
176	// Back-patch the length.
177	totalLength := uint32(off)
178	if _, err := w.Seek(0, io.SeekStart); err != nil {
179		return digest, err
180	}
181	b.wrUint32(w, totalLength)
182	if b.werr != nil {
183		return digest, b.werr
184	}
185	return digest, nil
186}
187
188// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
189// a digest for it.
190func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
191	h := md5.New()
192	tmp := make([]byte, 0, 32)
193	hashFuncDesc(h, f, tmp)
194	var r [16]byte
195	copy(r[:], h.Sum(nil))
196	return r
197}
198
199// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
200func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
201	io.WriteString(h, f.Funcname)
202	io.WriteString(h, f.Srcfile)
203	for _, u := range f.Units {
204		h32(u.StLine, h, tmp)
205		h32(u.StCol, h, tmp)
206		h32(u.EnLine, h, tmp)
207		h32(u.EnCol, h, tmp)
208		h32(u.NxStmts, h, tmp)
209	}
210	lit := uint32(0)
211	if f.Lit {
212		lit = 1
213	}
214	h32(lit, h, tmp)
215}
216