blob: 8db4f514e8eae98ef199fb899925664dc791255f [file] [log] [blame]
// 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 encodecounter
import (
"bufio"
"encoding/binary"
"fmt"
"internal/coverage"
"internal/coverage/slicewriter"
"internal/coverage/stringtab"
"internal/coverage/uleb128"
"io"
"os"
"sort"
)
// This package contains APIs and helpers for encoding initial portions
// of the counter data files emitted at runtime when coverage instrumentation
// is enabled. Counter data files may contain multiple segments; the file
// header and first segment are written via the "Write" method below, and
// additional segments can then be added using "AddSegment".
type CoverageDataWriter struct {
stab *stringtab.Writer
w *bufio.Writer
tmp []byte
cflavor coverage.CounterFlavor
segs uint32
debug bool
}
func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDataWriter {
r := &CoverageDataWriter{
stab: &stringtab.Writer{},
w: bufio.NewWriter(w),
tmp: make([]byte, 64),
cflavor: flav,
}
r.stab.InitWriter()
r.stab.Lookup("")
return r
}
// CounterVisitor describes a helper object used during counter file
// writing; when writing counter data files, clients pass a
// CounterVisitor to the write/emit routines. The writers will then
// first invoke the visitor's NumFuncs() method to find out how many
// function's worth of data to write, then it will invoke VisitFuncs.
// The expectation is that the VisitFuncs method will then invoke the
// callback "f" with data for each function to emit to the file.
type CounterVisitor interface {
NumFuncs() (int, error)
VisitFuncs(f CounterVisitorFn) error
}
// CounterVisitorFn describes a callback function invoked when writing
// coverage counter data.
type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error
// Write writes the contents of the count-data file to the writer
// previously supplied to NewCoverageDataWriter. Returns an error
// if something went wrong somewhere with the write.
func (cfw *CoverageDataWriter) Write(metaFileHash [16]byte, args map[string]string, visitor CounterVisitor) error {
if err := cfw.writeHeader(metaFileHash); err != nil {
return err
}
return cfw.AppendSegment(args, visitor)
}
func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error {
sz := len(ws.BytesWritten())
zeros := []byte{0, 0, 0, 0}
rem := uint32(sz) % 4
if rem != 0 {
pad := zeros[:(4 - rem)]
if nw, err := ws.Write(pad); err != nil {
return err
} else if nw != len(pad) {
return fmt.Errorf("error: short write")
}
}
return nil
}
func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, visitor CounterVisitor) error {
var csh coverage.CounterSegmentHeader
if nf, err := visitor.NumFuncs(); err != nil {
return err
} else {
csh.FcnEntries = uint64(nf)
}
// Write string table and args to a byte slice (since we need
// to capture offsets at various points), then emit the slice
// once we are done.
cfw.stab.Freeze()
ws := &slicewriter.WriteSeeker{}
if err := cfw.stab.Write(ws); err != nil {
return err
}
csh.StrTabLen = uint32(len(ws.BytesWritten()))
akeys := make([]string, 0, len(args))
for k := range args {
akeys = append(akeys, k)
}
sort.Strings(akeys)
wrULEB128 := func(v uint) error {
cfw.tmp = cfw.tmp[:0]
cfw.tmp = uleb128.AppendUleb128(cfw.tmp, v)
if _, err := ws.Write(cfw.tmp); err != nil {
return err
}
return nil
}
// Count of arg pairs.
if err := wrULEB128(uint(len(args))); err != nil {
return err
}
// Arg pairs themselves.
for _, k := range akeys {
ki := uint(cfw.stab.Lookup(k))
if err := wrULEB128(ki); err != nil {
return err
}
v := args[k]
vi := uint(cfw.stab.Lookup(v))
if err := wrULEB128(vi); err != nil {
return err
}
}
if err := padToFourByteBoundary(ws); err != nil {
return err
}
csh.ArgsLen = uint32(len(ws.BytesWritten())) - csh.StrTabLen
if cfw.debug {
fmt.Fprintf(os.Stderr, "=-= counter segment header: %+v", csh)
fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n",
csh.FcnEntries, csh.StrTabLen, csh.ArgsLen)
}
// At this point we can now do the actual write.
if err := binary.Write(cfw.w, binary.LittleEndian, csh); err != nil {
return err
}
if err := cfw.writeBytes(ws.BytesWritten()); err != nil {
return err
}
return nil
}
// AppendSegment appends a new segment to a counter data, with a new
// args section followed by a payload of counter data clauses.
func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor CounterVisitor) error {
cfw.stab = &stringtab.Writer{}
cfw.stab.InitWriter()
cfw.stab.Lookup("")
var err error
for k, v := range args {
cfw.stab.Lookup(k)
cfw.stab.Lookup(v)
}
if err = cfw.writeSegmentPreamble(args, visitor); err != nil {
return err
}
if err = cfw.writeCounters(visitor); err != nil {
return err
}
if err = cfw.writeFooter(); err != nil {
return err
}
if err := cfw.w.Flush(); err != nil {
return fmt.Errorf("write error: %v", err)
}
cfw.stab = nil
return nil
}
func (cfw *CoverageDataWriter) writeHeader(metaFileHash [16]byte) error {
// Emit file header.
ch := coverage.CounterFileHeader{
Magic: coverage.CovCounterMagic,
Version: coverage.CounterFileVersion,
MetaHash: metaFileHash,
CFlavor: cfw.cflavor,
BigEndian: false,
}
if err := binary.Write(cfw.w, binary.LittleEndian, ch); err != nil {
return err
}
return nil
}
func (cfw *CoverageDataWriter) writeBytes(b []byte) error {
if len(b) == 0 {
return nil
}
nw, err := cfw.w.Write(b)
if err != nil {
return fmt.Errorf("error writing counter data: %v", err)
}
if len(b) != nw {
return fmt.Errorf("error writing counter data: short write")
}
return nil
}
func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor) error {
// Notes:
// - this version writes everything little-endian, which means
// a call is needed to encode every value (expensive)
// - we may want to move to a model in which we just blast out
// all counters, or possibly mmap the file and do the write
// implicitly.
ctrb := make([]byte, 4)
wrval := func(val uint32) error {
var buf []byte
var towr int
if cfw.cflavor == coverage.CtrRaw {
binary.LittleEndian.PutUint32(ctrb, val)
buf = ctrb
towr = 4
} else if cfw.cflavor == coverage.CtrULeb128 {
cfw.tmp = cfw.tmp[:0]
cfw.tmp = uleb128.AppendUleb128(cfw.tmp, uint(val))
buf = cfw.tmp
towr = len(buf)
} else {
panic("internal error: bad counter flavor")
}
if sz, err := cfw.w.Write(buf); err != nil {
return err
} else if sz != towr {
return fmt.Errorf("writing counters: short write")
}
return nil
}
// Write out entries for each live function.
emitter := func(pkid uint32, funcid uint32, counters []uint32) error {
if err := wrval(uint32(len(counters))); err != nil {
return err
}
if err := wrval(pkid); err != nil {
return err
}
if err := wrval(funcid); err != nil {
return err
}
for _, val := range counters {
if err := wrval(val); err != nil {
return err
}
}
return nil
}
if err := visitor.VisitFuncs(emitter); err != nil {
return err
}
return nil
}
func (cfw *CoverageDataWriter) writeFooter() error {
cfw.segs++
cf := coverage.CounterFileFooter{
Magic: coverage.CovCounterMagic,
NumSegments: cfw.segs,
}
if err := binary.Write(cfw.w, binary.LittleEndian, cf); err != nil {
return err
}
return nil
}