github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/encodemeta/encode.go (about)

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