github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/encodecounter/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 encodecounter
     6  
     7  import (
     8  	"bufio"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"sort"
    14  
    15  	"github.com/go-asm/go/coverage"
    16  	"github.com/go-asm/go/coverage/slicewriter"
    17  	"github.com/go-asm/go/coverage/stringtab"
    18  	"github.com/go-asm/go/coverage/uleb128"
    19  )
    20  
    21  // This package contains APIs and helpers for encoding initial portions
    22  // of the counter data files emitted at runtime when coverage instrumentation
    23  // is enabled.  Counter data files may contain multiple segments; the file
    24  // header and first segment are written via the "Write" method below, and
    25  // additional segments can then be added using "AddSegment".
    26  
    27  type CoverageDataWriter struct {
    28  	stab    *stringtab.Writer
    29  	w       *bufio.Writer
    30  	csh     coverage.CounterSegmentHeader
    31  	tmp     []byte
    32  	cflavor coverage.CounterFlavor
    33  	segs    uint32
    34  	debug   bool
    35  }
    36  
    37  func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDataWriter {
    38  	r := &CoverageDataWriter{
    39  		stab: &stringtab.Writer{},
    40  		w:    bufio.NewWriter(w),
    41  
    42  		tmp:     make([]byte, 64),
    43  		cflavor: flav,
    44  	}
    45  	r.stab.InitWriter()
    46  	r.stab.Lookup("")
    47  	return r
    48  }
    49  
    50  // CounterVisitor describes a helper object used during counter file
    51  // writing; when writing counter data files, clients pass a
    52  // CounterVisitor to the write/emit routines, then the expectation is
    53  // that the VisitFuncs method will then invoke the callback "f" with
    54  // data for each function to emit to the file.
    55  type CounterVisitor interface {
    56  	VisitFuncs(f CounterVisitorFn) error
    57  }
    58  
    59  // CounterVisitorFn describes a callback function invoked when writing
    60  // coverage counter data.
    61  type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error
    62  
    63  // Write writes the contents of the count-data file to the writer
    64  // previously supplied to NewCoverageDataWriter. Returns an error
    65  // if something went wrong somewhere with the write.
    66  func (cfw *CoverageDataWriter) Write(metaFileHash [16]byte, args map[string]string, visitor CounterVisitor) error {
    67  	if err := cfw.writeHeader(metaFileHash); err != nil {
    68  		return err
    69  	}
    70  	return cfw.AppendSegment(args, visitor)
    71  }
    72  
    73  func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error {
    74  	sz := len(ws.BytesWritten())
    75  	zeros := []byte{0, 0, 0, 0}
    76  	rem := uint32(sz) % 4
    77  	if rem != 0 {
    78  		pad := zeros[:(4 - rem)]
    79  		if nw, err := ws.Write(pad); err != nil {
    80  			return err
    81  		} else if nw != len(pad) {
    82  			return fmt.Errorf("error: short write")
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  func (cfw *CoverageDataWriter) patchSegmentHeader(ws *slicewriter.WriteSeeker) error {
    89  	// record position
    90  	off, err := ws.Seek(0, io.SeekCurrent)
    91  	if err != nil {
    92  		return fmt.Errorf("error seeking in patchSegmentHeader: %v", err)
    93  	}
    94  	// seek back to start so that we can update the segment header
    95  	if _, err := ws.Seek(0, io.SeekStart); err != nil {
    96  		return fmt.Errorf("error seeking in patchSegmentHeader: %v", err)
    97  	}
    98  	if cfw.debug {
    99  		fmt.Fprintf(os.Stderr, "=-= writing counter segment header: %+v", cfw.csh)
   100  	}
   101  	if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil {
   102  		return err
   103  	}
   104  	// ... and finally return to the original offset.
   105  	if _, err := ws.Seek(off, io.SeekStart); err != nil {
   106  		return fmt.Errorf("error seeking in patchSegmentHeader: %v", err)
   107  	}
   108  	return nil
   109  }
   110  
   111  func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, ws *slicewriter.WriteSeeker) error {
   112  	if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil {
   113  		return err
   114  	}
   115  	hdrsz := uint32(len(ws.BytesWritten()))
   116  
   117  	// Write string table and args to a byte slice (since we need
   118  	// to capture offsets at various points), then emit the slice
   119  	// once we are done.
   120  	cfw.stab.Freeze()
   121  	if err := cfw.stab.Write(ws); err != nil {
   122  		return err
   123  	}
   124  	cfw.csh.StrTabLen = uint32(len(ws.BytesWritten())) - hdrsz
   125  
   126  	akeys := make([]string, 0, len(args))
   127  	for k := range args {
   128  		akeys = append(akeys, k)
   129  	}
   130  	sort.Strings(akeys)
   131  
   132  	wrULEB128 := func(v uint) error {
   133  		cfw.tmp = cfw.tmp[:0]
   134  		cfw.tmp = uleb128.AppendUleb128(cfw.tmp, v)
   135  		if _, err := ws.Write(cfw.tmp); err != nil {
   136  			return err
   137  		}
   138  		return nil
   139  	}
   140  
   141  	// Count of arg pairs.
   142  	if err := wrULEB128(uint(len(args))); err != nil {
   143  		return err
   144  	}
   145  	// Arg pairs themselves.
   146  	for _, k := range akeys {
   147  		ki := uint(cfw.stab.Lookup(k))
   148  		if err := wrULEB128(ki); err != nil {
   149  			return err
   150  		}
   151  		v := args[k]
   152  		vi := uint(cfw.stab.Lookup(v))
   153  		if err := wrULEB128(vi); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	if err := padToFourByteBoundary(ws); err != nil {
   158  		return err
   159  	}
   160  	cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz)
   161  
   162  	return nil
   163  }
   164  
   165  // AppendSegment appends a new segment to a counter data, with a new
   166  // args section followed by a payload of counter data clauses.
   167  func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor CounterVisitor) error {
   168  	cfw.stab = &stringtab.Writer{}
   169  	cfw.stab.InitWriter()
   170  	cfw.stab.Lookup("")
   171  
   172  	var err error
   173  	for k, v := range args {
   174  		cfw.stab.Lookup(k)
   175  		cfw.stab.Lookup(v)
   176  	}
   177  
   178  	ws := &slicewriter.WriteSeeker{}
   179  	if err = cfw.writeSegmentPreamble(args, ws); err != nil {
   180  		return err
   181  	}
   182  	if err = cfw.writeCounters(visitor, ws); err != nil {
   183  		return err
   184  	}
   185  	if err = cfw.patchSegmentHeader(ws); err != nil {
   186  		return err
   187  	}
   188  	if err := cfw.writeBytes(ws.BytesWritten()); err != nil {
   189  		return err
   190  	}
   191  	if err = cfw.writeFooter(); err != nil {
   192  		return err
   193  	}
   194  	if err := cfw.w.Flush(); err != nil {
   195  		return fmt.Errorf("write error: %v", err)
   196  	}
   197  	cfw.stab = nil
   198  	return nil
   199  }
   200  
   201  func (cfw *CoverageDataWriter) writeHeader(metaFileHash [16]byte) error {
   202  	// Emit file header.
   203  	ch := coverage.CounterFileHeader{
   204  		Magic:     coverage.CovCounterMagic,
   205  		Version:   coverage.CounterFileVersion,
   206  		MetaHash:  metaFileHash,
   207  		CFlavor:   cfw.cflavor,
   208  		BigEndian: false,
   209  	}
   210  	if err := binary.Write(cfw.w, binary.LittleEndian, ch); err != nil {
   211  		return err
   212  	}
   213  	return nil
   214  }
   215  
   216  func (cfw *CoverageDataWriter) writeBytes(b []byte) error {
   217  	if len(b) == 0 {
   218  		return nil
   219  	}
   220  	nw, err := cfw.w.Write(b)
   221  	if err != nil {
   222  		return fmt.Errorf("error writing counter data: %v", err)
   223  	}
   224  	if len(b) != nw {
   225  		return fmt.Errorf("error writing counter data: short write")
   226  	}
   227  	return nil
   228  }
   229  
   230  func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor, ws *slicewriter.WriteSeeker) error {
   231  	// Notes:
   232  	// - this version writes everything little-endian, which means
   233  	//   a call is needed to encode every value (expensive)
   234  	// - we may want to move to a model in which we just blast out
   235  	//   all counters, or possibly mmap the file and do the write
   236  	//   implicitly.
   237  	ctrb := make([]byte, 4)
   238  	wrval := func(val uint32) error {
   239  		var buf []byte
   240  		var towr int
   241  		if cfw.cflavor == coverage.CtrRaw {
   242  			binary.LittleEndian.PutUint32(ctrb, val)
   243  			buf = ctrb
   244  			towr = 4
   245  		} else if cfw.cflavor == coverage.CtrULeb128 {
   246  			cfw.tmp = cfw.tmp[:0]
   247  			cfw.tmp = uleb128.AppendUleb128(cfw.tmp, uint(val))
   248  			buf = cfw.tmp
   249  			towr = len(buf)
   250  		} else {
   251  			panic("internal error: bad counter flavor")
   252  		}
   253  		if sz, err := ws.Write(buf); err != nil {
   254  			return err
   255  		} else if sz != towr {
   256  			return fmt.Errorf("writing counters: short write")
   257  		}
   258  		return nil
   259  	}
   260  
   261  	// Write out entries for each live function.
   262  	emitter := func(pkid uint32, funcid uint32, counters []uint32) error {
   263  		cfw.csh.FcnEntries++
   264  		if err := wrval(uint32(len(counters))); err != nil {
   265  			return err
   266  		}
   267  
   268  		if err := wrval(pkid); err != nil {
   269  			return err
   270  		}
   271  
   272  		if err := wrval(funcid); err != nil {
   273  			return err
   274  		}
   275  		for _, val := range counters {
   276  			if err := wrval(val); err != nil {
   277  				return err
   278  			}
   279  		}
   280  		return nil
   281  	}
   282  	if err := visitor.VisitFuncs(emitter); err != nil {
   283  		return err
   284  	}
   285  	return nil
   286  }
   287  
   288  func (cfw *CoverageDataWriter) writeFooter() error {
   289  	cfw.segs++
   290  	cf := coverage.CounterFileFooter{
   291  		Magic:       coverage.CovCounterMagic,
   292  		NumSegments: cfw.segs,
   293  	}
   294  	if err := binary.Write(cfw.w, binary.LittleEndian, cf); err != nil {
   295  		return err
   296  	}
   297  	return nil
   298  }