encode.mx raw

   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  
   5  package 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  
  11  import (
  12  	"bytes"
  13  	"encoding/binary"
  14  	"fmt"
  15  	"hash"
  16  	"hash/fnv"
  17  	"internal/coverage"
  18  	"internal/coverage/stringtab"
  19  	"internal/coverage/uleb128"
  20  	"io"
  21  	"os"
  22  )
  23  
  24  type 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  
  36  func NewCoverageMetaDataBuilder(pkgpath []byte, pkgname []byte, modulepath []byte) (*CoverageMetaDataBuilder, error) {
  37  	if pkgpath == "" {
  38  		return nil, fmt.Errorf("invalid empty package path")
  39  	}
  40  	x := &CoverageMetaDataBuilder{
  41  		tmp: []byte{:0:256},
  42  		h:   fnv.New128a(),
  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  
  55  func 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  
  62  type funcDesc struct {
  63  	encoded []byte
  64  }
  65  
  66  // AddFunc registers a new function with the meta data builder.
  67  func (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  
  92  func (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  
 102  func (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  
 112  func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
 113  	if b.werr != nil {
 114  		b.werr = err
 115  	}
 116  }
 117  
 118  func (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.
 131  func (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.
 190  func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
 191  	h := fnv.New128a()
 192  	tmp := []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'.
 200  func 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