| // 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" | |
| "maps" | |
| "os" | |
| "slices" | |
| ) | |
| // 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 | |
| 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 | |
| } | |
| // CounterVisitor describes a helper object used during counter file | |
| // writing; when writing counter data files, clients pass a | |
| // CounterVisitor to the write/emit routines, then 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 { | |
| 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) patchSegmentHeader(ws *slicewriter.WriteSeeker) error { | |
| // record position | |
| off, err := ws.Seek(0, io.SeekCurrent) | |
| if err != nil { | |
| return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) | |
| } | |
| // seek back to start so that we can update the segment header | |
| 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 | |
| } | |
| // ... and finally return to the original offset. | |
| 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())) | |
| // 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() | |
| 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 | |
| } | |
| // 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 | |
| } | |
| cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz) | |
| 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) | |
| } | |
| 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 { | |
| // 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, ws *slicewriter.WriteSeeker) 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 := ws.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 { | |
| 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 | |
| } |