// 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) }