diff options
Diffstat (limited to 'src/internal/coverage/encodemeta/encode.go')
-rw-r--r-- | src/internal/coverage/encodemeta/encode.go | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/src/internal/coverage/encodemeta/encode.go b/src/internal/coverage/encodemeta/encode.go new file mode 100644 index 0000000..d211c7c --- /dev/null +++ b/src/internal/coverage/encodemeta/encode.go @@ -0,0 +1,215 @@ +// 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) +} |