blob: d211c7c08eb4b79863d45ca082a1c79e65b53208 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package encodemeta
// This package contains APIs and helpers for encoding the meta-data
// "blob" for a single Go package, created when coverage
// instrumentation is turned on.
import (
"bytes"
"crypto/md5"
"encoding/binary"
"fmt"
"hash"
"internal/coverage"
"internal/coverage/stringtab"
"internal/coverage/uleb128"
"io"
"os"
)
type CoverageMetaDataBuilder struct {
stab stringtab.Writer
funcs []funcDesc
tmp []byte // temp work slice
h hash.Hash
pkgpath uint32
pkgname uint32
modpath uint32
debug bool
werr error
}
func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
if pkgpath == "" {
return nil, fmt.Errorf("invalid empty package path")
}
x := &CoverageMetaDataBuilder{
tmp: make([]byte, 0, 256),
h: md5.New(),
}
x.stab.InitWriter()
x.stab.Lookup("")
x.pkgpath = x.stab.Lookup(pkgpath)
x.pkgname = x.stab.Lookup(pkgname)
x.modpath = x.stab.Lookup(modulepath)
io.WriteString(x.h, pkgpath)
io.WriteString(x.h, pkgname)
io.WriteString(x.h, modulepath)
return x, nil
}
func h32(x uint32, h hash.Hash, tmp []byte) {
tmp = tmp[:0]
tmp = append(tmp, []byte{0, 0, 0, 0}...)
binary.LittleEndian.PutUint32(tmp, x)
h.Write(tmp)
}
type funcDesc struct {
encoded []byte
}
// AddFunc registers a new function with the meta data builder.
func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
hashFuncDesc(b.h, &f, b.tmp)
fd := funcDesc{}
b.tmp = b.tmp[:0]
b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
for _, u := range f.Units {
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
}
lit := uint(0)
if f.Lit {
lit = 1
}
b.tmp = uleb128.AppendUleb128(b.tmp, lit)
fd.encoded = bytes.Clone(b.tmp)
rv := uint(len(b.funcs))
b.funcs = append(b.funcs, fd)
return rv
}
func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
nFuncs := len(b.funcs)
var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
for idx := 0; idx < nFuncs; idx++ {
b.wrUint32(w, uint32(foff))
foff += int64(len(b.funcs[idx].encoded))
}
return off + (int64(len(b.funcs)) * 4)
}
func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
ew := len(f.encoded)
if nw, err := w.Write(f.encoded); err != nil {
return 0, err
} else if ew != nw {
return 0, fmt.Errorf("short write emitting coverage meta-data")
}
return off + int64(ew), nil
}
func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
if b.werr != nil {
b.werr = err
}
}
func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
b.tmp = b.tmp[:0]
b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...)
binary.LittleEndian.PutUint32(b.tmp, v)
if nw, err := w.Write(b.tmp); err != nil {
b.reportWriteError(err)
} else if nw != 4 {
b.reportWriteError(fmt.Errorf("short write"))
}
}
// Emit writes the meta-data accumulated so far in this builder to 'w'.
// Returns a hash of the meta-data payload and an error.
func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
// Emit header. Length will initially be zero, we'll
// back-patch it later.
var digest [16]byte
copy(digest[:], b.h.Sum(nil))
mh := coverage.MetaSymbolHeader{
// hash and length initially zero, will be back-patched
PkgPath: uint32(b.pkgpath),
PkgName: uint32(b.pkgname),
ModulePath: uint32(b.modpath),
NumFiles: uint32(b.stab.Nentries()),
NumFuncs: uint32(len(b.funcs)),
MetaHash: digest,
}
if b.debug {
fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
}
if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
return digest, fmt.Errorf("error writing meta-file header: %v", err)
}
off := int64(coverage.CovMetaHeaderSize)
// Write function offsets section
off = b.emitFuncOffsets(w, off)
// Check for any errors up to this point.
if b.werr != nil {
return digest, b.werr
}
// Write string table.
if err := b.stab.Write(w); err != nil {
return digest, err
}
off += int64(b.stab.Size())
// Write functions
for _, f := range b.funcs {
var err error
off, err = b.emitFunc(w, off, f)
if err != nil {
return digest, err
}
}
// Back-patch the length.
totalLength := uint32(off)
if _, err := w.Seek(0, io.SeekStart); err != nil {
return digest, err
}
b.wrUint32(w, totalLength)
if b.werr != nil {
return digest, b.werr
}
return digest, nil
}
// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
// a digest for it.
func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
h := md5.New()
tmp := make([]byte, 0, 32)
hashFuncDesc(h, f, tmp)
var r [16]byte
copy(r[:], h.Sum(nil))
return r
}
// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
io.WriteString(h, f.Funcname)
io.WriteString(h, f.Srcfile)
for _, u := range f.Units {
h32(u.StLine, h, tmp)
h32(u.StCol, h, tmp)
h32(u.EnLine, h, tmp)
h32(u.EnCol, h, tmp)
h32(u.NxStmts, h, tmp)
}
lit := uint32(0)
if f.Lit {
lit = 1
}
h32(lit, h, tmp)
}