summaryrefslogtreecommitdiffstats
path: root/src/internal/coverage
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
commitf6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch)
tree7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/internal/coverage
parentInitial commit. (diff)
downloadgolang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz
golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/internal/coverage/calloc/batchcounteralloc.go29
-rw-r--r--src/internal/coverage/cformat/fmt_test.go155
-rw-r--r--src/internal/coverage/cformat/format.go352
-rw-r--r--src/internal/coverage/cmerge/merge.go127
-rw-r--r--src/internal/coverage/cmerge/merge_test.go118
-rw-r--r--src/internal/coverage/decodecounter/decodecounterfile.go373
-rw-r--r--src/internal/coverage/decodemeta/decode.go136
-rw-r--r--src/internal/coverage/decodemeta/decodefile.go223
-rw-r--r--src/internal/coverage/defs.go388
-rw-r--r--src/internal/coverage/encodecounter/encode.go297
-rw-r--r--src/internal/coverage/encodemeta/encode.go215
-rw-r--r--src/internal/coverage/encodemeta/encodefile.go132
-rw-r--r--src/internal/coverage/pkid.go81
-rw-r--r--src/internal/coverage/pods/pods.go197
-rw-r--r--src/internal/coverage/pods/pods_test.go142
-rw-r--r--src/internal/coverage/rtcov/rtcov.go34
-rw-r--r--src/internal/coverage/slicereader/slicereader.go123
-rw-r--r--src/internal/coverage/slicereader/slr_test.go95
-rw-r--r--src/internal/coverage/slicewriter/slicewriter.go80
-rw-r--r--src/internal/coverage/slicewriter/slw_test.go134
-rw-r--r--src/internal/coverage/stringtab/stringtab.go139
-rw-r--r--src/internal/coverage/test/counter_test.go237
-rw-r--r--src/internal/coverage/test/roundtrip_test.go331
-rw-r--r--src/internal/coverage/uleb128/uleb128.go20
24 files changed, 4158 insertions, 0 deletions
diff --git a/src/internal/coverage/calloc/batchcounteralloc.go b/src/internal/coverage/calloc/batchcounteralloc.go
new file mode 100644
index 0000000..2b6495d
--- /dev/null
+++ b/src/internal/coverage/calloc/batchcounteralloc.go
@@ -0,0 +1,29 @@
+// Copyright 2022 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 calloc
+
+// This package contains a simple "batch" allocator for allocating
+// coverage counters (slices of uint32 basically), for working with
+// coverage data files. Collections of counter arrays tend to all be
+// live/dead over the same time period, so a good fit for batch
+// allocation.
+
+type BatchCounterAlloc struct {
+ pool []uint32
+}
+
+func (ca *BatchCounterAlloc) AllocateCounters(n int) []uint32 {
+ const chunk = 8192
+ if n > cap(ca.pool) {
+ siz := chunk
+ if n > chunk {
+ siz = n
+ }
+ ca.pool = make([]uint32, siz)
+ }
+ rv := ca.pool[:n]
+ ca.pool = ca.pool[n:]
+ return rv
+}
diff --git a/src/internal/coverage/cformat/fmt_test.go b/src/internal/coverage/cformat/fmt_test.go
new file mode 100644
index 0000000..f5ed01b
--- /dev/null
+++ b/src/internal/coverage/cformat/fmt_test.go
@@ -0,0 +1,155 @@
+// Copyright 2022 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 cformat_test
+
+import (
+ "internal/coverage"
+ "internal/coverage/cformat"
+ "slices"
+ "strings"
+ "testing"
+)
+
+func TestBasics(t *testing.T) {
+ fm := cformat.NewFormatter(coverage.CtrModeAtomic)
+
+ mku := func(stl, enl, nx uint32) coverage.CoverableUnit {
+ return coverage.CoverableUnit{
+ StLine: stl,
+ EnLine: enl,
+ NxStmts: nx,
+ }
+ }
+ fn1units := []coverage.CoverableUnit{
+ mku(10, 11, 2),
+ mku(15, 11, 1),
+ }
+ fn2units := []coverage.CoverableUnit{
+ mku(20, 25, 3),
+ mku(30, 31, 2),
+ mku(33, 40, 7),
+ }
+ fn3units := []coverage.CoverableUnit{
+ mku(99, 100, 1),
+ }
+ fm.SetPackage("my/pack1")
+ for k, u := range fn1units {
+ fm.AddUnit("p.go", "f1", false, u, uint32(k))
+ }
+ for k, u := range fn2units {
+ fm.AddUnit("q.go", "f2", false, u, 0)
+ fm.AddUnit("q.go", "f2", false, u, uint32(k))
+ }
+ fm.SetPackage("my/pack2")
+ for _, u := range fn3units {
+ fm.AddUnit("lit.go", "f3", true, u, 0)
+ }
+
+ var b1, b2, b3, b4 strings.Builder
+ if err := fm.EmitTextual(&b1); err != nil {
+ t.Fatalf("EmitTextual returned %v", err)
+ }
+ wantText := strings.TrimSpace(`
+mode: atomic
+p.go:10.0,11.0 2 0
+p.go:15.0,11.0 1 1
+q.go:20.0,25.0 3 0
+q.go:30.0,31.0 2 1
+q.go:33.0,40.0 7 2
+lit.go:99.0,100.0 1 0`)
+ gotText := strings.TrimSpace(b1.String())
+ if wantText != gotText {
+ t.Errorf("emit text: got:\n%s\nwant:\n%s\n", gotText, wantText)
+ }
+
+ // Percent output with no aggregation.
+ noCoverPkg := ""
+ if err := fm.EmitPercent(&b2, noCoverPkg, false, false); err != nil {
+ t.Fatalf("EmitPercent returned %v", err)
+ }
+ wantPercent := strings.Fields(`
+ my/pack1 coverage: 66.7% of statements
+ my/pack2 coverage: 0.0% of statements
+`)
+ gotPercent := strings.Fields(b2.String())
+ if !slices.Equal(wantPercent, gotPercent) {
+ t.Errorf("emit percent: got:\n%+v\nwant:\n%+v\n",
+ gotPercent, wantPercent)
+ }
+
+ // Percent mode with aggregation.
+ withCoverPkg := " in ./..."
+ if err := fm.EmitPercent(&b3, withCoverPkg, false, true); err != nil {
+ t.Fatalf("EmitPercent returned %v", err)
+ }
+ wantPercent = strings.Fields(`
+ coverage: 62.5% of statements in ./...
+`)
+ gotPercent = strings.Fields(b3.String())
+ if !slices.Equal(wantPercent, gotPercent) {
+ t.Errorf("emit percent: got:\n%+v\nwant:\n%+v\n",
+ gotPercent, wantPercent)
+ }
+
+ if err := fm.EmitFuncs(&b4); err != nil {
+ t.Fatalf("EmitFuncs returned %v", err)
+ }
+ wantFuncs := strings.TrimSpace(`
+p.go:10: f1 33.3%
+q.go:20: f2 75.0%
+total (statements) 62.5%`)
+ gotFuncs := strings.TrimSpace(b4.String())
+ if wantFuncs != gotFuncs {
+ t.Errorf("emit funcs: got:\n%s\nwant:\n%s\n", gotFuncs, wantFuncs)
+ }
+ if false {
+ t.Logf("text is %s\n", b1.String())
+ t.Logf("perc is %s\n", b2.String())
+ t.Logf("perc2 is %s\n", b3.String())
+ t.Logf("funcs is %s\n", b4.String())
+ }
+}
+
+func TestEmptyPackages(t *testing.T) {
+
+ fm := cformat.NewFormatter(coverage.CtrModeAtomic)
+ fm.SetPackage("my/pack1")
+ fm.SetPackage("my/pack2")
+
+ // No aggregation.
+ {
+ var b strings.Builder
+ noCoverPkg := ""
+ if err := fm.EmitPercent(&b, noCoverPkg, true, false); err != nil {
+ t.Fatalf("EmitPercent returned %v", err)
+ }
+ wantPercent := strings.Fields(`
+ my/pack1 coverage: [no statements]
+ my/pack2 coverage: [no statements]
+`)
+ gotPercent := strings.Fields(b.String())
+ if !slices.Equal(wantPercent, gotPercent) {
+ t.Errorf("emit percent: got:\n%+v\nwant:\n%+v\n",
+ gotPercent, wantPercent)
+ }
+ }
+
+ // With aggregation.
+ {
+ var b strings.Builder
+ noCoverPkg := ""
+ if err := fm.EmitPercent(&b, noCoverPkg, true, true); err != nil {
+ t.Fatalf("EmitPercent returned %v", err)
+ }
+ wantPercent := strings.Fields(`
+ coverage: [no statements]
+`)
+ gotPercent := strings.Fields(b.String())
+ if !slices.Equal(wantPercent, gotPercent) {
+ t.Errorf("emit percent: got:\n%+v\nwant:\n%+v\n",
+ gotPercent, wantPercent)
+ }
+ }
+}
diff --git a/src/internal/coverage/cformat/format.go b/src/internal/coverage/cformat/format.go
new file mode 100644
index 0000000..7e7a277
--- /dev/null
+++ b/src/internal/coverage/cformat/format.go
@@ -0,0 +1,352 @@
+// Copyright 2022 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 cformat
+
+// This package provides apis for producing human-readable summaries
+// of coverage data (e.g. a coverage percentage for a given package or
+// set of packages) and for writing data in the legacy test format
+// emitted by "go test -coverprofile=<outfile>".
+//
+// The model for using these apis is to create a Formatter object,
+// then make a series of calls to SetPackage and AddUnit passing in
+// data read from coverage meta-data and counter-data files. E.g.
+//
+// myformatter := cformat.NewFormatter()
+// ...
+// for each package P in meta-data file: {
+// myformatter.SetPackage(P)
+// for each function F in P: {
+// for each coverable unit U in F: {
+// myformatter.AddUnit(U)
+// }
+// }
+// }
+// myformatter.EmitPercent(os.Stdout, "", true, true)
+// myformatter.EmitTextual(somefile)
+//
+// These apis are linked into tests that are built with "-cover", and
+// called at the end of test execution to produce text output or
+// emit coverage percentages.
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/cmerge"
+ "io"
+ "sort"
+ "text/tabwriter"
+)
+
+type Formatter struct {
+ // Maps import path to package state.
+ pm map[string]*pstate
+ // Records current package being visited.
+ pkg string
+ // Pointer to current package state.
+ p *pstate
+ // Counter mode.
+ cm coverage.CounterMode
+}
+
+// pstate records package-level coverage data state:
+// - a table of functions (file/fname/literal)
+// - a map recording the index/ID of each func encountered so far
+// - a table storing execution count for the coverable units in each func
+type pstate struct {
+ // slice of unique functions
+ funcs []fnfile
+ // maps function to index in slice above (index acts as function ID)
+ funcTable map[fnfile]uint32
+
+ // A table storing coverage counts for each coverable unit.
+ unitTable map[extcu]uint32
+}
+
+// extcu encapsulates a coverable unit within some function.
+type extcu struct {
+ fnfid uint32 // index into p.funcs slice
+ coverage.CoverableUnit
+}
+
+// fnfile is a function-name/file-name tuple.
+type fnfile struct {
+ file string
+ fname string
+ lit bool
+}
+
+func NewFormatter(cm coverage.CounterMode) *Formatter {
+ return &Formatter{
+ pm: make(map[string]*pstate),
+ cm: cm,
+ }
+}
+
+// SetPackage tells the formatter that we're about to visit the
+// coverage data for the package with the specified import path.
+// Note that it's OK to call SetPackage more than once with the
+// same import path; counter data values will be accumulated.
+func (fm *Formatter) SetPackage(importpath string) {
+ if importpath == fm.pkg {
+ return
+ }
+ fm.pkg = importpath
+ ps, ok := fm.pm[importpath]
+ if !ok {
+ ps = new(pstate)
+ fm.pm[importpath] = ps
+ ps.unitTable = make(map[extcu]uint32)
+ ps.funcTable = make(map[fnfile]uint32)
+ }
+ fm.p = ps
+}
+
+// AddUnit passes info on a single coverable unit (file, funcname,
+// literal flag, range of lines, and counter value) to the formatter.
+// Counter values will be accumulated where appropriate.
+func (fm *Formatter) AddUnit(file string, fname string, isfnlit bool, unit coverage.CoverableUnit, count uint32) {
+ if fm.p == nil {
+ panic("AddUnit invoked before SetPackage")
+ }
+ fkey := fnfile{file: file, fname: fname, lit: isfnlit}
+ idx, ok := fm.p.funcTable[fkey]
+ if !ok {
+ idx = uint32(len(fm.p.funcs))
+ fm.p.funcs = append(fm.p.funcs, fkey)
+ fm.p.funcTable[fkey] = idx
+ }
+ ukey := extcu{fnfid: idx, CoverableUnit: unit}
+ pcount := fm.p.unitTable[ukey]
+ var result uint32
+ if fm.cm == coverage.CtrModeSet {
+ if count != 0 || pcount != 0 {
+ result = 1
+ }
+ } else {
+ // Use saturating arithmetic.
+ result, _ = cmerge.SaturatingAdd(pcount, count)
+ }
+ fm.p.unitTable[ukey] = result
+}
+
+// sortUnits sorts a slice of extcu objects in a package according to
+// source position information (e.g. file and line). Note that we don't
+// include function name as part of the sorting criteria, the thinking
+// being that is better to provide things in the original source order.
+func (p *pstate) sortUnits(units []extcu) {
+ sort.Slice(units, func(i, j int) bool {
+ ui := units[i]
+ uj := units[j]
+ ifile := p.funcs[ui.fnfid].file
+ jfile := p.funcs[uj.fnfid].file
+ if ifile != jfile {
+ return ifile < jfile
+ }
+ // NB: not taking function literal flag into account here (no
+ // need, since other fields are guaranteed to be distinct).
+ if units[i].StLine != units[j].StLine {
+ return units[i].StLine < units[j].StLine
+ }
+ if units[i].EnLine != units[j].EnLine {
+ return units[i].EnLine < units[j].EnLine
+ }
+ if units[i].StCol != units[j].StCol {
+ return units[i].StCol < units[j].StCol
+ }
+ if units[i].EnCol != units[j].EnCol {
+ return units[i].EnCol < units[j].EnCol
+ }
+ return units[i].NxStmts < units[j].NxStmts
+ })
+}
+
+// EmitTextual writes the accumulated coverage data in the legacy
+// cmd/cover text format to the writer 'w'. We sort the data items by
+// importpath, source file, and line number before emitting (this sorting
+// is not explicitly mandated by the format, but seems like a good idea
+// for repeatable/deterministic dumps).
+func (fm *Formatter) EmitTextual(w io.Writer) error {
+ if fm.cm == coverage.CtrModeInvalid {
+ panic("internal error, counter mode unset")
+ }
+ if _, err := fmt.Fprintf(w, "mode: %s\n", fm.cm.String()); err != nil {
+ return err
+ }
+ pkgs := make([]string, 0, len(fm.pm))
+ for importpath := range fm.pm {
+ pkgs = append(pkgs, importpath)
+ }
+ sort.Strings(pkgs)
+ for _, importpath := range pkgs {
+ p := fm.pm[importpath]
+ units := make([]extcu, 0, len(p.unitTable))
+ for u := range p.unitTable {
+ units = append(units, u)
+ }
+ p.sortUnits(units)
+ for _, u := range units {
+ count := p.unitTable[u]
+ file := p.funcs[u.fnfid].file
+ if _, err := fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n",
+ file, u.StLine, u.StCol,
+ u.EnLine, u.EnCol, u.NxStmts, count); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// EmitPercent writes out a "percentage covered" string to the writer 'w'.
+func (fm *Formatter) EmitPercent(w io.Writer, covpkgs string, noteEmpty bool, aggregate bool) error {
+ pkgs := make([]string, 0, len(fm.pm))
+ for importpath := range fm.pm {
+ pkgs = append(pkgs, importpath)
+ }
+
+ rep := func(cov, tot uint64) error {
+ if tot != 0 {
+ if _, err := fmt.Fprintf(w, "coverage: %.1f%% of statements%s\n",
+ 100.0*float64(cov)/float64(tot), covpkgs); err != nil {
+ return err
+ }
+ } else if noteEmpty {
+ if _, err := fmt.Fprintf(w, "coverage: [no statements]\n"); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ sort.Strings(pkgs)
+ var totalStmts, coveredStmts uint64
+ for _, importpath := range pkgs {
+ p := fm.pm[importpath]
+ if !aggregate {
+ totalStmts, coveredStmts = 0, 0
+ }
+ for unit, count := range p.unitTable {
+ nx := uint64(unit.NxStmts)
+ totalStmts += nx
+ if count != 0 {
+ coveredStmts += nx
+ }
+ }
+ if !aggregate {
+ if _, err := fmt.Fprintf(w, "\t%s\t\t", importpath); err != nil {
+ return err
+ }
+ if err := rep(coveredStmts, totalStmts); err != nil {
+ return err
+ }
+ }
+ }
+ if aggregate {
+ if err := rep(coveredStmts, totalStmts); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// EmitFuncs writes out a function-level summary to the writer 'w'. A
+// note on handling function literals: although we collect coverage
+// data for unnamed literals, it probably does not make sense to
+// include them in the function summary since there isn't any good way
+// to name them (this is also consistent with the legacy cmd/cover
+// implementation). We do want to include their counts in the overall
+// summary however.
+func (fm *Formatter) EmitFuncs(w io.Writer) error {
+ if fm.cm == coverage.CtrModeInvalid {
+ panic("internal error, counter mode unset")
+ }
+ perc := func(covered, total uint64) float64 {
+ if total == 0 {
+ total = 1
+ }
+ return 100.0 * float64(covered) / float64(total)
+ }
+ tabber := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
+ defer tabber.Flush()
+ allStmts := uint64(0)
+ covStmts := uint64(0)
+
+ pkgs := make([]string, 0, len(fm.pm))
+ for importpath := range fm.pm {
+ pkgs = append(pkgs, importpath)
+ }
+ sort.Strings(pkgs)
+
+ // Emit functions for each package, sorted by import path.
+ for _, importpath := range pkgs {
+ p := fm.pm[importpath]
+ if len(p.unitTable) == 0 {
+ continue
+ }
+ units := make([]extcu, 0, len(p.unitTable))
+ for u := range p.unitTable {
+ units = append(units, u)
+ }
+
+ // Within a package, sort the units, then walk through the
+ // sorted array. Each time we hit a new function, emit the
+ // summary entry for the previous function, then make one last
+ // emit call at the end of the loop.
+ p.sortUnits(units)
+ fname := ""
+ ffile := ""
+ flit := false
+ var fline uint32
+ var cstmts, tstmts uint64
+ captureFuncStart := func(u extcu) {
+ fname = p.funcs[u.fnfid].fname
+ ffile = p.funcs[u.fnfid].file
+ flit = p.funcs[u.fnfid].lit
+ fline = u.StLine
+ }
+ emitFunc := func(u extcu) error {
+ // Don't emit entries for function literals (see discussion
+ // in function header comment above).
+ if !flit {
+ if _, err := fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n",
+ ffile, fline, fname, perc(cstmts, tstmts)); err != nil {
+ return err
+ }
+ }
+ captureFuncStart(u)
+ allStmts += tstmts
+ covStmts += cstmts
+ tstmts = 0
+ cstmts = 0
+ return nil
+ }
+ for k, u := range units {
+ if k == 0 {
+ captureFuncStart(u)
+ } else {
+ if fname != p.funcs[u.fnfid].fname {
+ // New function; emit entry for previous one.
+ if err := emitFunc(u); err != nil {
+ return err
+ }
+ }
+ }
+ tstmts += uint64(u.NxStmts)
+ count := p.unitTable[u]
+ if count != 0 {
+ cstmts += uint64(u.NxStmts)
+ }
+ }
+ if err := emitFunc(extcu{}); err != nil {
+ return err
+ }
+ }
+ if _, err := fmt.Fprintf(tabber, "%s\t%s\t%.1f%%\n",
+ "total", "(statements)", perc(covStmts, allStmts)); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/src/internal/coverage/cmerge/merge.go b/src/internal/coverage/cmerge/merge.go
new file mode 100644
index 0000000..1339803
--- /dev/null
+++ b/src/internal/coverage/cmerge/merge.go
@@ -0,0 +1,127 @@
+// Copyright 2022 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 cmerge
+
+// package cmerge provides a few small utility APIs for helping
+// with merging of counter data for a given function.
+
+import (
+ "fmt"
+ "internal/coverage"
+ "math"
+)
+
+type ModeMergePolicy uint8
+
+const (
+ ModeMergeStrict ModeMergePolicy = iota
+ ModeMergeRelaxed
+)
+
+// Merger provides state and methods to help manage the process of
+// merging together coverage counter data for a given function, for
+// tools that need to implicitly merge counter as they read multiple
+// coverage counter data files.
+type Merger struct {
+ cmode coverage.CounterMode
+ cgran coverage.CounterGranularity
+ policy ModeMergePolicy
+ overflow bool
+}
+
+func (cm *Merger) SetModeMergePolicy(policy ModeMergePolicy) {
+ cm.policy = policy
+}
+
+// MergeCounters takes the counter values in 'src' and merges them
+// into 'dst' according to the correct counter mode.
+func (m *Merger) MergeCounters(dst, src []uint32) (error, bool) {
+ if len(src) != len(dst) {
+ return fmt.Errorf("merging counters: len(dst)=%d len(src)=%d", len(dst), len(src)), false
+ }
+ if m.cmode == coverage.CtrModeSet {
+ for i := 0; i < len(src); i++ {
+ if src[i] != 0 {
+ dst[i] = 1
+ }
+ }
+ } else {
+ for i := 0; i < len(src); i++ {
+ dst[i] = m.SaturatingAdd(dst[i], src[i])
+ }
+ }
+ ovf := m.overflow
+ m.overflow = false
+ return nil, ovf
+}
+
+// Saturating add does a saturating addition of 'dst' and 'src',
+// returning added value or math.MaxUint32 if there is an overflow.
+// Overflows are recorded in case the client needs to track them.
+func (m *Merger) SaturatingAdd(dst, src uint32) uint32 {
+ result, overflow := SaturatingAdd(dst, src)
+ if overflow {
+ m.overflow = true
+ }
+ return result
+}
+
+// Saturating add does a saturating addition of 'dst' and 'src',
+// returning added value or math.MaxUint32 plus an overflow flag.
+func SaturatingAdd(dst, src uint32) (uint32, bool) {
+ d, s := uint64(dst), uint64(src)
+ sum := d + s
+ overflow := false
+ if uint64(uint32(sum)) != sum {
+ overflow = true
+ sum = math.MaxUint32
+ }
+ return uint32(sum), overflow
+}
+
+// SetModeAndGranularity records the counter mode and granularity for
+// the current merge. In the specific case of merging across coverage
+// data files from different binaries, where we're combining data from
+// more than one meta-data file, we need to check for and resolve
+// mode/granularity clashes.
+func (cm *Merger) SetModeAndGranularity(mdf string, cmode coverage.CounterMode, cgran coverage.CounterGranularity) error {
+ if cm.cmode == coverage.CtrModeInvalid {
+ // Set merger mode based on what we're seeing here.
+ cm.cmode = cmode
+ cm.cgran = cgran
+ } else {
+ // Granularity clashes are always errors.
+ if cm.cgran != cgran {
+ return fmt.Errorf("counter granularity clash while reading meta-data file %s: previous file had %s, new file has %s", mdf, cm.cgran.String(), cgran.String())
+ }
+ // Mode clashes are treated as errors if we're using the
+ // default strict policy.
+ if cm.cmode != cmode {
+ if cm.policy == ModeMergeStrict {
+ return fmt.Errorf("counter mode clash while reading meta-data file %s: previous file had %s, new file has %s", mdf, cm.cmode.String(), cmode.String())
+ }
+ // In the case of a relaxed mode merge policy, upgrade
+ // mode if needed.
+ if cm.cmode < cmode {
+ cm.cmode = cmode
+ }
+ }
+ }
+ return nil
+}
+
+func (cm *Merger) ResetModeAndGranularity() {
+ cm.cmode = coverage.CtrModeInvalid
+ cm.cgran = coverage.CtrGranularityInvalid
+ cm.overflow = false
+}
+
+func (cm *Merger) Mode() coverage.CounterMode {
+ return cm.cmode
+}
+
+func (cm *Merger) Granularity() coverage.CounterGranularity {
+ return cm.cgran
+}
diff --git a/src/internal/coverage/cmerge/merge_test.go b/src/internal/coverage/cmerge/merge_test.go
new file mode 100644
index 0000000..0e6112a
--- /dev/null
+++ b/src/internal/coverage/cmerge/merge_test.go
@@ -0,0 +1,118 @@
+// Copyright 2022 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 cmerge_test
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/cmerge"
+ "testing"
+)
+
+func TestClash(t *testing.T) {
+ m := &cmerge.Merger{}
+ err := m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
+ if err != nil {
+ t.Fatalf("unexpected clash: %v", err)
+ }
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
+ if err != nil {
+ t.Fatalf("unexpected clash: %v", err)
+ }
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerBlock)
+ if err == nil {
+ t.Fatalf("expected mode clash, not found")
+ }
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerFunc)
+ if err == nil {
+ t.Fatalf("expected granularity clash, not found")
+ }
+ m.SetModeMergePolicy(cmerge.ModeMergeRelaxed)
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerBlock)
+ if err != nil {
+ t.Fatalf("unexpected clash: %v", err)
+ }
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
+ if err != nil {
+ t.Fatalf("unexpected clash: %v", err)
+ }
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeAtomic, coverage.CtrGranularityPerBlock)
+ if err != nil {
+ t.Fatalf("unexpected clash: %v", err)
+ }
+ m.ResetModeAndGranularity()
+ err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerFunc)
+ if err != nil {
+ t.Fatalf("unexpected clash after reset: %v", err)
+ }
+}
+
+func TestBasic(t *testing.T) {
+ scenarios := []struct {
+ cmode coverage.CounterMode
+ cgran coverage.CounterGranularity
+ src, dst, res []uint32
+ iters int
+ merr bool
+ overflow bool
+ }{
+ {
+ cmode: coverage.CtrModeSet,
+ cgran: coverage.CtrGranularityPerBlock,
+ src: []uint32{1, 0, 1},
+ dst: []uint32{1, 1, 0},
+ res: []uint32{1, 1, 1},
+ iters: 2,
+ overflow: false,
+ },
+ {
+ cmode: coverage.CtrModeCount,
+ cgran: coverage.CtrGranularityPerBlock,
+ src: []uint32{1, 0, 3},
+ dst: []uint32{5, 7, 0},
+ res: []uint32{6, 7, 3},
+ iters: 1,
+ overflow: false,
+ },
+ {
+ cmode: coverage.CtrModeCount,
+ cgran: coverage.CtrGranularityPerBlock,
+ src: []uint32{4294967200, 0, 3},
+ dst: []uint32{4294967001, 7, 0},
+ res: []uint32{4294967295, 7, 3},
+ iters: 1,
+ overflow: true,
+ },
+ }
+
+ for k, scenario := range scenarios {
+ var err error
+ var ovf bool
+ m := &cmerge.Merger{}
+ mdf := fmt.Sprintf("file%d", k)
+ err = m.SetModeAndGranularity(mdf, scenario.cmode, scenario.cgran)
+ if err != nil {
+ t.Fatalf("case %d SetModeAndGranularity failed: %v", k, err)
+ }
+ for i := 0; i < scenario.iters; i++ {
+ err, ovf = m.MergeCounters(scenario.dst, scenario.src)
+ if ovf != scenario.overflow {
+ t.Fatalf("case %d overflow mismatch: got %v want %v", k, ovf, scenario.overflow)
+ }
+ if !scenario.merr && err != nil {
+ t.Fatalf("case %d unexpected err %v", k, err)
+ }
+ if scenario.merr && err == nil {
+ t.Fatalf("case %d expected err, not received", k)
+ }
+ for i := range scenario.dst {
+ if scenario.dst[i] != scenario.res[i] {
+ t.Fatalf("case %d: bad merge at %d got %d want %d",
+ k, i, scenario.dst[i], scenario.res[i])
+ }
+ }
+ }
+ }
+}
diff --git a/src/internal/coverage/decodecounter/decodecounterfile.go b/src/internal/coverage/decodecounter/decodecounterfile.go
new file mode 100644
index 0000000..83934fe
--- /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 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
+}
diff --git a/src/internal/coverage/decodemeta/decode.go b/src/internal/coverage/decodemeta/decode.go
new file mode 100644
index 0000000..fa047c7
--- /dev/null
+++ b/src/internal/coverage/decodemeta/decode.go
@@ -0,0 +1,136 @@
+// 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 decodemeta
+
+// This package contains APIs and helpers for decoding a single package's
+// meta data "blob" emitted by the compiler when coverage instrumentation
+// is turned on.
+
+import (
+ "encoding/binary"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/slicereader"
+ "internal/coverage/stringtab"
+ "io"
+ "os"
+)
+
+// See comments in the encodecovmeta package for details on the format.
+
+type CoverageMetaDataDecoder struct {
+ r *slicereader.Reader
+ hdr coverage.MetaSymbolHeader
+ strtab *stringtab.Reader
+ tmp []byte
+ debug bool
+}
+
+func NewCoverageMetaDataDecoder(b []byte, readonly bool) (*CoverageMetaDataDecoder, error) {
+ slr := slicereader.NewReader(b, readonly)
+ x := &CoverageMetaDataDecoder{
+ r: slr,
+ tmp: make([]byte, 0, 256),
+ }
+ if err := x.readHeader(); err != nil {
+ return nil, err
+ }
+ if err := x.readStringTable(); err != nil {
+ return nil, err
+ }
+ return x, nil
+}
+
+func (d *CoverageMetaDataDecoder) readHeader() error {
+ if err := binary.Read(d.r, binary.LittleEndian, &d.hdr); err != nil {
+ return err
+ }
+ if d.debug {
+ fmt.Fprintf(os.Stderr, "=-= after readHeader: %+v\n", d.hdr)
+ }
+ return nil
+}
+
+func (d *CoverageMetaDataDecoder) readStringTable() error {
+ // Seek to the correct location to read the string table.
+ stringTableLocation := int64(coverage.CovMetaHeaderSize + 4*d.hdr.NumFuncs)
+ if _, err := d.r.Seek(stringTableLocation, io.SeekStart); err != nil {
+ return err
+ }
+
+ // Read the table itself.
+ d.strtab = stringtab.NewReader(d.r)
+ d.strtab.Read()
+ return nil
+}
+
+func (d *CoverageMetaDataDecoder) PackagePath() string {
+ return d.strtab.Get(d.hdr.PkgPath)
+}
+
+func (d *CoverageMetaDataDecoder) PackageName() string {
+ return d.strtab.Get(d.hdr.PkgName)
+}
+
+func (d *CoverageMetaDataDecoder) ModulePath() string {
+ return d.strtab.Get(d.hdr.ModulePath)
+}
+
+func (d *CoverageMetaDataDecoder) NumFuncs() uint32 {
+ return d.hdr.NumFuncs
+}
+
+// ReadFunc reads the coverage meta-data for the function with index
+// 'findex', filling it into the FuncDesc pointed to by 'f'.
+func (d *CoverageMetaDataDecoder) ReadFunc(fidx uint32, f *coverage.FuncDesc) error {
+ if fidx >= d.hdr.NumFuncs {
+ return fmt.Errorf("illegal function index")
+ }
+
+ // Seek to the correct location to read the function offset and read it.
+ funcOffsetLocation := int64(coverage.CovMetaHeaderSize + 4*fidx)
+ if _, err := d.r.Seek(funcOffsetLocation, io.SeekStart); err != nil {
+ return err
+ }
+ foff := d.r.ReadUint32()
+
+ // Check assumptions
+ if foff < uint32(funcOffsetLocation) || foff > d.hdr.Length {
+ return fmt.Errorf("malformed func offset %d", foff)
+ }
+
+ // Seek to the correct location to read the function.
+ floc := int64(foff)
+ if _, err := d.r.Seek(floc, io.SeekStart); err != nil {
+ return err
+ }
+
+ // Preamble containing number of units, file, and function.
+ numUnits := uint32(d.r.ReadULEB128())
+ fnameidx := uint32(d.r.ReadULEB128())
+ fileidx := uint32(d.r.ReadULEB128())
+
+ f.Srcfile = d.strtab.Get(fileidx)
+ f.Funcname = d.strtab.Get(fnameidx)
+
+ // Now the units
+ f.Units = f.Units[:0]
+ if cap(f.Units) < int(numUnits) {
+ f.Units = make([]coverage.CoverableUnit, 0, numUnits)
+ }
+ for k := uint32(0); k < numUnits; k++ {
+ f.Units = append(f.Units,
+ coverage.CoverableUnit{
+ StLine: uint32(d.r.ReadULEB128()),
+ StCol: uint32(d.r.ReadULEB128()),
+ EnLine: uint32(d.r.ReadULEB128()),
+ EnCol: uint32(d.r.ReadULEB128()),
+ NxStmts: uint32(d.r.ReadULEB128()),
+ })
+ }
+ lit := d.r.ReadULEB128()
+ f.Lit = lit != 0
+ return nil
+}
diff --git a/src/internal/coverage/decodemeta/decodefile.go b/src/internal/coverage/decodemeta/decodefile.go
new file mode 100644
index 0000000..96e0765
--- /dev/null
+++ b/src/internal/coverage/decodemeta/decodefile.go
@@ -0,0 +1,223 @@
+// 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 decodemeta
+
+// This package contains APIs and helpers for reading and decoding
+// meta-data output files emitted by the runtime when a
+// coverage-instrumented binary executes. A meta-data file contains
+// top-level info (counter mode, number of packages) and then a
+// separate self-contained meta-data section for each Go package.
+
+import (
+ "bufio"
+ "crypto/md5"
+ "encoding/binary"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/slicereader"
+ "internal/coverage/stringtab"
+ "io"
+ "os"
+)
+
+// CoverageMetaFileReader provides state and methods for reading
+// a meta-data file from a code coverage run.
+type CoverageMetaFileReader struct {
+ f *os.File
+ hdr coverage.MetaFileHeader
+ tmp []byte
+ pkgOffsets []uint64
+ pkgLengths []uint64
+ strtab *stringtab.Reader
+ fileRdr *bufio.Reader
+ fileView []byte
+ debug bool
+}
+
+// NewCoverageMetaFileReader returns a new helper object for reading
+// the coverage meta-data output file 'f'. The param 'fileView' is a
+// read-only slice containing the contents of 'f' obtained by mmap'ing
+// the file read-only; 'fileView' may be nil, in which case the helper
+// will read the contents of the file using regular file Read
+// operations.
+func NewCoverageMetaFileReader(f *os.File, fileView []byte) (*CoverageMetaFileReader, error) {
+ r := &CoverageMetaFileReader{
+ f: f,
+ fileView: fileView,
+ tmp: make([]byte, 256),
+ }
+
+ if err := r.readFileHeader(); err != nil {
+ return nil, err
+ }
+ return r, nil
+}
+
+func (r *CoverageMetaFileReader) readFileHeader() error {
+ var err error
+
+ r.fileRdr = bufio.NewReader(r.f)
+
+ // Read file header.
+ if err := binary.Read(r.fileRdr, binary.LittleEndian, &r.hdr); err != nil {
+ return err
+ }
+
+ // Verify magic string
+ m := r.hdr.Magic
+ g := coverage.CovMetaMagic
+ if m[0] != g[0] || m[1] != g[1] || m[2] != g[2] || m[3] != g[3] {
+ return fmt.Errorf("invalid meta-data file magic string")
+ }
+
+ // Vet the version. If this is a meta-data file from the future,
+ // we won't be able to read it.
+ if r.hdr.Version > coverage.MetaFileVersion {
+ return fmt.Errorf("meta-data file withn unknown version %d (expected %d)", r.hdr.Version, coverage.MetaFileVersion)
+ }
+
+ // Read package offsets for good measure
+ r.pkgOffsets = make([]uint64, r.hdr.Entries)
+ for i := uint64(0); i < r.hdr.Entries; i++ {
+ if r.pkgOffsets[i], err = r.rdUint64(); err != nil {
+ return err
+ }
+ if r.pkgOffsets[i] > r.hdr.TotalLength {
+ return fmt.Errorf("insane pkg offset %d: %d > totlen %d",
+ i, r.pkgOffsets[i], r.hdr.TotalLength)
+ }
+ }
+ r.pkgLengths = make([]uint64, r.hdr.Entries)
+ for i := uint64(0); i < r.hdr.Entries; i++ {
+ if r.pkgLengths[i], err = r.rdUint64(); err != nil {
+ return err
+ }
+ if r.pkgLengths[i] > r.hdr.TotalLength {
+ return fmt.Errorf("insane pkg length %d: %d > totlen %d",
+ i, r.pkgLengths[i], r.hdr.TotalLength)
+ }
+ }
+
+ // Read string table.
+ b := make([]byte, r.hdr.StrTabLength)
+ nr, err := r.fileRdr.Read(b)
+ if err != nil {
+ return err
+ }
+ if nr != int(r.hdr.StrTabLength) {
+ return fmt.Errorf("error: short read on string table")
+ }
+ slr := slicereader.NewReader(b, false /* not readonly */)
+ r.strtab = stringtab.NewReader(slr)
+ r.strtab.Read()
+
+ if r.debug {
+ fmt.Fprintf(os.Stderr, "=-= read-in header is: %+v\n", *r)
+ }
+
+ return nil
+}
+
+func (r *CoverageMetaFileReader) rdUint64() (uint64, error) {
+ r.tmp = r.tmp[:0]
+ r.tmp = append(r.tmp, make([]byte, 8)...)
+ n, err := r.fileRdr.Read(r.tmp)
+ if err != nil {
+ return 0, err
+ }
+ if n != 8 {
+ return 0, fmt.Errorf("premature end of file on read")
+ }
+ v := binary.LittleEndian.Uint64(r.tmp)
+ return v, nil
+}
+
+// NumPackages returns the number of packages for which this file
+// contains meta-data.
+func (r *CoverageMetaFileReader) NumPackages() uint64 {
+ return r.hdr.Entries
+}
+
+// CounterMode returns the counter mode (set, count, atomic) used
+// when building for coverage for the program that produce this
+// meta-data file.
+func (r *CoverageMetaFileReader) CounterMode() coverage.CounterMode {
+ return r.hdr.CMode
+}
+
+// CounterGranularity returns the counter granularity (single counter per
+// function, or counter per block) selected when building for coverage
+// for the program that produce this meta-data file.
+func (r *CoverageMetaFileReader) CounterGranularity() coverage.CounterGranularity {
+ return r.hdr.CGranularity
+}
+
+// FileHash returns the hash computed for all of the package meta-data
+// blobs. Coverage counter data files refer to this hash, and the
+// hash will be encoded into the meta-data file name.
+func (r *CoverageMetaFileReader) FileHash() [16]byte {
+ return r.hdr.MetaFileHash
+}
+
+// GetPackageDecoder requests a decoder object for the package within
+// the meta-data file whose index is 'pkIdx'. If the
+// CoverageMetaFileReader was set up with a read-only file view, a
+// pointer into that file view will be returned, otherwise the buffer
+// 'payloadbuf' will be written to (or if it is not of sufficient
+// size, a new buffer will be allocated). Return value is the decoder,
+// a byte slice with the encoded meta-data, and an error.
+func (r *CoverageMetaFileReader) GetPackageDecoder(pkIdx uint32, payloadbuf []byte) (*CoverageMetaDataDecoder, []byte, error) {
+ pp, err := r.GetPackagePayload(pkIdx, payloadbuf)
+ if r.debug {
+ fmt.Fprintf(os.Stderr, "=-= pkidx=%d payload length is %d hash=%s\n",
+ pkIdx, len(pp), fmt.Sprintf("%x", md5.Sum(pp)))
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+ mdd, err := NewCoverageMetaDataDecoder(pp, r.fileView != nil)
+ if err != nil {
+ return nil, nil, err
+ }
+ return mdd, pp, nil
+}
+
+// GetPackagePayload returns the raw (encoded) meta-data payload for the
+// package with index 'pkIdx'. As with GetPackageDecoder, if the
+// CoverageMetaFileReader was set up with a read-only file view, a
+// pointer into that file view will be returned, otherwise the buffer
+// 'payloadbuf' will be written to (or if it is not of sufficient
+// size, a new buffer will be allocated). Return value is the decoder,
+// a byte slice with the encoded meta-data, and an error.
+func (r *CoverageMetaFileReader) GetPackagePayload(pkIdx uint32, payloadbuf []byte) ([]byte, error) {
+
+ // Determine correct offset/length.
+ if uint64(pkIdx) >= r.hdr.Entries {
+ return nil, fmt.Errorf("GetPackagePayload: illegal pkg index %d", pkIdx)
+ }
+ off := r.pkgOffsets[pkIdx]
+ len := r.pkgLengths[pkIdx]
+
+ if r.debug {
+ fmt.Fprintf(os.Stderr, "=-= for pk %d, off=%d len=%d\n", pkIdx, off, len)
+ }
+
+ if r.fileView != nil {
+ return r.fileView[off : off+len], nil
+ }
+
+ payload := payloadbuf[:0]
+ if cap(payload) < int(len) {
+ payload = make([]byte, 0, len)
+ }
+ payload = append(payload, make([]byte, len)...)
+ if _, err := r.f.Seek(int64(off), io.SeekStart); err != nil {
+ return nil, err
+ }
+ if _, err := io.ReadFull(r.f, payload); err != nil {
+ return nil, err
+ }
+ return payload, nil
+}
diff --git a/src/internal/coverage/defs.go b/src/internal/coverage/defs.go
new file mode 100644
index 0000000..340ac95
--- /dev/null
+++ b/src/internal/coverage/defs.go
@@ -0,0 +1,388 @@
+// Copyright 2022 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 coverage
+
+// Types and constants related to the output files written
+// by code coverage tooling. When a coverage-instrumented binary
+// is run, it emits two output files: a meta-data output file, and
+// a counter data output file.
+
+//.....................................................................
+//
+// Meta-data definitions:
+//
+// The meta-data file is composed of a file header, a series of
+// meta-data blobs/sections (one per instrumented package), and an offsets
+// area storing the offsets of each section. Format of the meta-data
+// file looks like:
+//
+// --header----------
+// | magic: [4]byte magic string
+// | version
+// | total length of meta-data file in bytes
+// | numPkgs: number of package entries in file
+// | hash: [16]byte hash of entire meta-data payload
+// | offset to string table section
+// | length of string table
+// | number of entries in string table
+// | counter mode
+// | counter granularity
+// --package offsets table------
+// <offset to pkg 0>
+// <offset to pkg 1>
+// ...
+// --package lengths table------
+// <length of pkg 0>
+// <length of pkg 1>
+// ...
+// --string table------
+// <uleb128 len> 8
+// <data> "somestring"
+// ...
+// --package payloads------
+// <meta-symbol for pkg 0>
+// <meta-symbol for pkg 1>
+// ...
+//
+// Each package payload is a stand-alone blob emitted by the compiler,
+// and does not depend on anything else in the meta-data file. In
+// particular, each blob has it's own string table. Note that the
+// file-level string table is expected to be very short (most strings
+// will be in the meta-data blobs themselves).
+
+// CovMetaMagic holds the magic string for a meta-data file.
+var CovMetaMagic = [4]byte{'\x00', '\x63', '\x76', '\x6d'}
+
+// MetaFilePref is a prefix used when emitting meta-data files; these
+// files are of the form "covmeta.<hash>", where hash is a hash
+// computed from the hashes of all the package meta-data symbols in
+// the program.
+const MetaFilePref = "covmeta"
+
+// MetaFileVersion contains the current (most recent) meta-data file version.
+const MetaFileVersion = 1
+
+// MetaFileHeader stores file header information for a meta-data file.
+type MetaFileHeader struct {
+ Magic [4]byte
+ Version uint32
+ TotalLength uint64
+ Entries uint64
+ MetaFileHash [16]byte
+ StrTabOffset uint32
+ StrTabLength uint32
+ CMode CounterMode
+ CGranularity CounterGranularity
+ _ [6]byte // padding
+}
+
+// MetaSymbolHeader stores header information for a single
+// meta-data blob, e.g. the coverage meta-data payload
+// computed for a given Go package.
+type MetaSymbolHeader struct {
+ Length uint32 // size of meta-symbol payload in bytes
+ PkgName uint32 // string table index
+ PkgPath uint32 // string table index
+ ModulePath uint32 // string table index
+ MetaHash [16]byte
+ _ byte // currently unused
+ _ [3]byte // padding
+ NumFiles uint32
+ NumFuncs uint32
+}
+
+const CovMetaHeaderSize = 16 + 4 + 4 + 4 + 4 + 4 + 4 + 4 // keep in sync with above
+
+// As an example, consider the following Go package:
+//
+// 01: package p
+// 02:
+// 03: var v, w, z int
+// 04:
+// 05: func small(x, y int) int {
+// 06: v++
+// 07: // comment
+// 08: if y == 0 {
+// 09: return x
+// 10: }
+// 11: return (x << 1) ^ (9 / y)
+// 12: }
+// 13:
+// 14: func Medium(q, r int) int {
+// 15: s1 := small(q, r)
+// 16: z += s1
+// 17: s2 := small(r, q)
+// 18: w -= s2
+// 19: return w + z
+// 20: }
+//
+// The meta-data blob for the single package above might look like the
+// following:
+//
+// -- MetaSymbolHeader header----------
+// | size: size of this blob in bytes
+// | packagepath: <path to p>
+// | modulepath: <modpath for p>
+// | nfiles: 1
+// | nfunctions: 2
+// --func offsets table------
+// <offset to func 0>
+// <offset to func 1>
+// --string table (contains all files and functions)------
+// | <uleb128 len> 4
+// | <data> "p.go"
+// | <uleb128 len> 5
+// | <data> "small"
+// | <uleb128 len> 6
+// | <data> "Medium"
+// --func 0------
+// | <uleb128> num units: 3
+// | <uleb128> func name: S1 (index into string table)
+// | <uleb128> file: S0 (index into string table)
+// | <unit 0>: S0 L6 L8 2
+// | <unit 1>: S0 L9 L9 1
+// | <unit 2>: S0 L11 L11 1
+// --func 1------
+// | <uleb128> num units: 1
+// | <uleb128> func name: S2 (index into string table)
+// | <uleb128> file: S0 (index into string table)
+// | <unit 0>: S0 L15 L19 5
+// ---end-----------
+
+// The following types and constants used by the meta-data encoder/decoder.
+
+// FuncDesc encapsulates the meta-data definitions for a single Go function.
+// This version assumes that we're looking at a function before inlining;
+// if we want to capture a post-inlining view of the world, the
+// representations of source positions would need to be a good deal more
+// complicated.
+type FuncDesc struct {
+ Funcname string
+ Srcfile string
+ Units []CoverableUnit
+ Lit bool // true if this is a function literal
+}
+
+// CoverableUnit describes the source characteristics of a single
+// program unit for which we want to gather coverage info. Coverable
+// units are either "simple" or "intraline"; a "simple" coverable unit
+// corresponds to a basic block (region of straight-line code with no
+// jumps or control transfers). An "intraline" unit corresponds to a
+// logical clause nested within some other simple unit. A simple unit
+// will have a zero Parent value; for an intraline unit NxStmts will
+// be zero and Parent will be set to 1 plus the index of the
+// containing simple statement. Example:
+//
+// L7: q := 1
+// L8: x := (y == 101 || launch() == false)
+// L9: r := x * 2
+//
+// For the code above we would have three simple units (one for each
+// line), then an intraline unit describing the "launch() == false"
+// clause in line 8, with Parent pointing to the index of the line 8
+// unit in the units array.
+//
+// Note: in the initial version of the coverage revamp, only simple
+// units will be in use.
+type CoverableUnit struct {
+ StLine, StCol uint32
+ EnLine, EnCol uint32
+ NxStmts uint32
+ Parent uint32
+}
+
+// CounterMode tracks the "flavor" of the coverage counters being
+// used in a given coverage-instrumented program.
+type CounterMode uint8
+
+const (
+ CtrModeInvalid CounterMode = iota
+ CtrModeSet // "set" mode
+ CtrModeCount // "count" mode
+ CtrModeAtomic // "atomic" mode
+ CtrModeRegOnly // registration-only pseudo-mode
+ CtrModeTestMain // testmain pseudo-mode
+)
+
+func (cm CounterMode) String() string {
+ switch cm {
+ case CtrModeSet:
+ return "set"
+ case CtrModeCount:
+ return "count"
+ case CtrModeAtomic:
+ return "atomic"
+ case CtrModeRegOnly:
+ return "regonly"
+ case CtrModeTestMain:
+ return "testmain"
+ }
+ return "<invalid>"
+}
+
+func ParseCounterMode(mode string) CounterMode {
+ var cm CounterMode
+ switch mode {
+ case "set":
+ cm = CtrModeSet
+ case "count":
+ cm = CtrModeCount
+ case "atomic":
+ cm = CtrModeAtomic
+ case "regonly":
+ cm = CtrModeRegOnly
+ case "testmain":
+ cm = CtrModeTestMain
+ default:
+ cm = CtrModeInvalid
+ }
+ return cm
+}
+
+// CounterGranularity tracks the granularity of the coverage counters being
+// used in a given coverage-instrumented program.
+type CounterGranularity uint8
+
+const (
+ CtrGranularityInvalid CounterGranularity = iota
+ CtrGranularityPerBlock
+ CtrGranularityPerFunc
+)
+
+func (cm CounterGranularity) String() string {
+ switch cm {
+ case CtrGranularityPerBlock:
+ return "perblock"
+ case CtrGranularityPerFunc:
+ return "perfunc"
+ }
+ return "<invalid>"
+}
+
+// Name of file within the "go test -cover" temp coverdir directory
+// containing a list of meta-data files for packages being tested
+// in a "go test -coverpkg=... ..." run. This constant is shared
+// by the Go command and by the coverage runtime.
+const MetaFilesFileName = "metafiles.txt"
+
+// MetaFilePaths contains information generated by the Go command and
+// the read in by coverage test support functions within an executing
+// "go test -cover" binary.
+type MetaFileCollection struct {
+ ImportPaths []string
+ MetaFileFragments []string
+}
+
+//.....................................................................
+//
+// Counter data definitions:
+//
+
+// A counter data file is composed of a file header followed by one or
+// more "segments" (each segment representing a given run or partial
+// run of a give binary) followed by a footer.
+
+// CovCounterMagic holds the magic string for a coverage counter-data file.
+var CovCounterMagic = [4]byte{'\x00', '\x63', '\x77', '\x6d'}
+
+// CounterFileVersion stores the most recent counter data file version.
+const CounterFileVersion = 1
+
+// CounterFileHeader stores files header information for a counter-data file.
+type CounterFileHeader struct {
+ Magic [4]byte
+ Version uint32
+ MetaHash [16]byte
+ CFlavor CounterFlavor
+ BigEndian bool
+ _ [6]byte // padding
+}
+
+// CounterSegmentHeader encapsulates information about a specific
+// segment in a counter data file, which at the moment contains
+// counters data from a single execution of a coverage-instrumented
+// program. Following the segment header will be the string table and
+// args table, and then (possibly) padding bytes to bring the byte
+// size of the preamble up to a multiple of 4. Immediately following
+// that will be the counter payloads.
+//
+// The "args" section of a segment is used to store annotations
+// describing where the counter data came from; this section is
+// basically a series of key-value pairs (can be thought of as an
+// encoded 'map[string]string'). At the moment we only write os.Args()
+// data to this section, using pairs of the form "argc=<integer>",
+// "argv0=<os.Args[0]>", "argv1=<os.Args[1]>", and so on. In the
+// future the args table may also include things like GOOS/GOARCH
+// values, and/or tags indicating which tests were run to generate the
+// counter data.
+type CounterSegmentHeader struct {
+ FcnEntries uint64
+ StrTabLen uint32
+ ArgsLen uint32
+}
+
+// CounterFileFooter appears at the tail end of a counter data file,
+// and stores the number of segments it contains.
+type CounterFileFooter struct {
+ Magic [4]byte
+ _ [4]byte // padding
+ NumSegments uint32
+ _ [4]byte // padding
+}
+
+// CounterFilePref is the file prefix used when emitting coverage data
+// output files. CounterFileTemplate describes the format of the file
+// name: prefix followed by meta-file hash followed by process ID
+// followed by emit UnixNanoTime.
+const CounterFilePref = "covcounters"
+const CounterFileTempl = "%s.%x.%d.%d"
+const CounterFileRegexp = `^%s\.(\S+)\.(\d+)\.(\d+)+$`
+
+// CounterFlavor describes how function and counters are
+// stored/represented in the counter section of the file.
+type CounterFlavor uint8
+
+const (
+ // "Raw" representation: all values (pkg ID, func ID, num counters,
+ // and counters themselves) are stored as uint32's.
+ CtrRaw CounterFlavor = iota + 1
+
+ // "ULeb" representation: all values (pkg ID, func ID, num counters,
+ // and counters themselves) are stored with ULEB128 encoding.
+ CtrULeb128
+)
+
+func Round4(x int) int {
+ return (x + 3) &^ 3
+}
+
+//.....................................................................
+//
+// Runtime counter data definitions.
+//
+
+// At runtime within a coverage-instrumented program, the "counters"
+// object we associated with instrumented function can be thought of
+// as a struct of the following form:
+//
+// struct {
+// numCtrs uint32
+// pkgid uint32
+// funcid uint32
+// counterArray [numBlocks]uint32
+// }
+//
+// where "numCtrs" is the number of blocks / coverable units within the
+// function, "pkgid" is the unique index assigned to this package by
+// the runtime, "funcid" is the index of this function within its containing
+// package, and "counterArray" stores the actual counters.
+//
+// The counter variable itself is created not as a struct but as a flat
+// array of uint32's; we then use the offsets below to index into it.
+
+const NumCtrsOffset = 0
+const PkgIdOffset = 1
+const FuncIdOffset = 2
+const FirstCtrOffset = 3
diff --git a/src/internal/coverage/encodecounter/encode.go b/src/internal/coverage/encodecounter/encode.go
new file mode 100644
index 0000000..5958673
--- /dev/null
+++ b/src/internal/coverage/encodecounter/encode.go
@@ -0,0 +1,297 @@
+// 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
+ 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 := 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
+ }
+ 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
+}
diff --git a/src/internal/coverage/encodemeta/encode.go b/src/internal/coverage/encodemeta/encode.go
new file mode 100644
index 0000000..d211c7c
--- /dev/null
+++ b/src/internal/coverage/encodemeta/encode.go
@@ -0,0 +1,215 @@
+// 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 encodemeta
+
+// This package contains APIs and helpers for encoding the meta-data
+// "blob" for a single Go package, created when coverage
+// instrumentation is turned on.
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "internal/coverage"
+ "internal/coverage/stringtab"
+ "internal/coverage/uleb128"
+ "io"
+ "os"
+)
+
+type CoverageMetaDataBuilder struct {
+ stab stringtab.Writer
+ funcs []funcDesc
+ tmp []byte // temp work slice
+ h hash.Hash
+ pkgpath uint32
+ pkgname uint32
+ modpath uint32
+ debug bool
+ werr error
+}
+
+func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
+ if pkgpath == "" {
+ return nil, fmt.Errorf("invalid empty package path")
+ }
+ x := &CoverageMetaDataBuilder{
+ tmp: make([]byte, 0, 256),
+ h: md5.New(),
+ }
+ x.stab.InitWriter()
+ x.stab.Lookup("")
+ x.pkgpath = x.stab.Lookup(pkgpath)
+ x.pkgname = x.stab.Lookup(pkgname)
+ x.modpath = x.stab.Lookup(modulepath)
+ io.WriteString(x.h, pkgpath)
+ io.WriteString(x.h, pkgname)
+ io.WriteString(x.h, modulepath)
+ return x, nil
+}
+
+func h32(x uint32, h hash.Hash, tmp []byte) {
+ tmp = tmp[:0]
+ tmp = append(tmp, []byte{0, 0, 0, 0}...)
+ binary.LittleEndian.PutUint32(tmp, x)
+ h.Write(tmp)
+}
+
+type funcDesc struct {
+ encoded []byte
+}
+
+// AddFunc registers a new function with the meta data builder.
+func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
+ hashFuncDesc(b.h, &f, b.tmp)
+ fd := funcDesc{}
+ b.tmp = b.tmp[:0]
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
+ for _, u := range f.Units {
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
+ b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
+ }
+ lit := uint(0)
+ if f.Lit {
+ lit = 1
+ }
+ b.tmp = uleb128.AppendUleb128(b.tmp, lit)
+ fd.encoded = bytes.Clone(b.tmp)
+ rv := uint(len(b.funcs))
+ b.funcs = append(b.funcs, fd)
+ return rv
+}
+
+func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
+ nFuncs := len(b.funcs)
+ var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
+ for idx := 0; idx < nFuncs; idx++ {
+ b.wrUint32(w, uint32(foff))
+ foff += int64(len(b.funcs[idx].encoded))
+ }
+ return off + (int64(len(b.funcs)) * 4)
+}
+
+func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
+ ew := len(f.encoded)
+ if nw, err := w.Write(f.encoded); err != nil {
+ return 0, err
+ } else if ew != nw {
+ return 0, fmt.Errorf("short write emitting coverage meta-data")
+ }
+ return off + int64(ew), nil
+}
+
+func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
+ if b.werr != nil {
+ b.werr = err
+ }
+}
+
+func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
+ b.tmp = b.tmp[:0]
+ b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...)
+ binary.LittleEndian.PutUint32(b.tmp, v)
+ if nw, err := w.Write(b.tmp); err != nil {
+ b.reportWriteError(err)
+ } else if nw != 4 {
+ b.reportWriteError(fmt.Errorf("short write"))
+ }
+}
+
+// Emit writes the meta-data accumulated so far in this builder to 'w'.
+// Returns a hash of the meta-data payload and an error.
+func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
+ // Emit header. Length will initially be zero, we'll
+ // back-patch it later.
+ var digest [16]byte
+ copy(digest[:], b.h.Sum(nil))
+ mh := coverage.MetaSymbolHeader{
+ // hash and length initially zero, will be back-patched
+ PkgPath: uint32(b.pkgpath),
+ PkgName: uint32(b.pkgname),
+ ModulePath: uint32(b.modpath),
+ NumFiles: uint32(b.stab.Nentries()),
+ NumFuncs: uint32(len(b.funcs)),
+ MetaHash: digest,
+ }
+ if b.debug {
+ fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
+ }
+ if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
+ return digest, fmt.Errorf("error writing meta-file header: %v", err)
+ }
+ off := int64(coverage.CovMetaHeaderSize)
+
+ // Write function offsets section
+ off = b.emitFuncOffsets(w, off)
+
+ // Check for any errors up to this point.
+ if b.werr != nil {
+ return digest, b.werr
+ }
+
+ // Write string table.
+ if err := b.stab.Write(w); err != nil {
+ return digest, err
+ }
+ off += int64(b.stab.Size())
+
+ // Write functions
+ for _, f := range b.funcs {
+ var err error
+ off, err = b.emitFunc(w, off, f)
+ if err != nil {
+ return digest, err
+ }
+ }
+
+ // Back-patch the length.
+ totalLength := uint32(off)
+ if _, err := w.Seek(0, io.SeekStart); err != nil {
+ return digest, err
+ }
+ b.wrUint32(w, totalLength)
+ if b.werr != nil {
+ return digest, b.werr
+ }
+ return digest, nil
+}
+
+// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
+// a digest for it.
+func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
+ h := md5.New()
+ tmp := make([]byte, 0, 32)
+ hashFuncDesc(h, f, tmp)
+ var r [16]byte
+ copy(r[:], h.Sum(nil))
+ return r
+}
+
+// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
+func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
+ io.WriteString(h, f.Funcname)
+ io.WriteString(h, f.Srcfile)
+ for _, u := range f.Units {
+ h32(u.StLine, h, tmp)
+ h32(u.StCol, h, tmp)
+ h32(u.EnLine, h, tmp)
+ h32(u.EnCol, h, tmp)
+ h32(u.NxStmts, h, tmp)
+ }
+ lit := uint32(0)
+ if f.Lit {
+ lit = 1
+ }
+ h32(lit, h, tmp)
+}
diff --git a/src/internal/coverage/encodemeta/encodefile.go b/src/internal/coverage/encodemeta/encodefile.go
new file mode 100644
index 0000000..38ae46e
--- /dev/null
+++ b/src/internal/coverage/encodemeta/encodefile.go
@@ -0,0 +1,132 @@
+// 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 encodemeta
+
+import (
+ "bufio"
+ "crypto/md5"
+ "encoding/binary"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/stringtab"
+ "io"
+ "os"
+ "unsafe"
+)
+
+// This package contains APIs and helpers for writing out a meta-data
+// file (composed of a file header, offsets/lengths, and then a series of
+// meta-data blobs emitted by the compiler, one per Go package).
+
+type CoverageMetaFileWriter struct {
+ stab stringtab.Writer
+ mfname string
+ w *bufio.Writer
+ tmp []byte
+ debug bool
+}
+
+func NewCoverageMetaFileWriter(mfname string, w io.Writer) *CoverageMetaFileWriter {
+ r := &CoverageMetaFileWriter{
+ mfname: mfname,
+ w: bufio.NewWriter(w),
+ tmp: make([]byte, 64),
+ }
+ r.stab.InitWriter()
+ r.stab.Lookup("")
+ return r
+}
+
+func (m *CoverageMetaFileWriter) Write(finalHash [16]byte, blobs [][]byte, mode coverage.CounterMode, granularity coverage.CounterGranularity) error {
+ mhsz := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
+ stSize := m.stab.Size()
+ stOffset := mhsz + uint64(16*len(blobs))
+ preambleLength := stOffset + uint64(stSize)
+
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= sizeof(MetaFileHeader)=%d\n", mhsz)
+ fmt.Fprintf(os.Stderr, "=+= preambleLength=%d stSize=%d\n", preambleLength, stSize)
+ }
+
+ // Compute total size
+ tlen := preambleLength
+ for i := 0; i < len(blobs); i++ {
+ tlen += uint64(len(blobs[i]))
+ }
+
+ // Emit header
+ mh := coverage.MetaFileHeader{
+ Magic: coverage.CovMetaMagic,
+ Version: coverage.MetaFileVersion,
+ TotalLength: tlen,
+ Entries: uint64(len(blobs)),
+ MetaFileHash: finalHash,
+ StrTabOffset: uint32(stOffset),
+ StrTabLength: stSize,
+ CMode: mode,
+ CGranularity: granularity,
+ }
+ var err error
+ if err = binary.Write(m.w, binary.LittleEndian, mh); err != nil {
+ return fmt.Errorf("error writing %s: %v", m.mfname, err)
+ }
+
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= len(blobs) is %d\n", mh.Entries)
+ }
+
+ // Emit package offsets section followed by package lengths section.
+ off := preambleLength
+ off2 := mhsz
+ buf := make([]byte, 8)
+ for _, blob := range blobs {
+ binary.LittleEndian.PutUint64(buf, off)
+ if _, err = m.w.Write(buf); err != nil {
+ return fmt.Errorf("error writing %s: %v", m.mfname, err)
+ }
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= pkg offset %d 0x%x\n", off, off)
+ }
+ off += uint64(len(blob))
+ off2 += 8
+ }
+ for _, blob := range blobs {
+ bl := uint64(len(blob))
+ binary.LittleEndian.PutUint64(buf, bl)
+ if _, err = m.w.Write(buf); err != nil {
+ return fmt.Errorf("error writing %s: %v", m.mfname, err)
+ }
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= pkg len %d 0x%x\n", bl, bl)
+ }
+ off2 += 8
+ }
+
+ // Emit string table
+ if err = m.stab.Write(m.w); err != nil {
+ return err
+ }
+
+ // Now emit blobs themselves.
+ for k, blob := range blobs {
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= writing blob %d len %d at off=%d hash %s\n", k, len(blob), off2, fmt.Sprintf("%x", md5.Sum(blob)))
+ }
+ if _, err = m.w.Write(blob); err != nil {
+ return fmt.Errorf("error writing %s: %v", m.mfname, err)
+ }
+ if m.debug {
+ fmt.Fprintf(os.Stderr, "=+= wrote package payload of %d bytes\n",
+ len(blob))
+ }
+ off2 += uint64(len(blob))
+ }
+
+ // Flush writer, and we're done.
+ if err = m.w.Flush(); err != nil {
+ return fmt.Errorf("error writing %s: %v", m.mfname, err)
+ }
+ return nil
+}
diff --git a/src/internal/coverage/pkid.go b/src/internal/coverage/pkid.go
new file mode 100644
index 0000000..372a9cb
--- /dev/null
+++ b/src/internal/coverage/pkid.go
@@ -0,0 +1,81 @@
+// Copyright 2022 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 coverage
+
+// Building the runtime package with coverage instrumentation enabled
+// is tricky. For all other packages, you can be guaranteed that
+// the package init function is run before any functions are executed,
+// but this invariant is not maintained for packages such as "runtime",
+// "internal/cpu", etc. To handle this, hard-code the package ID for
+// the set of packages whose functions may be running before the
+// init function of the package is complete.
+//
+// Hardcoding is unfortunate because it means that the tool that does
+// coverage instrumentation has to keep a list of runtime packages,
+// meaning that if someone makes changes to the pkg "runtime"
+// dependencies, unexpected behavior will result for coverage builds.
+// The coverage runtime will detect and report the unexpected
+// behavior; look for an error of this form:
+//
+// internal error in coverage meta-data tracking:
+// list of hard-coded runtime package IDs needs revising.
+// registered list:
+// slot: 0 path='internal/cpu' hard-coded id: 1
+// slot: 1 path='internal/goarch' hard-coded id: 2
+// slot: 2 path='runtime/internal/atomic' hard-coded id: 3
+// slot: 3 path='internal/goos'
+// slot: 4 path='runtime/internal/sys' hard-coded id: 5
+// slot: 5 path='internal/abi' hard-coded id: 4
+// slot: 6 path='runtime/internal/math' hard-coded id: 6
+// slot: 7 path='internal/bytealg' hard-coded id: 7
+// slot: 8 path='internal/goexperiment'
+// slot: 9 path='runtime/internal/syscall' hard-coded id: 8
+// slot: 10 path='runtime' hard-coded id: 9
+// fatal error: runtime.addCovMeta
+//
+// For the error above, the hard-coded list is missing "internal/goos"
+// and "internal/goexperiment" ; the developer in question will need
+// to copy the list above into "rtPkgs" below.
+//
+// Note: this strategy assumes that the list of dependencies of
+// package runtime is fixed, and doesn't vary depending on OS/arch. If
+// this were to be the case, we would need a table of some sort below
+// as opposed to a fixed list.
+
+var rtPkgs = [...]string{
+ "internal/cpu",
+ "internal/goarch",
+ "runtime/internal/atomic",
+ "internal/goos",
+ "internal/chacha8rand",
+ "runtime/internal/sys",
+ "internal/abi",
+ "runtime/internal/math",
+ "internal/bytealg",
+ "internal/goexperiment",
+ "runtime/internal/syscall",
+ "runtime",
+}
+
+// Scoping note: the constants and apis in this file are internal
+// only, not expected to ever be exposed outside of the runtime (unlike
+// other coverage file formats and APIs, which will likely be shared
+// at some point).
+
+// NotHardCoded is a package pseudo-ID indicating that a given package
+// is not part of the runtime and doesn't require a hard-coded ID.
+const NotHardCoded = -1
+
+// HardCodedPkgID returns the hard-coded ID for the specified package
+// path, or -1 if we don't use a hard-coded ID. Hard-coded IDs start
+// at -2 and decrease as we go down the list.
+func HardCodedPkgID(pkgpath string) int {
+ for k, p := range rtPkgs {
+ if p == pkgpath {
+ return (0 - k) - 2
+ }
+ }
+ return NotHardCoded
+}
diff --git a/src/internal/coverage/pods/pods.go b/src/internal/coverage/pods/pods.go
new file mode 100644
index 0000000..e08f82e
--- /dev/null
+++ b/src/internal/coverage/pods/pods.go
@@ -0,0 +1,197 @@
+// Copyright 2022 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 pods
+
+import (
+ "fmt"
+ "internal/coverage"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strconv"
+)
+
+// Pod encapsulates a set of files emitted during the executions of a
+// coverage-instrumented binary. Each pod contains a single meta-data
+// file, and then 0 or more counter data files that refer to that
+// meta-data file. Pods are intended to simplify processing of
+// coverage output files in the case where we have several coverage
+// output directories containing output files derived from more
+// than one instrumented executable. In the case where the files that
+// make up a pod are spread out across multiple directories, each
+// element of the "Origins" field below will be populated with the
+// index of the originating directory for the corresponding counter
+// data file (within the slice of input dirs handed to CollectPods).
+// The ProcessIDs field will be populated with the process ID of each
+// data file in the CounterDataFiles slice.
+type Pod struct {
+ MetaFile string
+ CounterDataFiles []string
+ Origins []int
+ ProcessIDs []int
+}
+
+// CollectPods visits the files contained within the directories in
+// the list 'dirs', collects any coverage-related files, partitions
+// them into pods, and returns a list of the pods to the caller, along
+// with an error if something went wrong during directory/file
+// reading.
+//
+// CollectPods skips over any file that is not related to coverage
+// (e.g. avoids looking at things that are not meta-data files or
+// counter-data files). CollectPods also skips over 'orphaned' counter
+// data files (e.g. counter data files for which we can't find the
+// corresponding meta-data file). If "warn" is true, CollectPods will
+// issue warnings to stderr when it encounters non-fatal problems (for
+// orphans or a directory with no meta-data files).
+func CollectPods(dirs []string, warn bool) ([]Pod, error) {
+ files := []string{}
+ dirIndices := []int{}
+ for k, dir := range dirs {
+ dents, err := os.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+ for _, e := range dents {
+ if e.IsDir() {
+ continue
+ }
+ files = append(files, filepath.Join(dir, e.Name()))
+ dirIndices = append(dirIndices, k)
+ }
+ }
+ return collectPodsImpl(files, dirIndices, warn), nil
+}
+
+// CollectPodsFromFiles functions the same as "CollectPods" but
+// operates on an explicit list of files instead of a directory.
+func CollectPodsFromFiles(files []string, warn bool) []Pod {
+ return collectPodsImpl(files, nil, warn)
+}
+
+type fileWithAnnotations struct {
+ file string
+ origin int
+ pid int
+}
+
+type protoPod struct {
+ mf string
+ elements []fileWithAnnotations
+}
+
+// collectPodsImpl examines the specified list of files and picks out
+// subsets that correspond to coverage pods. The first stage in this
+// process is collecting a set { M1, M2, ... MN } where each M_k is a
+// distinct coverage meta-data file. We then create a single pod for
+// each meta-data file M_k, then find all of the counter data files
+// that refer to that meta-data file (recall that the counter data
+// file name incorporates the meta-data hash), and add the counter
+// data file to the appropriate pod.
+//
+// This process is complicated by the fact that we need to keep track
+// of directory indices for counter data files. Here is an example to
+// motivate:
+//
+// directory 1:
+//
+// M1 covmeta.9bbf1777f47b3fcacb05c38b035512d6
+// C1 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677673.1662138360208416486
+// C2 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677637.1662138359974441782
+//
+// directory 2:
+//
+// M2 covmeta.9bbf1777f47b3fcacb05c38b035512d6
+// C3 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677445.1662138360208416480
+// C4 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677677.1662138359974441781
+// M3 covmeta.a723844208cea2ae80c63482c78b2245
+// C5 covcounters.a723844208cea2ae80c63482c78b2245.3677445.1662138360208416480
+// C6 covcounters.a723844208cea2ae80c63482c78b2245.1877677.1662138359974441781
+//
+// In these two directories we have three meta-data files, but only
+// two are distinct, meaning that we'll wind up with two pods. The
+// first pod (with meta-file M1) will have four counter data files
+// (C1, C2, C3, C4) and the second pod will have two counter data files
+// (C5, C6).
+func collectPodsImpl(files []string, dirIndices []int, warn bool) []Pod {
+ metaRE := regexp.MustCompile(fmt.Sprintf(`^%s\.(\S+)$`, coverage.MetaFilePref))
+ mm := make(map[string]protoPod)
+ for _, f := range files {
+ base := filepath.Base(f)
+ if m := metaRE.FindStringSubmatch(base); m != nil {
+ tag := m[1]
+ // We need to allow for the possibility of duplicate
+ // meta-data files. If we hit this case, use the
+ // first encountered as the canonical version.
+ if _, ok := mm[tag]; !ok {
+ mm[tag] = protoPod{mf: f}
+ }
+ // FIXME: should probably check file length and hash here for
+ // the duplicate.
+ }
+ }
+ counterRE := regexp.MustCompile(fmt.Sprintf(coverage.CounterFileRegexp, coverage.CounterFilePref))
+ for k, f := range files {
+ base := filepath.Base(f)
+ if m := counterRE.FindStringSubmatch(base); m != nil {
+ tag := m[1] // meta hash
+ pid, err := strconv.Atoi(m[2])
+ if err != nil {
+ continue
+ }
+ if v, ok := mm[tag]; ok {
+ idx := -1
+ if dirIndices != nil {
+ idx = dirIndices[k]
+ }
+ fo := fileWithAnnotations{file: f, origin: idx, pid: pid}
+ v.elements = append(v.elements, fo)
+ mm[tag] = v
+ } else {
+ if warn {
+ warning("skipping orphaned counter file: %s", f)
+ }
+ }
+ }
+ }
+ if len(mm) == 0 {
+ if warn {
+ warning("no coverage data files found")
+ }
+ return nil
+ }
+ pods := make([]Pod, 0, len(mm))
+ for _, p := range mm {
+ sort.Slice(p.elements, func(i, j int) bool {
+ if p.elements[i].origin != p.elements[j].origin {
+ return p.elements[i].origin < p.elements[j].origin
+ }
+ return p.elements[i].file < p.elements[j].file
+ })
+ pod := Pod{
+ MetaFile: p.mf,
+ CounterDataFiles: make([]string, 0, len(p.elements)),
+ Origins: make([]int, 0, len(p.elements)),
+ ProcessIDs: make([]int, 0, len(p.elements)),
+ }
+ for _, e := range p.elements {
+ pod.CounterDataFiles = append(pod.CounterDataFiles, e.file)
+ pod.Origins = append(pod.Origins, e.origin)
+ pod.ProcessIDs = append(pod.ProcessIDs, e.pid)
+ }
+ pods = append(pods, pod)
+ }
+ sort.Slice(pods, func(i, j int) bool {
+ return pods[i].MetaFile < pods[j].MetaFile
+ })
+ return pods
+}
+
+func warning(s string, a ...interface{}) {
+ fmt.Fprintf(os.Stderr, "warning: ")
+ fmt.Fprintf(os.Stderr, s, a...)
+ fmt.Fprintf(os.Stderr, "\n")
+}
diff --git a/src/internal/coverage/pods/pods_test.go b/src/internal/coverage/pods/pods_test.go
new file mode 100644
index 0000000..69c16e0
--- /dev/null
+++ b/src/internal/coverage/pods/pods_test.go
@@ -0,0 +1,142 @@
+// Copyright 2022 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 pods_test
+
+import (
+ "crypto/md5"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/pods"
+ "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func TestPodCollection(t *testing.T) {
+ //testenv.MustHaveGoBuild(t)
+
+ mkdir := func(d string, perm os.FileMode) string {
+ dp := filepath.Join(t.TempDir(), d)
+ if err := os.Mkdir(dp, perm); err != nil {
+ t.Fatal(err)
+ }
+ return dp
+ }
+
+ mkfile := func(d string, fn string) string {
+ fp := filepath.Join(d, fn)
+ if err := os.WriteFile(fp, []byte("foo"), 0666); err != nil {
+ t.Fatal(err)
+ }
+ return fp
+ }
+
+ mkmeta := func(dir string, tag string) string {
+ hash := md5.Sum([]byte(tag))
+ fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, hash)
+ return mkfile(dir, fn)
+ }
+
+ mkcounter := func(dir string, tag string, nt int, pid int) string {
+ hash := md5.Sum([]byte(tag))
+ fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, hash, pid, nt)
+ return mkfile(dir, fn)
+ }
+
+ trim := func(path string) string {
+ b := filepath.Base(path)
+ d := filepath.Dir(path)
+ db := filepath.Base(d)
+ return db + "/" + b
+ }
+
+ podToString := func(p pods.Pod) string {
+ rv := trim(p.MetaFile) + " [\n"
+ for k, df := range p.CounterDataFiles {
+ rv += trim(df)
+ if p.Origins != nil {
+ rv += fmt.Sprintf(" o:%d", p.Origins[k])
+ }
+ rv += "\n"
+ }
+ return rv + "]"
+ }
+
+ // Create a couple of directories.
+ o1 := mkdir("o1", 0777)
+ o2 := mkdir("o2", 0777)
+
+ // Add some random files (not coverage related)
+ mkfile(o1, "blah.txt")
+ mkfile(o1, "something.exe")
+
+ // Add a meta-data file with two counter files to first dir.
+ mkmeta(o1, "m1")
+ mkcounter(o1, "m1", 1, 42)
+ mkcounter(o1, "m1", 2, 41)
+ mkcounter(o1, "m1", 2, 40)
+
+ // Add a counter file with no associated meta file.
+ mkcounter(o1, "orphan", 9, 39)
+
+ // Add a meta-data file with three counter files to second dir.
+ mkmeta(o2, "m2")
+ mkcounter(o2, "m2", 1, 38)
+ mkcounter(o2, "m2", 2, 37)
+ mkcounter(o2, "m2", 3, 36)
+
+ // Add a duplicate of the first meta-file and a corresponding
+ // counter file to the second dir. This is intended to capture
+ // the scenario where we have two different runs of the same
+ // coverage-instrumented binary, but with the output files
+ // sent to separate directories.
+ mkmeta(o2, "m1")
+ mkcounter(o2, "m1", 11, 35)
+
+ // Collect pods.
+ podlist, err := pods.CollectPods([]string{o1, o2}, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify pods
+ if len(podlist) != 2 {
+ t.Fatalf("expected 2 pods got %d pods", len(podlist))
+ }
+
+ for k, p := range podlist {
+ t.Logf("%d: mf=%s\n", k, p.MetaFile)
+ }
+
+ expected := []string{
+ `o1/covmeta.ae7be26cdaa742ca148068d5ac90eaca [
+o1/covcounters.ae7be26cdaa742ca148068d5ac90eaca.40.2 o:0
+o1/covcounters.ae7be26cdaa742ca148068d5ac90eaca.41.2 o:0
+o1/covcounters.ae7be26cdaa742ca148068d5ac90eaca.42.1 o:0
+o2/covcounters.ae7be26cdaa742ca148068d5ac90eaca.35.11 o:1
+]`,
+ `o2/covmeta.aaf2f89992379705dac844c0a2a1d45f [
+o2/covcounters.aaf2f89992379705dac844c0a2a1d45f.36.3 o:1
+o2/covcounters.aaf2f89992379705dac844c0a2a1d45f.37.2 o:1
+o2/covcounters.aaf2f89992379705dac844c0a2a1d45f.38.1 o:1
+]`,
+ }
+ for k, exp := range expected {
+ got := podToString(podlist[k])
+ if exp != got {
+ t.Errorf("pod %d: expected:\n%s\ngot:\n%s", k, exp, got)
+ }
+ }
+
+ // Check handling of bad/unreadable dir.
+ if runtime.GOOS == "linux" {
+ dbad := "/dev/null"
+ _, err = pods.CollectPods([]string{dbad}, true)
+ if err == nil {
+ t.Errorf("executed error due to unreadable dir")
+ }
+ }
+}
diff --git a/src/internal/coverage/rtcov/rtcov.go b/src/internal/coverage/rtcov/rtcov.go
new file mode 100644
index 0000000..bbb93ac
--- /dev/null
+++ b/src/internal/coverage/rtcov/rtcov.go
@@ -0,0 +1,34 @@
+// Copyright 2022 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 rtcov
+
+// This package contains types whose structure is shared between
+// the runtime package and the "runtime/coverage" package.
+
+// CovMetaBlob is a container for holding the meta-data symbol (an
+// RODATA variable) for an instrumented Go package. Here "p" points to
+// the symbol itself, "len" is the length of the sym in bytes, and
+// "hash" is an md5sum for the sym computed by the compiler. When
+// the init function for a coverage-instrumented package executes, it
+// will make a call into the runtime which will create a covMetaBlob
+// object for the package and chain it onto a global list.
+type CovMetaBlob struct {
+ P *byte
+ Len uint32
+ Hash [16]byte
+ PkgPath string
+ PkgID int
+ CounterMode uint8 // coverage.CounterMode
+ CounterGranularity uint8 // coverage.CounterGranularity
+}
+
+// CovCounterBlob is a container for encapsulating a counter section
+// (BSS variable) for an instrumented Go module. Here "counters"
+// points to the counter payload and "len" is the number of uint32
+// entries in the section.
+type CovCounterBlob struct {
+ Counters *uint32
+ Len uint64
+}
diff --git a/src/internal/coverage/slicereader/slicereader.go b/src/internal/coverage/slicereader/slicereader.go
new file mode 100644
index 0000000..d9f2a7e
--- /dev/null
+++ b/src/internal/coverage/slicereader/slicereader.go
@@ -0,0 +1,123 @@
+// 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 slicereader
+
+import (
+ "encoding/binary"
+ "fmt"
+ "io"
+ "unsafe"
+)
+
+// This file contains the helper "SliceReader", a utility for
+// reading values from a byte slice that may or may not be backed
+// by a read-only mmap'd region.
+
+type Reader struct {
+ b []byte
+ readonly bool
+ off int64
+}
+
+func NewReader(b []byte, readonly bool) *Reader {
+ r := Reader{
+ b: b,
+ readonly: readonly,
+ }
+ return &r
+}
+
+func (r *Reader) Read(b []byte) (int, error) {
+ amt := len(b)
+ toread := r.b[r.off:]
+ if len(toread) < amt {
+ amt = len(toread)
+ }
+ copy(b, toread)
+ r.off += int64(amt)
+ return amt, nil
+}
+
+func (r *Reader) Seek(offset int64, whence int) (ret int64, err error) {
+ switch whence {
+ case io.SeekStart:
+ if offset < 0 || offset > int64(len(r.b)) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", offset, len(r.b))
+ }
+ r.off = offset
+ return offset, nil
+ case io.SeekCurrent:
+ newoff := r.off + offset
+ if newoff < 0 || newoff > int64(len(r.b)) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(r.b))
+ }
+ r.off = newoff
+ return r.off, nil
+ case io.SeekEnd:
+ newoff := int64(len(r.b)) + offset
+ if newoff < 0 || newoff > int64(len(r.b)) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(r.b))
+ }
+ r.off = newoff
+ return r.off, nil
+ }
+ // other modes are not supported
+ return 0, fmt.Errorf("unsupported seek mode %d", whence)
+}
+
+func (r *Reader) Offset() int64 {
+ return r.off
+}
+
+func (r *Reader) ReadUint8() uint8 {
+ rv := uint8(r.b[int(r.off)])
+ r.off += 1
+ return rv
+}
+
+func (r *Reader) ReadUint32() uint32 {
+ end := int(r.off) + 4
+ rv := binary.LittleEndian.Uint32(r.b[int(r.off):end:end])
+ r.off += 4
+ return rv
+}
+
+func (r *Reader) ReadUint64() uint64 {
+ end := int(r.off) + 8
+ rv := binary.LittleEndian.Uint64(r.b[int(r.off):end:end])
+ r.off += 8
+ return rv
+}
+
+func (r *Reader) ReadULEB128() (value uint64) {
+ var shift uint
+
+ for {
+ b := r.b[r.off]
+ r.off++
+ value |= (uint64(b&0x7F) << shift)
+ if b&0x80 == 0 {
+ break
+ }
+ shift += 7
+ }
+ return
+}
+
+func (r *Reader) ReadString(len int64) string {
+ b := r.b[r.off : r.off+len]
+ r.off += len
+ if r.readonly {
+ return toString(b) // backed by RO memory, ok to make unsafe string
+ }
+ return string(b)
+}
+
+func toString(b []byte) string {
+ if len(b) == 0 {
+ return ""
+ }
+ return unsafe.String(&b[0], len(b))
+}
diff --git a/src/internal/coverage/slicereader/slr_test.go b/src/internal/coverage/slicereader/slr_test.go
new file mode 100644
index 0000000..461436d
--- /dev/null
+++ b/src/internal/coverage/slicereader/slr_test.go
@@ -0,0 +1,95 @@
+// 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 slicereader
+
+import (
+ "encoding/binary"
+ "io"
+ "testing"
+)
+
+func TestSliceReader(t *testing.T) {
+ b := []byte{}
+
+ bt := make([]byte, 4)
+ e32 := uint32(1030507)
+ binary.LittleEndian.PutUint32(bt, e32)
+ b = append(b, bt...)
+
+ bt = make([]byte, 8)
+ e64 := uint64(907050301)
+ binary.LittleEndian.PutUint64(bt, e64)
+ b = append(b, bt...)
+
+ b = appendUleb128(b, uint(e32))
+ b = appendUleb128(b, uint(e64))
+ b = appendUleb128(b, 6)
+ s1 := "foobar"
+ s1b := []byte(s1)
+ b = append(b, s1b...)
+ b = appendUleb128(b, 9)
+ s2 := "bazbasher"
+ s2b := []byte(s2)
+ b = append(b, s2b...)
+
+ readStr := func(slr *Reader) string {
+ len := slr.ReadULEB128()
+ return slr.ReadString(int64(len))
+ }
+
+ for i := 0; i < 2; i++ {
+ slr := NewReader(b, i == 0)
+ g32 := slr.ReadUint32()
+ if g32 != e32 {
+ t.Fatalf("slr.ReadUint32() got %d want %d", g32, e32)
+ }
+ g64 := slr.ReadUint64()
+ if g64 != e64 {
+ t.Fatalf("slr.ReadUint64() got %d want %d", g64, e64)
+ }
+ g32 = uint32(slr.ReadULEB128())
+ if g32 != e32 {
+ t.Fatalf("slr.ReadULEB128() got %d want %d", g32, e32)
+ }
+ g64 = slr.ReadULEB128()
+ if g64 != e64 {
+ t.Fatalf("slr.ReadULEB128() got %d want %d", g64, e64)
+ }
+ gs1 := readStr(slr)
+ if gs1 != s1 {
+ t.Fatalf("readStr got %s want %s", gs1, s1)
+ }
+ gs2 := readStr(slr)
+ if gs2 != s2 {
+ t.Fatalf("readStr got %s want %s", gs2, s2)
+ }
+ if _, err := slr.Seek(4, io.SeekStart); err != nil {
+ t.Fatal(err)
+ }
+ off := slr.Offset()
+ if off != 4 {
+ t.Fatalf("Offset() returned %d wanted 4", off)
+ }
+ g64 = slr.ReadUint64()
+ if g64 != e64 {
+ t.Fatalf("post-seek slr.ReadUint64() got %d want %d", g64, e64)
+ }
+ }
+}
+
+func appendUleb128(b []byte, v uint) []byte {
+ for {
+ c := uint8(v & 0x7f)
+ v >>= 7
+ if v != 0 {
+ c |= 0x80
+ }
+ b = append(b, c)
+ if c&0x80 == 0 {
+ break
+ }
+ }
+ return b
+}
diff --git a/src/internal/coverage/slicewriter/slicewriter.go b/src/internal/coverage/slicewriter/slicewriter.go
new file mode 100644
index 0000000..460e9dc
--- /dev/null
+++ b/src/internal/coverage/slicewriter/slicewriter.go
@@ -0,0 +1,80 @@
+// Copyright 2022 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 slicewriter
+
+import (
+ "fmt"
+ "io"
+)
+
+// WriteSeeker is a helper object that implements the io.WriteSeeker
+// interface. Clients can create a WriteSeeker, make a series of Write
+// calls to add data to it (and possibly Seek calls to update
+// previously written portions), then finally invoke BytesWritten() to
+// get a pointer to the constructed byte slice.
+type WriteSeeker struct {
+ payload []byte
+ off int64
+}
+
+func (sws *WriteSeeker) Write(p []byte) (n int, err error) {
+ amt := len(p)
+ towrite := sws.payload[sws.off:]
+ if len(towrite) < amt {
+ sws.payload = append(sws.payload, make([]byte, amt-len(towrite))...)
+ towrite = sws.payload[sws.off:]
+ }
+ copy(towrite, p)
+ sws.off += int64(amt)
+ return amt, nil
+}
+
+// Seek repositions the read/write position of the WriteSeeker within
+// its internally maintained slice. Note that it is not possible to
+// expand the size of the slice using SEEK_SET; trying to seek outside
+// the slice will result in an error.
+func (sws *WriteSeeker) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ case io.SeekStart:
+ if sws.off != offset && (offset < 0 || offset > int64(len(sws.payload))) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", offset, len(sws.payload))
+ }
+ sws.off = offset
+ return offset, nil
+ case io.SeekCurrent:
+ newoff := sws.off + offset
+ if newoff != sws.off && (newoff < 0 || newoff > int64(len(sws.payload))) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(sws.payload))
+ }
+ sws.off += offset
+ return sws.off, nil
+ case io.SeekEnd:
+ newoff := int64(len(sws.payload)) + offset
+ if newoff != sws.off && (newoff < 0 || newoff > int64(len(sws.payload))) {
+ return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(sws.payload))
+ }
+ sws.off = newoff
+ return sws.off, nil
+ }
+ // other modes not supported
+ return 0, fmt.Errorf("unsupported seek mode %d", whence)
+}
+
+// BytesWritten returns the underlying byte slice for the WriteSeeker,
+// containing the data written to it via Write/Seek calls.
+func (sws *WriteSeeker) BytesWritten() []byte {
+ return sws.payload
+}
+
+func (sws *WriteSeeker) Read(p []byte) (n int, err error) {
+ amt := len(p)
+ toread := sws.payload[sws.off:]
+ if len(toread) < amt {
+ amt = len(toread)
+ }
+ copy(p, toread)
+ sws.off += int64(amt)
+ return amt, nil
+}
diff --git a/src/internal/coverage/slicewriter/slw_test.go b/src/internal/coverage/slicewriter/slw_test.go
new file mode 100644
index 0000000..9e26767
--- /dev/null
+++ b/src/internal/coverage/slicewriter/slw_test.go
@@ -0,0 +1,134 @@
+// Copyright 2022 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 slicewriter
+
+import (
+ "io"
+ "testing"
+)
+
+func TestSliceWriter(t *testing.T) {
+
+ sleq := func(t *testing.T, got []byte, want []byte) {
+ t.Helper()
+ if len(got) != len(want) {
+ t.Fatalf("bad length got %d want %d", len(got), len(want))
+ }
+ for i := range got {
+ if got[i] != want[i] {
+ t.Fatalf("bad read at %d got %d want %d", i, got[i], want[i])
+ }
+ }
+ }
+
+ wf := func(t *testing.T, ws *WriteSeeker, p []byte) {
+ t.Helper()
+ nw, werr := ws.Write(p)
+ if werr != nil {
+ t.Fatalf("unexpected write error: %v", werr)
+ }
+ if nw != len(p) {
+ t.Fatalf("wrong amount written want %d got %d", len(p), nw)
+ }
+ }
+
+ rf := func(t *testing.T, ws *WriteSeeker, p []byte) {
+ t.Helper()
+ b := make([]byte, len(p))
+ nr, rerr := ws.Read(b)
+ if rerr != nil {
+ t.Fatalf("unexpected read error: %v", rerr)
+ }
+ if nr != len(p) {
+ t.Fatalf("wrong amount read want %d got %d", len(p), nr)
+ }
+ sleq(t, b, p)
+ }
+
+ sk := func(t *testing.T, ws *WriteSeeker, offset int64, whence int) int64 {
+ t.Helper()
+ off, err := ws.Seek(offset, whence)
+ if err != nil {
+ t.Fatalf("unexpected seek error: %v", err)
+ }
+ return off
+ }
+
+ wp1 := []byte{1, 2}
+ ws := &WriteSeeker{}
+
+ // write some stuff
+ wf(t, ws, wp1)
+ // check that BytesWritten returns what we wrote.
+ sleq(t, ws.BytesWritten(), wp1)
+ // offset is at end of slice, so reading should return zero bytes.
+ rf(t, ws, []byte{})
+
+ // write some more stuff
+ wp2 := []byte{7, 8, 9}
+ wf(t, ws, wp2)
+ // check that BytesWritten returns what we expect.
+ wpex := []byte{1, 2, 7, 8, 9}
+ sleq(t, ws.BytesWritten(), wpex)
+ rf(t, ws, []byte{})
+
+ // seeks and reads.
+ sk(t, ws, 1, io.SeekStart)
+ rf(t, ws, []byte{2, 7})
+ sk(t, ws, -2, io.SeekCurrent)
+ rf(t, ws, []byte{2, 7})
+ sk(t, ws, -4, io.SeekEnd)
+ rf(t, ws, []byte{2, 7})
+ off := sk(t, ws, 0, io.SeekEnd)
+ sk(t, ws, off, io.SeekStart)
+
+ // seek back and overwrite
+ sk(t, ws, 1, io.SeekStart)
+ wf(t, ws, []byte{9, 11})
+ wpex = []byte{1, 9, 11, 8, 9}
+ sleq(t, ws.BytesWritten(), wpex)
+
+ // seeks on empty writer.
+ ws2 := &WriteSeeker{}
+ sk(t, ws2, 0, io.SeekStart)
+ sk(t, ws2, 0, io.SeekCurrent)
+ sk(t, ws2, 0, io.SeekEnd)
+
+ // check for seek errors.
+ _, err := ws.Seek(-1, io.SeekStart)
+ if err == nil {
+ t.Fatalf("expected error on invalid -1 seek")
+ }
+ _, err = ws.Seek(int64(len(ws.BytesWritten())+1), io.SeekStart)
+ if err == nil {
+ t.Fatalf("expected error on invalid %d seek", len(ws.BytesWritten()))
+ }
+
+ ws.Seek(0, io.SeekStart)
+ _, err = ws.Seek(-1, io.SeekCurrent)
+ if err == nil {
+ t.Fatalf("expected error on invalid -1 seek")
+ }
+ _, err = ws.Seek(int64(len(ws.BytesWritten())+1), io.SeekCurrent)
+ if err == nil {
+ t.Fatalf("expected error on invalid %d seek", len(ws.BytesWritten()))
+ }
+
+ _, err = ws.Seek(1, io.SeekEnd)
+ if err == nil {
+ t.Fatalf("expected error on invalid 1 seek")
+ }
+ bsamt := int64(-1*len(ws.BytesWritten()) - 1)
+ _, err = ws.Seek(bsamt, io.SeekEnd)
+ if err == nil {
+ t.Fatalf("expected error on invalid %d seek", bsamt)
+ }
+
+ // bad seek mode
+ _, err = ws.Seek(-1, io.SeekStart+9)
+ if err == nil {
+ t.Fatalf("expected error on invalid seek mode")
+ }
+}
diff --git a/src/internal/coverage/stringtab/stringtab.go b/src/internal/coverage/stringtab/stringtab.go
new file mode 100644
index 0000000..156c8ad
--- /dev/null
+++ b/src/internal/coverage/stringtab/stringtab.go
@@ -0,0 +1,139 @@
+// Copyright 2022 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 stringtab
+
+import (
+ "fmt"
+ "internal/coverage/slicereader"
+ "internal/coverage/uleb128"
+ "io"
+)
+
+// This package implements string table writer and reader utilities,
+// for use in emitting and reading/decoding coverage meta-data and
+// counter-data files.
+
+// Writer implements a string table writing utility.
+type Writer struct {
+ stab map[string]uint32
+ strs []string
+ tmp []byte
+ frozen bool
+}
+
+// InitWriter initializes a stringtab.Writer.
+func (stw *Writer) InitWriter() {
+ stw.stab = make(map[string]uint32)
+ stw.tmp = make([]byte, 64)
+}
+
+// Nentries returns the number of strings interned so far.
+func (stw *Writer) Nentries() uint32 {
+ return uint32(len(stw.strs))
+}
+
+// Lookup looks up string 's' in the writer's table, adding
+// a new entry if need be, and returning an index into the table.
+func (stw *Writer) Lookup(s string) uint32 {
+ if idx, ok := stw.stab[s]; ok {
+ return idx
+ }
+ if stw.frozen {
+ panic("internal error: string table previously frozen")
+ }
+ idx := uint32(len(stw.strs))
+ stw.stab[s] = idx
+ stw.strs = append(stw.strs, s)
+ return idx
+}
+
+// Size computes the memory in bytes needed for the serialized
+// version of a stringtab.Writer.
+func (stw *Writer) Size() uint32 {
+ rval := uint32(0)
+ stw.tmp = stw.tmp[:0]
+ stw.tmp = uleb128.AppendUleb128(stw.tmp, uint(len(stw.strs)))
+ rval += uint32(len(stw.tmp))
+ for _, s := range stw.strs {
+ stw.tmp = stw.tmp[:0]
+ slen := uint(len(s))
+ stw.tmp = uleb128.AppendUleb128(stw.tmp, slen)
+ rval += uint32(len(stw.tmp)) + uint32(slen)
+ }
+ return rval
+}
+
+// Write writes the string table in serialized form to the specified
+// io.Writer.
+func (stw *Writer) Write(w io.Writer) error {
+ wr128 := func(v uint) error {
+ stw.tmp = stw.tmp[:0]
+ stw.tmp = uleb128.AppendUleb128(stw.tmp, v)
+ if nw, err := w.Write(stw.tmp); err != nil {
+ return fmt.Errorf("writing string table: %v", err)
+ } else if nw != len(stw.tmp) {
+ return fmt.Errorf("short write emitting stringtab uleb")
+ }
+ return nil
+ }
+ if err := wr128(uint(len(stw.strs))); err != nil {
+ return err
+ }
+ for _, s := range stw.strs {
+ if err := wr128(uint(len(s))); err != nil {
+ return err
+ }
+ if nw, err := w.Write([]byte(s)); err != nil {
+ return fmt.Errorf("writing string table: %v", err)
+ } else if nw != len([]byte(s)) {
+ return fmt.Errorf("short write emitting stringtab")
+ }
+ }
+ return nil
+}
+
+// Freeze sends a signal to the writer that no more additions are
+// allowed, only lookups of existing strings (if a lookup triggers
+// addition, a panic will result). Useful as a mechanism for
+// "finalizing" a string table prior to writing it out.
+func (stw *Writer) Freeze() {
+ stw.frozen = true
+}
+
+// Reader is a helper for reading a string table previously
+// serialized by a Writer.Write call.
+type Reader struct {
+ r *slicereader.Reader
+ strs []string
+}
+
+// NewReader creates a stringtab.Reader to read the contents
+// of a string table from 'r'.
+func NewReader(r *slicereader.Reader) *Reader {
+ str := &Reader{
+ r: r,
+ }
+ return str
+}
+
+// Read reads/decodes a string table using the reader provided.
+func (str *Reader) Read() {
+ numEntries := int(str.r.ReadULEB128())
+ str.strs = make([]string, 0, numEntries)
+ for idx := 0; idx < numEntries; idx++ {
+ slen := str.r.ReadULEB128()
+ str.strs = append(str.strs, str.r.ReadString(int64(slen)))
+ }
+}
+
+// Entries returns the number of decoded entries in a string table.
+func (str *Reader) Entries() int {
+ return len(str.strs)
+}
+
+// Get returns string 'idx' within the string table.
+func (str *Reader) Get(idx uint32) string {
+ return str.strs[idx]
+}
diff --git a/src/internal/coverage/test/counter_test.go b/src/internal/coverage/test/counter_test.go
new file mode 100644
index 0000000..e29baed
--- /dev/null
+++ b/src/internal/coverage/test/counter_test.go
@@ -0,0 +1,237 @@
+// 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 test
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/decodecounter"
+ "internal/coverage/encodecounter"
+ "io"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+type ctrVis struct {
+ funcs []decodecounter.FuncPayload
+}
+
+func (v *ctrVis) VisitFuncs(f encodecounter.CounterVisitorFn) error {
+ for _, fn := range v.funcs {
+ if err := f(fn.PkgIdx, fn.FuncIdx, fn.Counters); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func mkfunc(p uint32, f uint32, c []uint32) decodecounter.FuncPayload {
+ return decodecounter.FuncPayload{
+ PkgIdx: p,
+ FuncIdx: f,
+ Counters: c,
+ }
+}
+
+func TestCounterDataWriterReader(t *testing.T) {
+ flavors := []coverage.CounterFlavor{
+ coverage.CtrRaw,
+ coverage.CtrULeb128,
+ }
+
+ isDead := func(fp decodecounter.FuncPayload) bool {
+ for _, v := range fp.Counters {
+ if v != 0 {
+ return false
+ }
+ }
+ return true
+ }
+
+ funcs := []decodecounter.FuncPayload{
+ mkfunc(0, 0, []uint32{13, 14, 15}),
+ mkfunc(0, 1, []uint32{16, 17}),
+ mkfunc(1, 0, []uint32{18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 976543, 7}),
+ }
+ writeVisitor := &ctrVis{funcs: funcs}
+
+ for kf, flav := range flavors {
+
+ t.Logf("testing flavor %d\n", flav)
+
+ // Open a counter data file in preparation for emitting data.
+ d := t.TempDir()
+ cfpath := filepath.Join(d, fmt.Sprintf("covcounters.hash.0.%d", kf))
+ of, err := os.OpenFile(cfpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ t.Fatalf("opening covcounters: %v", err)
+ }
+
+ // Perform the encode and write.
+ cdfw := encodecounter.NewCoverageDataWriter(of, flav)
+ if cdfw == nil {
+ t.Fatalf("NewCoverageDataWriter failed")
+ }
+ finalHash := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}
+ args := map[string]string{"argc": "3", "argv0": "arg0", "argv1": "arg1", "argv2": "arg_________2"}
+ if err := cdfw.Write(finalHash, args, writeVisitor); err != nil {
+ t.Fatalf("counter file Write failed: %v", err)
+ }
+ if err := of.Close(); err != nil {
+ t.Fatalf("closing covcounters: %v", err)
+ }
+ cdfw = nil
+
+ // Decode the same file.
+ var cdr *decodecounter.CounterDataReader
+ inf, err := os.Open(cfpath)
+ defer func() {
+ if err := inf.Close(); err != nil {
+ t.Fatalf("close failed with: %v", err)
+ }
+ }()
+
+ if err != nil {
+ t.Fatalf("reopening covcounters file: %v", err)
+ }
+ if cdr, err = decodecounter.NewCounterDataReader(cfpath, inf); err != nil {
+ t.Fatalf("opening covcounters for read: %v", err)
+ }
+ decodedArgs := cdr.OsArgs()
+ aWant := "[arg0 arg1 arg_________2]"
+ aGot := fmt.Sprintf("%+v", decodedArgs)
+ if aWant != aGot {
+ t.Errorf("reading decoded args, got %s want %s", aGot, aWant)
+ }
+ for i := range funcs {
+ if isDead(funcs[i]) {
+ continue
+ }
+ var fp decodecounter.FuncPayload
+ if ok, err := cdr.NextFunc(&fp); err != nil {
+ t.Fatalf("reading func %d: %v", i, err)
+ } else if !ok {
+ t.Fatalf("reading func %d: bad return", i)
+ }
+ got := fmt.Sprintf("%+v", fp)
+ want := fmt.Sprintf("%+v", funcs[i])
+ if got != want {
+ t.Errorf("cdr.NextFunc iter %d\ngot %+v\nwant %+v", i, got, want)
+ }
+ }
+ var dummy decodecounter.FuncPayload
+ if ok, err := cdr.NextFunc(&dummy); err != nil {
+ t.Fatalf("reading func after loop: %v", err)
+ } else if ok {
+ t.Fatalf("reading func after loop: expected EOF")
+ }
+ }
+}
+
+func TestCounterDataAppendSegment(t *testing.T) {
+ d := t.TempDir()
+ cfpath := filepath.Join(d, "covcounters.hash2.0")
+ of, err := os.OpenFile(cfpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ t.Fatalf("opening covcounters: %v", err)
+ }
+
+ const numSegments = 2
+
+ // Write a counter with with multiple segments.
+ args := map[string]string{"argc": "1", "argv0": "prog.exe"}
+ allfuncs := [][]decodecounter.FuncPayload{}
+ ctrs := []uint32{}
+ q := uint32(0)
+ var cdfw *encodecounter.CoverageDataWriter
+ for idx := 0; idx < numSegments; idx++ {
+ args[fmt.Sprintf("seg%d", idx)] = "x"
+ q += 7
+ ctrs = append(ctrs, q)
+ funcs := []decodecounter.FuncPayload{}
+ for k := 0; k < idx+1; k++ {
+ c := make([]uint32, len(ctrs))
+ copy(c, ctrs)
+ funcs = append(funcs, mkfunc(uint32(idx), uint32(k), c))
+ }
+ allfuncs = append(allfuncs, funcs)
+
+ writeVisitor := &ctrVis{funcs: funcs}
+
+ if idx == 0 {
+ // Perform the encode and write.
+ cdfw = encodecounter.NewCoverageDataWriter(of, coverage.CtrRaw)
+ if cdfw == nil {
+ t.Fatalf("NewCoverageDataWriter failed")
+ }
+ finalHash := [16]byte{1, 2}
+ if err := cdfw.Write(finalHash, args, writeVisitor); err != nil {
+ t.Fatalf("counter file Write failed: %v", err)
+ }
+ } else {
+ if err := cdfw.AppendSegment(args, writeVisitor); err != nil {
+ t.Fatalf("counter file AppendSegment failed: %v", err)
+ }
+ }
+ }
+ if err := of.Close(); err != nil {
+ t.Fatalf("closing covcounters: %v", err)
+ }
+
+ // Read the result file.
+ var cdr *decodecounter.CounterDataReader
+ inf, err := os.Open(cfpath)
+ defer func() {
+ if err := inf.Close(); err != nil {
+ t.Fatalf("close failed with: %v", err)
+ }
+ }()
+
+ if err != nil {
+ t.Fatalf("reopening covcounters file: %v", err)
+ }
+ if cdr, err = decodecounter.NewCounterDataReader(cfpath, inf); err != nil {
+ t.Fatalf("opening covcounters for read: %v", err)
+ }
+ ns := cdr.NumSegments()
+ if ns != numSegments {
+ t.Fatalf("got %d segments want %d", ns, numSegments)
+ }
+ if len(allfuncs) != numSegments {
+ t.Fatalf("expected %d got %d", numSegments, len(allfuncs))
+ }
+
+ for sidx := 0; sidx < int(ns); sidx++ {
+ if off, err := inf.Seek(0, io.SeekCurrent); err != nil {
+ t.Fatalf("Seek failed: %v", err)
+ } else {
+ t.Logf("sidx=%d off=%d\n", sidx, off)
+ }
+
+ if sidx != 0 {
+ if ok, err := cdr.BeginNextSegment(); err != nil {
+ t.Fatalf("BeginNextSegment failed: %v", err)
+ } else if !ok {
+ t.Fatalf("BeginNextSegment return %v on iter %d",
+ ok, sidx)
+ }
+ }
+ funcs := allfuncs[sidx]
+ for i := range funcs {
+ var fp decodecounter.FuncPayload
+ if ok, err := cdr.NextFunc(&fp); err != nil {
+ t.Fatalf("reading func %d: %v", i, err)
+ } else if !ok {
+ t.Fatalf("reading func %d: bad return", i)
+ }
+ got := fmt.Sprintf("%+v", fp)
+ want := fmt.Sprintf("%+v", funcs[i])
+ if got != want {
+ t.Errorf("cdr.NextFunc iter %d\ngot %+v\nwant %+v", i, got, want)
+ }
+ }
+ }
+}
diff --git a/src/internal/coverage/test/roundtrip_test.go b/src/internal/coverage/test/roundtrip_test.go
new file mode 100644
index 0000000..614f56e
--- /dev/null
+++ b/src/internal/coverage/test/roundtrip_test.go
@@ -0,0 +1,331 @@
+// 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 test
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/decodemeta"
+ "internal/coverage/encodemeta"
+ "internal/coverage/slicewriter"
+ "io"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func cmpFuncDesc(want, got coverage.FuncDesc) string {
+ swant := fmt.Sprintf("%+v", want)
+ sgot := fmt.Sprintf("%+v", got)
+ if swant == sgot {
+ return ""
+ }
+ return fmt.Sprintf("wanted %q got %q", swant, sgot)
+}
+
+func TestMetaDataEmptyPackage(t *testing.T) {
+ // Make sure that encoding/decoding works properly with packages
+ // that don't actually have any functions.
+ p := "empty/package"
+ pn := "package"
+ mp := "m"
+ b, err := encodemeta.NewCoverageMetaDataBuilder(p, pn, mp)
+ if err != nil {
+ t.Fatalf("making builder: %v", err)
+ }
+ drws := &slicewriter.WriteSeeker{}
+ b.Emit(drws)
+ drws.Seek(0, io.SeekStart)
+ dec, err := decodemeta.NewCoverageMetaDataDecoder(drws.BytesWritten(), false)
+ if err != nil {
+ t.Fatalf("making decoder: %v", err)
+ }
+ nf := dec.NumFuncs()
+ if nf != 0 {
+ t.Errorf("dec.NumFuncs(): got %d want %d", nf, 0)
+ }
+ pp := dec.PackagePath()
+ if pp != p {
+ t.Errorf("dec.PackagePath(): got %s want %s", pp, p)
+ }
+ ppn := dec.PackageName()
+ if ppn != pn {
+ t.Errorf("dec.PackageName(): got %s want %s", ppn, pn)
+ }
+ pmp := dec.ModulePath()
+ if pmp != mp {
+ t.Errorf("dec.ModulePath(): got %s want %s", pmp, mp)
+ }
+}
+
+func TestMetaDataEncoderDecoder(t *testing.T) {
+ // Test encode path.
+ pp := "foo/bar/pkg"
+ pn := "pkg"
+ mp := "barmod"
+ b, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)
+ if err != nil {
+ t.Fatalf("making builder: %v", err)
+ }
+ f1 := coverage.FuncDesc{
+ Funcname: "func",
+ Srcfile: "foo.go",
+ Units: []coverage.CoverableUnit{
+ coverage.CoverableUnit{StLine: 1, StCol: 2, EnLine: 3, EnCol: 4, NxStmts: 5},
+ coverage.CoverableUnit{StLine: 6, StCol: 7, EnLine: 8, EnCol: 9, NxStmts: 10},
+ },
+ }
+ idx := b.AddFunc(f1)
+ if idx != 0 {
+ t.Errorf("b.AddFunc(f1) got %d want %d", idx, 0)
+ }
+
+ f2 := coverage.FuncDesc{
+ Funcname: "xfunc",
+ Srcfile: "bar.go",
+ Units: []coverage.CoverableUnit{
+ coverage.CoverableUnit{StLine: 1, StCol: 2, EnLine: 3, EnCol: 4, NxStmts: 5},
+ coverage.CoverableUnit{StLine: 6, StCol: 7, EnLine: 8, EnCol: 9, NxStmts: 10},
+ coverage.CoverableUnit{StLine: 11, StCol: 12, EnLine: 13, EnCol: 14, NxStmts: 15},
+ },
+ }
+ idx = b.AddFunc(f2)
+ if idx != 1 {
+ t.Errorf("b.AddFunc(f2) got %d want %d", idx, 0)
+ }
+
+ // Emit into a writer.
+ drws := &slicewriter.WriteSeeker{}
+ b.Emit(drws)
+
+ // Test decode path.
+ drws.Seek(0, io.SeekStart)
+ dec, err := decodemeta.NewCoverageMetaDataDecoder(drws.BytesWritten(), false)
+ if err != nil {
+ t.Fatalf("NewCoverageMetaDataDecoder error: %v", err)
+ }
+ nf := dec.NumFuncs()
+ if nf != 2 {
+ t.Errorf("dec.NumFuncs(): got %d want %d", nf, 2)
+ }
+
+ gotpp := dec.PackagePath()
+ if gotpp != pp {
+ t.Errorf("packagepath: got %s want %s", gotpp, pp)
+ }
+ gotpn := dec.PackageName()
+ if gotpn != pn {
+ t.Errorf("packagename: got %s want %s", gotpn, pn)
+ }
+
+ cases := []coverage.FuncDesc{f1, f2}
+ for i := uint32(0); i < uint32(len(cases)); i++ {
+ var fn coverage.FuncDesc
+ if err := dec.ReadFunc(i, &fn); err != nil {
+ t.Fatalf("err reading function %d: %v", i, err)
+ }
+ res := cmpFuncDesc(cases[i], fn)
+ if res != "" {
+ t.Errorf("ReadFunc(%d): %s", i, res)
+ }
+ }
+}
+
+func createFuncs(i int) []coverage.FuncDesc {
+ res := []coverage.FuncDesc{}
+ lc := uint32(1)
+ for fi := 0; fi < i+1; fi++ {
+ units := []coverage.CoverableUnit{}
+ for ui := 0; ui < (fi+1)*(i+1); ui++ {
+ units = append(units,
+ coverage.CoverableUnit{StLine: lc, StCol: lc + 1,
+ EnLine: lc + 2, EnCol: lc + 3, NxStmts: lc + 4,
+ })
+ lc += 5
+ }
+ f := coverage.FuncDesc{
+ Funcname: fmt.Sprintf("func_%d_%d", i, fi),
+ Srcfile: fmt.Sprintf("foo_%d.go", i),
+ Units: units,
+ }
+ res = append(res, f)
+ }
+ return res
+}
+
+func createBlob(t *testing.T, i int) []byte {
+ nomodule := ""
+ b, err := encodemeta.NewCoverageMetaDataBuilder("foo/pkg", "pkg", nomodule)
+ if err != nil {
+ t.Fatalf("making builder: %v", err)
+ }
+
+ funcs := createFuncs(i)
+ for _, f := range funcs {
+ b.AddFunc(f)
+ }
+ drws := &slicewriter.WriteSeeker{}
+ b.Emit(drws)
+ return drws.BytesWritten()
+}
+
+func createMetaDataBlobs(t *testing.T, nb int) [][]byte {
+ res := [][]byte{}
+ for i := 0; i < nb; i++ {
+ res = append(res, createBlob(t, i))
+ }
+ return res
+}
+
+func TestMetaDataWriterReader(t *testing.T) {
+ d := t.TempDir()
+
+ // Emit a meta-file...
+ mfpath := filepath.Join(d, "covmeta.hash.0")
+ of, err := os.OpenFile(mfpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ t.Fatalf("opening covmeta: %v", err)
+ }
+ //t.Logf("meta-file path is %s", mfpath)
+ blobs := createMetaDataBlobs(t, 7)
+ gran := coverage.CtrGranularityPerBlock
+ mfw := encodemeta.NewCoverageMetaFileWriter(mfpath, of)
+ finalHash := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+ err = mfw.Write(finalHash, blobs, coverage.CtrModeAtomic, gran)
+ if err != nil {
+ t.Fatalf("writing meta-file: %v", err)
+ }
+ if err = of.Close(); err != nil {
+ t.Fatalf("closing meta-file: %v", err)
+ }
+
+ // ... then read it back in, first time without setting fileView,
+ // second time setting it.
+ for k := 0; k < 2; k++ {
+ var fileView []byte
+
+ inf, err := os.Open(mfpath)
+ if err != nil {
+ t.Fatalf("open() on meta-file: %v", err)
+ }
+
+ if k != 0 {
+ // Use fileview to exercise different paths in reader.
+ fi, err := os.Stat(mfpath)
+ if err != nil {
+ t.Fatalf("stat() on meta-file: %v", err)
+ }
+ fileView = make([]byte, fi.Size())
+ if _, err := inf.Read(fileView); err != nil {
+ t.Fatalf("read() on meta-file: %v", err)
+ }
+ if _, err := inf.Seek(int64(0), io.SeekStart); err != nil {
+ t.Fatalf("seek() on meta-file: %v", err)
+ }
+ }
+
+ mfr, err := decodemeta.NewCoverageMetaFileReader(inf, fileView)
+ if err != nil {
+ t.Fatalf("k=%d NewCoverageMetaFileReader failed with: %v", k, err)
+ }
+ np := mfr.NumPackages()
+ if np != 7 {
+ t.Fatalf("k=%d wanted 7 packages got %d", k, np)
+ }
+ md := mfr.CounterMode()
+ wmd := coverage.CtrModeAtomic
+ if md != wmd {
+ t.Fatalf("k=%d wanted mode %d got %d", k, wmd, md)
+ }
+ gran := mfr.CounterGranularity()
+ wgran := coverage.CtrGranularityPerBlock
+ if gran != wgran {
+ t.Fatalf("k=%d wanted gran %d got %d", k, wgran, gran)
+ }
+
+ payload := []byte{}
+ for pi := 0; pi < int(np); pi++ {
+ var pd *decodemeta.CoverageMetaDataDecoder
+ var err error
+ pd, payload, err = mfr.GetPackageDecoder(uint32(pi), payload)
+ if err != nil {
+ t.Fatalf("GetPackageDecoder(%d) failed with: %v", pi, err)
+ }
+ efuncs := createFuncs(pi)
+ nf := pd.NumFuncs()
+ if len(efuncs) != int(nf) {
+ t.Fatalf("decoding pk %d wanted %d funcs got %d",
+ pi, len(efuncs), nf)
+ }
+ var f coverage.FuncDesc
+ for fi := 0; fi < int(nf); fi++ {
+ if err := pd.ReadFunc(uint32(fi), &f); err != nil {
+ t.Fatalf("ReadFunc(%d) pk %d got error %v",
+ fi, pi, err)
+ }
+ res := cmpFuncDesc(efuncs[fi], f)
+ if res != "" {
+ t.Errorf("ReadFunc(%d) pk %d: %s", fi, pi, res)
+ }
+ }
+ }
+ inf.Close()
+ }
+}
+
+func TestMetaDataDecodeLitFlagIssue57942(t *testing.T) {
+
+ // Encode a package with a few functions. The funcs alternate
+ // between regular functions and function literals.
+ pp := "foo/bar/pkg"
+ pn := "pkg"
+ mp := "barmod"
+ b, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)
+ if err != nil {
+ t.Fatalf("making builder: %v", err)
+ }
+ const NF = 6
+ const NCU = 1
+ ln := uint32(10)
+ wantfds := []coverage.FuncDesc{}
+ for fi := uint32(0); fi < NF; fi++ {
+ fis := fmt.Sprintf("%d", fi)
+ fd := coverage.FuncDesc{
+ Funcname: "func" + fis,
+ Srcfile: "foo" + fis + ".go",
+ Units: []coverage.CoverableUnit{
+ coverage.CoverableUnit{StLine: ln + 1, StCol: 2, EnLine: ln + 3, EnCol: 4, NxStmts: fi + 2},
+ },
+ Lit: (fi % 2) == 0,
+ }
+ wantfds = append(wantfds, fd)
+ b.AddFunc(fd)
+ }
+
+ // Emit into a writer.
+ drws := &slicewriter.WriteSeeker{}
+ b.Emit(drws)
+
+ // Decode the result.
+ drws.Seek(0, io.SeekStart)
+ dec, err := decodemeta.NewCoverageMetaDataDecoder(drws.BytesWritten(), false)
+ if err != nil {
+ t.Fatalf("making decoder: %v", err)
+ }
+ nf := dec.NumFuncs()
+ if nf != NF {
+ t.Fatalf("decoder number of functions: got %d want %d", nf, NF)
+ }
+ var fn coverage.FuncDesc
+ for i := uint32(0); i < uint32(NF); i++ {
+ if err := dec.ReadFunc(i, &fn); err != nil {
+ t.Fatalf("err reading function %d: %v", i, err)
+ }
+ res := cmpFuncDesc(wantfds[i], fn)
+ if res != "" {
+ t.Errorf("ReadFunc(%d): %s", i, res)
+ }
+ }
+}
diff --git a/src/internal/coverage/uleb128/uleb128.go b/src/internal/coverage/uleb128/uleb128.go
new file mode 100644
index 0000000..e5cd92a
--- /dev/null
+++ b/src/internal/coverage/uleb128/uleb128.go
@@ -0,0 +1,20 @@
+// 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 uleb128
+
+func AppendUleb128(b []byte, v uint) []byte {
+ for {
+ c := uint8(v & 0x7f)
+ v >>= 7
+ if v != 0 {
+ c |= 0x80
+ }
+ b = append(b, c)
+ if c&0x80 == 0 {
+ break
+ }
+ }
+ return b
+}