summaryrefslogtreecommitdiffstats
path: root/src/internal/coverage/decodecounter/decodecounterfile.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/internal/coverage/decodecounter/decodecounterfile.go
parentInitial commit. (diff)
downloadgolang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz
golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/internal/coverage/decodecounter/decodecounterfile.go')
-rw-r--r--src/internal/coverage/decodecounter/decodecounterfile.go373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/internal/coverage/decodecounter/decodecounterfile.go b/src/internal/coverage/decodecounter/decodecounterfile.go
new file mode 100644
index 0000000..fce060a
--- /dev/null
+++ b/src/internal/coverage/decodecounter/decodecounterfile.go
@@ -0,0 +1,373 @@
+// 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 decodecounter
+
+import (
+ "encoding/binary"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/slicereader"
+ "internal/coverage/stringtab"
+ "io"
+ "os"
+ "strconv"
+ "unsafe"
+)
+
+// This file contains helpers for reading counter data files created
+// during the executions of a coverage-instrumented binary.
+
+type CounterDataReader struct {
+ stab *stringtab.Reader
+ args map[string]string
+ osargs []string
+ goarch string // GOARCH setting from run that produced counter data
+ goos string // GOOS setting from run that produced counter data
+ mr io.ReadSeeker
+ hdr coverage.CounterFileHeader
+ ftr coverage.CounterFileFooter
+ shdr coverage.CounterSegmentHeader
+ u32b []byte
+ u8b []byte
+ fcnCount uint32
+ segCount uint32
+ debug bool
+}
+
+func NewCounterDataReader(fn string, rs io.ReadSeeker) (*CounterDataReader, error) {
+ cdr := &CounterDataReader{
+ mr: rs,
+ u32b: make([]byte, 4),
+ u8b: make([]byte, 1),
+ }
+ // Read header
+ if err := binary.Read(rs, binary.LittleEndian, &cdr.hdr); err != nil {
+ return nil, err
+ }
+ if cdr.debug {
+ fmt.Fprintf(os.Stderr, "=-= counter file header: %+v\n", cdr.hdr)
+ }
+ if !checkMagic(cdr.hdr.Magic) {
+ return nil, fmt.Errorf("invalid magic string: not a counter data file")
+ }
+ if cdr.hdr.Version > coverage.CounterFileVersion {
+ return nil, fmt.Errorf("version data incompatibility: reader is %d data is %d", coverage.CounterFileVersion, cdr.hdr.Version)
+ }
+
+ // Read footer.
+ if err := cdr.readFooter(); err != nil {
+ return nil, err
+ }
+ // Seek back to just past the file header.
+ hsz := int64(unsafe.Sizeof(cdr.hdr))
+ if _, err := cdr.mr.Seek(hsz, io.SeekStart); err != nil {
+ return nil, err
+ }
+ // Read preamble for first segment.
+ if err := cdr.readSegmentPreamble(); err != nil {
+ return nil, err
+ }
+ return cdr, nil
+}
+
+func checkMagic(v [4]byte) bool {
+ g := coverage.CovCounterMagic
+ return v[0] == g[0] && v[1] == g[1] && v[2] == g[2] && v[3] == g[3]
+}
+
+func (cdr *CounterDataReader) readFooter() error {
+ ftrSize := int64(unsafe.Sizeof(cdr.ftr))
+ if _, err := cdr.mr.Seek(-ftrSize, io.SeekEnd); err != nil {
+ return err
+ }
+ if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.ftr); err != nil {
+ return err
+ }
+ if !checkMagic(cdr.ftr.Magic) {
+ return fmt.Errorf("invalid magic string (not a counter data file)")
+ }
+ if cdr.ftr.NumSegments == 0 {
+ return fmt.Errorf("invalid counter data file (no segments)")
+ }
+ return nil
+}
+
+// readSegmentPreamble reads and consumes the segment header, segment string
+// table, and segment args table.
+func (cdr *CounterDataReader) readSegmentPreamble() error {
+ // Read segment header.
+ if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.shdr); err != nil {
+ return err
+ }
+ if cdr.debug {
+ fmt.Fprintf(os.Stderr, "=-= read counter segment header: %+v", cdr.shdr)
+ fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n",
+ cdr.shdr.FcnEntries, cdr.shdr.StrTabLen, cdr.shdr.ArgsLen)
+ }
+
+ // Read string table and args.
+ if err := cdr.readStringTable(); err != nil {
+ return err
+ }
+ if err := cdr.readArgs(); err != nil {
+ return err
+ }
+ // Seek past any padding to bring us up to a 4-byte boundary.
+ if of, err := cdr.mr.Seek(0, io.SeekCurrent); err != nil {
+ return err
+ } else {
+ rem := of % 4
+ if rem != 0 {
+ pad := 4 - rem
+ if _, err := cdr.mr.Seek(pad, io.SeekCurrent); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (cdr *CounterDataReader) readStringTable() error {
+ b := make([]byte, cdr.shdr.StrTabLen)
+ nr, err := cdr.mr.Read(b)
+ if err != nil {
+ return err
+ }
+ if nr != int(cdr.shdr.StrTabLen) {
+ return fmt.Errorf("error: short read on string table")
+ }
+ slr := slicereader.NewReader(b, false /* not readonly */)
+ cdr.stab = stringtab.NewReader(slr)
+ cdr.stab.Read()
+ return nil
+}
+
+func (cdr *CounterDataReader) readArgs() error {
+ b := make([]byte, cdr.shdr.ArgsLen)
+ nr, err := cdr.mr.Read(b)
+ if err != nil {
+ return err
+ }
+ if nr != int(cdr.shdr.ArgsLen) {
+ return fmt.Errorf("error: short read on args table")
+ }
+ slr := slicereader.NewReader(b, false /* not readonly */)
+ sget := func() (string, error) {
+ kidx := slr.ReadULEB128()
+ if int(kidx) >= cdr.stab.Entries() {
+ return "", fmt.Errorf("malformed string table ref")
+ }
+ return cdr.stab.Get(uint32(kidx)), nil
+ }
+ nents := slr.ReadULEB128()
+ cdr.args = make(map[string]string, int(nents))
+ for i := uint64(0); i < nents; i++ {
+ k, errk := sget()
+ if errk != nil {
+ return errk
+ }
+ v, errv := sget()
+ if errv != nil {
+ return errv
+ }
+ if _, ok := cdr.args[k]; ok {
+ return fmt.Errorf("malformed args table")
+ }
+ cdr.args[k] = v
+ }
+ if argcs, ok := cdr.args["argc"]; ok {
+ argc, err := strconv.Atoi(argcs)
+ if err != nil {
+ return fmt.Errorf("malformed argc in counter data file args section")
+ }
+ cdr.osargs = make([]string, 0, argc)
+ for i := 0; i < argc; i++ {
+ arg := cdr.args[fmt.Sprintf("argv%d", i)]
+ cdr.osargs = append(cdr.osargs, arg)
+ }
+ }
+ if goos, ok := cdr.args["GOOS"]; ok {
+ cdr.goos = goos
+ }
+ if goarch, ok := cdr.args["GOARCH"]; ok {
+ cdr.goarch = goarch
+ }
+ return nil
+}
+
+// OsArgs returns the program arguments (saved from os.Args during
+// the run of the instrumented binary) read from the counter
+// data file. Not all coverage data files will have os.Args values;
+// for example, if a data file is produced by merging coverage
+// data from two distinct runs, no os args will be available (an
+// empty list is returned).
+func (cdr *CounterDataReader) OsArgs() []string {
+ return cdr.osargs
+}
+
+// Goos returns the GOOS setting in effect for the "-cover" binary
+// that produced this counter data file. The GOOS value may be
+// empty in the case where the counter data file was produced
+// from a merge in which more than one GOOS value was present.
+func (cdr *CounterDataReader) Goos() string {
+ return cdr.goos
+}
+
+// Goarch returns the GOARCH setting in effect for the "-cover" binary
+// that produced this counter data file. The GOARCH value may be
+// empty in the case where the counter data file was produced
+// from a merge in which more than one GOARCH value was present.
+func (cdr *CounterDataReader) Goarch() string {
+ return cdr.goarch
+}
+
+// FuncPayload encapsulates the counter data payload for a single
+// function as read from a counter data file.
+type FuncPayload struct {
+ PkgIdx uint32
+ FuncIdx uint32
+ Counters []uint32
+}
+
+// NumSegments returns the number of execution segments in the file.
+func (cdr *CounterDataReader) NumSegments() uint32 {
+ return cdr.ftr.NumSegments
+}
+
+// BeginNextSegment sets up the the reader to read the next segment,
+// returning TRUE if we do have another segment to read, or FALSE
+// if we're done with all the segments (also an error if
+// something went wrong).
+func (cdr *CounterDataReader) BeginNextSegment() (bool, error) {
+ if cdr.segCount >= cdr.ftr.NumSegments {
+ return false, nil
+ }
+ cdr.segCount++
+ cdr.fcnCount = 0
+ // Seek past footer from last segment.
+ ftrSize := int64(unsafe.Sizeof(cdr.ftr))
+ if _, err := cdr.mr.Seek(ftrSize, io.SeekCurrent); err != nil {
+ return false, err
+ }
+ // Read preamble for this segment.
+ if err := cdr.readSegmentPreamble(); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// NumFunctionsInSegment returns the number of live functions
+// in the currently selected segment.
+func (cdr *CounterDataReader) NumFunctionsInSegment() uint32 {
+ return uint32(cdr.shdr.FcnEntries)
+}
+
+const supportDeadFunctionsInCounterData = false
+
+// NextFunc reads data for the next function in this current segment
+// into "p", returning TRUE if the read was successful or FALSE
+// if we've read all the functions already (also an error if
+// something went wrong with the read or we hit a premature
+// EOF).
+func (cdr *CounterDataReader) NextFunc(p *FuncPayload) (bool, error) {
+ if cdr.fcnCount >= uint32(cdr.shdr.FcnEntries) {
+ return false, nil
+ }
+ cdr.fcnCount++
+ var rdu32 func() (uint32, error)
+ if cdr.hdr.CFlavor == coverage.CtrULeb128 {
+ rdu32 = func() (uint32, error) {
+ var shift uint
+ var value uint64
+ for {
+ _, err := cdr.mr.Read(cdr.u8b)
+ if err != nil {
+ return 0, err
+ }
+ b := cdr.u8b[0]
+ value |= (uint64(b&0x7F) << shift)
+ if b&0x80 == 0 {
+ break
+ }
+ shift += 7
+ }
+ return uint32(value), nil
+ }
+ } else if cdr.hdr.CFlavor == coverage.CtrRaw {
+ if cdr.hdr.BigEndian {
+ rdu32 = func() (uint32, error) {
+ n, err := cdr.mr.Read(cdr.u32b)
+ if err != nil {
+ return 0, err
+ }
+ if n != 4 {
+ return 0, io.EOF
+ }
+ return binary.BigEndian.Uint32(cdr.u32b), nil
+ }
+ } else {
+ rdu32 = func() (uint32, error) {
+ n, err := cdr.mr.Read(cdr.u32b)
+ if err != nil {
+ return 0, err
+ }
+ if n != 4 {
+ return 0, io.EOF
+ }
+ return binary.LittleEndian.Uint32(cdr.u32b), nil
+ }
+ }
+ } else {
+ panic("internal error: unknown counter flavor")
+ }
+
+ // Alternative/experimental path: one way we could handling writing
+ // out counter data would be to just memcpy the counter segment
+ // out to a file, meaning that a region in the counter memory
+ // corresponding to a dead (never-executed) function would just be
+ // zeroes. The code path below handles this case.
+ var nc uint32
+ var err error
+ if supportDeadFunctionsInCounterData {
+ for {
+ nc, err = rdu32()
+ if err == io.EOF {
+ return false, io.EOF
+ } else if err != nil {
+ break
+ }
+ if nc != 0 {
+ break
+ }
+ }
+ } else {
+ nc, err = rdu32()
+ }
+ if err != nil {
+ return false, err
+ }
+
+ // Read package and func indices.
+ p.PkgIdx, err = rdu32()
+ if err != nil {
+ return false, err
+ }
+ p.FuncIdx, err = rdu32()
+ if err != nil {
+ return false, err
+ }
+ if cap(p.Counters) < 1024 {
+ p.Counters = make([]uint32, 0, 1024)
+ }
+ p.Counters = p.Counters[:0]
+ for i := uint32(0); i < nc; i++ {
+ v, err := rdu32()
+ if err != nil {
+ return false, err
+ }
+ p.Counters = append(p.Counters, v)
+ }
+ return true, nil
+}