| | |
| | |
| | |
| |
|
| | package encodecounter |
| |
|
| | import ( |
| | "bufio" |
| | "encoding/binary" |
| | "fmt" |
| | "internal/coverage" |
| | "internal/coverage/slicewriter" |
| | "internal/coverage/stringtab" |
| | "internal/coverage/uleb128" |
| | "io" |
| | "maps" |
| | "os" |
| | "slices" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | type CoverageDataWriter struct { |
| | stab *stringtab.Writer |
| | w *bufio.Writer |
| | csh coverage.CounterSegmentHeader |
| | 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 |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | type CounterVisitor interface { |
| | VisitFuncs(f CounterVisitorFn) error |
| | } |
| |
|
| | |
| | |
| | type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error |
| |
|
| | |
| | |
| | |
| | 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) patchSegmentHeader(ws *slicewriter.WriteSeeker) error { |
| | |
| | off, err := ws.Seek(0, io.SeekCurrent) |
| | if err != nil { |
| | return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) |
| | } |
| | |
| | if _, err := ws.Seek(0, io.SeekStart); err != nil { |
| | return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) |
| | } |
| | if cfw.debug { |
| | fmt.Fprintf(os.Stderr, "=-= writing counter segment header: %+v", cfw.csh) |
| | } |
| | if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { |
| | return err |
| | } |
| | |
| | if _, err := ws.Seek(off, io.SeekStart); err != nil { |
| | return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) |
| | } |
| | return nil |
| | } |
| |
|
| | func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, ws *slicewriter.WriteSeeker) error { |
| | if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { |
| | return err |
| | } |
| | hdrsz := uint32(len(ws.BytesWritten())) |
| |
|
| | |
| | |
| | |
| | cfw.stab.Freeze() |
| | if err := cfw.stab.Write(ws); err != nil { |
| | return err |
| | } |
| | cfw.csh.StrTabLen = uint32(len(ws.BytesWritten())) - hdrsz |
| |
|
| | akeys := slices.Sorted(maps.Keys(args)) |
| |
|
| | 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 |
| | } |
| |
|
| | |
| | if err := wrULEB128(uint(len(args))); err != nil { |
| | return err |
| | } |
| | |
| | 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 |
| | } |
| | cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz) |
| |
|
| | return nil |
| | } |
| |
|
| | |
| | |
| | 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) |
| | } |
| |
|
| | ws := &slicewriter.WriteSeeker{} |
| | if err = cfw.writeSegmentPreamble(args, ws); err != nil { |
| | return err |
| | } |
| | if err = cfw.writeCounters(visitor, ws); err != nil { |
| | return err |
| | } |
| | if err = cfw.patchSegmentHeader(ws); err != nil { |
| | return err |
| | } |
| | if err := cfw.writeBytes(ws.BytesWritten()); 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 { |
| | |
| | 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, ws *slicewriter.WriteSeeker) error { |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 := ws.Write(buf); err != nil { |
| | return err |
| | } else if sz != towr { |
| | return fmt.Errorf("writing counters: short write") |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | emitter := func(pkid uint32, funcid uint32, counters []uint32) error { |
| | cfw.csh.FcnEntries++ |
| | 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 |
| | } |
| |
|