mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime/coverage: runtime routines to emit coverage data
This patch fleshes out the runtime support for emitting coverage data at the end of a run of an instrumented binary. Data is emitted in the form of a pair of files, a meta-out-file and counter-data-outfile, each written to the dir GOCOVERDIR. The meta-out-file is emitted only if required; no need to emit again if an existing meta-data file with the same hash and length is present. Updates #51430. Change-Id: I59d20a4b8c05910c933ee29527972f8e401b1685 Reviewed-on: https://go-review.googlesource.com/c/go/+/355451 Reviewed-by: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
7a74829858
commit
87db4ffada
9 changed files with 932 additions and 31 deletions
|
|
@ -32,6 +32,7 @@ const trimPathGoRootFinal string = "$GOROOT"
|
||||||
var runtimePackages = map[string]struct{}{
|
var runtimePackages = map[string]struct{}{
|
||||||
"internal/abi": struct{}{},
|
"internal/abi": struct{}{},
|
||||||
"internal/bytealg": struct{}{},
|
"internal/bytealg": struct{}{},
|
||||||
|
"internal/coverage/rtcov": struct{}{},
|
||||||
"internal/cpu": struct{}{},
|
"internal/cpu": struct{}{},
|
||||||
"internal/goarch": struct{}{},
|
"internal/goarch": struct{}{},
|
||||||
"internal/goos": struct{}{},
|
"internal/goos": struct{}{},
|
||||||
|
|
@ -39,7 +40,6 @@ var runtimePackages = map[string]struct{}{
|
||||||
"runtime/internal/atomic": struct{}{},
|
"runtime/internal/atomic": struct{}{},
|
||||||
"runtime/internal/math": struct{}{},
|
"runtime/internal/math": struct{}{},
|
||||||
"runtime/internal/sys": struct{}{},
|
"runtime/internal/sys": struct{}{},
|
||||||
"runtime/internal/syscall": struct{}{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Go toolchain.
|
// The Go toolchain.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
@ -584,6 +584,14 @@ var depsRules = `
|
||||||
FMT, internal/coverage, os,
|
FMT, internal/coverage, os,
|
||||||
path/filepath, regexp, sort, strconv
|
path/filepath, regexp, sort, strconv
|
||||||
< internal/coverage/pods;
|
< internal/coverage/pods;
|
||||||
|
|
||||||
|
FMT, bufio, crypto/md5, encoding/binary, runtime/debug,
|
||||||
|
internal/coverage, internal/coverage/cmerge,
|
||||||
|
internal/coverage/cformat, internal/coverage/calloc,
|
||||||
|
internal/coverage/decodecounter, internal/coverage/decodemeta,
|
||||||
|
internal/coverage/encodecounter, internal/coverage/encodemeta,
|
||||||
|
internal/coverage/pods, os, path/filepath, reflect, time, unsafe
|
||||||
|
< runtime/coverage;
|
||||||
`
|
`
|
||||||
|
|
||||||
// listStdPkgs returns the same list of packages as "go list std".
|
// listStdPkgs returns the same list of packages as "go list std".
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,12 @@ type CovMetaBlob struct {
|
||||||
CounterMode uint8 // coverage.CounterMode
|
CounterMode uint8 // coverage.CounterMode
|
||||||
CounterGranularity uint8 // coverage.CounterGranularity
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
8
src/runtime/coverage/dummy.s
Normal file
8
src/runtime/coverage/dummy.s
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// The runtime package uses //go:linkname to push a few functions into this
|
||||||
|
// package but we still need a .s file so the Go tool does not pass -complete
|
||||||
|
// to 'go tool compile' so the latter does not complain about Go functions
|
||||||
|
// with no bodies.
|
||||||
611
src/runtime/coverage/emit.go
Normal file
611
src/runtime/coverage/emit.go
Normal file
|
|
@ -0,0 +1,611 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"internal/coverage"
|
||||||
|
"internal/coverage/encodecounter"
|
||||||
|
"internal/coverage/encodemeta"
|
||||||
|
"internal/coverage/rtcov"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file contains functions that support the writing of data files
|
||||||
|
// emitted at the end of code coverage testing runs, from instrumented
|
||||||
|
// executables.
|
||||||
|
|
||||||
|
// getCovMetaList returns a list of meta-data blobs registered
|
||||||
|
// for the currently executing instrumented program. It is defined in the
|
||||||
|
// runtime.
|
||||||
|
func getCovMetaList() []rtcov.CovMetaBlob
|
||||||
|
|
||||||
|
// getCovCounterList returns a list of counter-data blobs registered
|
||||||
|
// for the currently executing instrumented program. It is defined in the
|
||||||
|
// runtime.
|
||||||
|
func getCovCounterList() []rtcov.CovCounterBlob
|
||||||
|
|
||||||
|
// getCovPkgMap returns a map storing the remapped package IDs for
|
||||||
|
// hard-coded runtime packages (see internal/coverage/pkgid.go for
|
||||||
|
// more on why hard-coded package IDs are needed). This function
|
||||||
|
// is defined in the runtime.
|
||||||
|
func getCovPkgMap() map[int]int
|
||||||
|
|
||||||
|
// emitState holds useful state information during the emit process.
|
||||||
|
//
|
||||||
|
// When an instrumented program finishes execution and starts the
|
||||||
|
// process of writing out coverage data, it's possible that an
|
||||||
|
// existing meta-data file already exists in the output directory. In
|
||||||
|
// this case openOutputFiles() below will leave the 'mf' field below
|
||||||
|
// as nil. If a new meta-data file is needed, field 'mfname' will be
|
||||||
|
// the final desired path of the meta file, 'mftmp' will be a
|
||||||
|
// temporary file, and 'mf' will be an open os.File pointer for
|
||||||
|
// 'mftmp'. The meta-data file payload will be written to 'mf', the
|
||||||
|
// temp file will be then closed and renamed (from 'mftmp' to
|
||||||
|
// 'mfname'), so as to insure that the meta-data file is created
|
||||||
|
// atomically; we want this so that things work smoothly in cases
|
||||||
|
// where there are several instances of a given instrumented program
|
||||||
|
// all terminating at the same time and trying to create meta-data
|
||||||
|
// files simultaneously.
|
||||||
|
//
|
||||||
|
// For counter data files there is less chance of a collision, hence
|
||||||
|
// the openOutputFiles() stores the counter data file in 'cfname' and
|
||||||
|
// then places the *io.File into 'cf'.
|
||||||
|
type emitState struct {
|
||||||
|
mfname string // path of final meta-data output file
|
||||||
|
mftmp string // path to meta-data temp file (if needed)
|
||||||
|
mf *os.File // open os.File for meta-data temp file
|
||||||
|
cfname string // path of final counter data file
|
||||||
|
cftmp string // path to counter data temp file
|
||||||
|
cf *os.File // open os.File for counter data file
|
||||||
|
outdir string // output directory
|
||||||
|
|
||||||
|
// List of meta-data symbols obtained from the runtime
|
||||||
|
metalist []rtcov.CovMetaBlob
|
||||||
|
|
||||||
|
// List of counter-data symbols obtained from the runtime
|
||||||
|
counterlist []rtcov.CovCounterBlob
|
||||||
|
|
||||||
|
// Table to use for remapping hard-coded pkg ids.
|
||||||
|
pkgmap map[int]int
|
||||||
|
|
||||||
|
// emit debug trace output
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// finalHash is computed at init time from the list of meta-data
|
||||||
|
// symbols registered during init. It is used both for writing the
|
||||||
|
// meta-data file and counter-data files.
|
||||||
|
finalHash [16]byte
|
||||||
|
// Set to true when we've computed finalHash + finalMetaLen.
|
||||||
|
finalHashComputed bool
|
||||||
|
// Total meta-data length.
|
||||||
|
finalMetaLen uint64
|
||||||
|
// Records whether we've already attempted to write meta-data.
|
||||||
|
metaDataEmitAttempted bool
|
||||||
|
// Counter mode for this instrumented program run.
|
||||||
|
cmode coverage.CounterMode
|
||||||
|
// Counter granularity for this instrumented program run.
|
||||||
|
cgran coverage.CounterGranularity
|
||||||
|
// Cached value of GOCOVERDIR environment variable.
|
||||||
|
goCoverDir string
|
||||||
|
// Copy of os.Args made at init time, converted into map format.
|
||||||
|
capturedOsArgs map[string]string
|
||||||
|
// Flag used in tests to signal that coverage data already written.
|
||||||
|
covProfileAlreadyEmitted bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileType is used to select between counter-data files and
|
||||||
|
// meta-data files.
|
||||||
|
type fileType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
noFile = 1 << iota
|
||||||
|
metaDataFile
|
||||||
|
counterDataFile
|
||||||
|
)
|
||||||
|
|
||||||
|
// emitMetaData emits the meta-data output file for this coverage run.
|
||||||
|
// This entry point is intended to be invoked by the compiler from
|
||||||
|
// an instrumented program's main package init func.
|
||||||
|
func emitMetaData() {
|
||||||
|
if covProfileAlreadyEmitted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ml, err := prepareForMetaEmit()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", err)
|
||||||
|
if os.Getenv("GOCOVERDEBUG") != "" {
|
||||||
|
panic("meta-data write failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ml) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "program not built with -cover\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
goCoverDir = os.Getenv("GOCOVERDIR")
|
||||||
|
if goCoverDir == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := emitMetaDataToDirectory(goCoverDir, ml); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", err)
|
||||||
|
if os.Getenv("GOCOVERDEBUG") != "" {
|
||||||
|
panic("meta-data write failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func modeClash(m coverage.CounterMode) bool {
|
||||||
|
if m == coverage.CtrModeRegOnly || m == coverage.CtrModeTestMain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if cmode == coverage.CtrModeInvalid {
|
||||||
|
cmode = m
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cmode != m
|
||||||
|
}
|
||||||
|
|
||||||
|
func granClash(g coverage.CounterGranularity) bool {
|
||||||
|
if cgran == coverage.CtrGranularityInvalid {
|
||||||
|
cgran = g
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cgran != g
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareForMetaEmit performs preparatory steps needed prior to
|
||||||
|
// emitting a meta-data file, notably computing a final hash of
|
||||||
|
// all meta-data blobs and capturing os args.
|
||||||
|
func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) {
|
||||||
|
// Ask the runtime for the list of coverage meta-data symbols.
|
||||||
|
ml := getCovMetaList()
|
||||||
|
|
||||||
|
// In the normal case (go build -o prog.exe ... ; ./prog.exe)
|
||||||
|
// len(ml) will always be non-zero, but we check here since at
|
||||||
|
// some point this function will be reachable via user-callable
|
||||||
|
// APIs (for example, to write out coverage data from a server
|
||||||
|
// program that doesn't ever call os.Exit).
|
||||||
|
if len(ml) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &emitState{
|
||||||
|
metalist: ml,
|
||||||
|
debug: os.Getenv("GOCOVERDEBUG") != "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture os.Args() now so as to avoid issues if args
|
||||||
|
// are rewritten during program execution.
|
||||||
|
capturedOsArgs = captureOsArgs()
|
||||||
|
|
||||||
|
if s.debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR"))
|
||||||
|
fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n")
|
||||||
|
for k, b := range ml {
|
||||||
|
fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", k, b.PkgPath)
|
||||||
|
if b.PkgID != -1 {
|
||||||
|
fmt.Fprintf(os.Stderr, " hcid: %d", b.PkgID)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
||||||
|
pm := getCovPkgMap()
|
||||||
|
fmt.Fprintf(os.Stderr, "=+= remap table:\n")
|
||||||
|
for from, to := range pm {
|
||||||
|
fmt.Fprintf(os.Stderr, "=+= from %d to %d\n",
|
||||||
|
uint32(from), uint32(to))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h := md5.New()
|
||||||
|
tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
|
||||||
|
for _, entry := range ml {
|
||||||
|
if _, err := h.Write(entry.Hash[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlen += uint64(entry.Len)
|
||||||
|
ecm := coverage.CounterMode(entry.CounterMode)
|
||||||
|
if modeClash(ecm) {
|
||||||
|
return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", ml[0].PkgPath, cmode, entry.PkgPath, ecm)
|
||||||
|
}
|
||||||
|
ecg := coverage.CounterGranularity(entry.CounterGranularity)
|
||||||
|
if granClash(ecg) {
|
||||||
|
return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", ml[0].PkgPath, cgran, entry.PkgPath, ecg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash mode and granularity as well.
|
||||||
|
h.Write([]byte(cmode.String()))
|
||||||
|
h.Write([]byte(cgran.String()))
|
||||||
|
|
||||||
|
// Compute final digest.
|
||||||
|
fh := h.Sum(nil)
|
||||||
|
copy(finalHash[:], fh)
|
||||||
|
finalHashComputed = true
|
||||||
|
finalMetaLen = tlen
|
||||||
|
|
||||||
|
return ml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitMetaData emits the meta-data output file to the specified
|
||||||
|
// directory, returning an error if something went wrong.
|
||||||
|
func emitMetaDataToDirectory(outdir string, ml []rtcov.CovMetaBlob) error {
|
||||||
|
ml, err := prepareForMetaEmit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(ml) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metaDataEmitAttempted = true
|
||||||
|
|
||||||
|
s := &emitState{
|
||||||
|
metalist: ml,
|
||||||
|
debug: os.Getenv("GOCOVERDEBUG") != "",
|
||||||
|
outdir: outdir,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open output files.
|
||||||
|
if err := s.openOutputFiles(finalHash, finalMetaLen, metaDataFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit meta-data file only if needed (may already be present).
|
||||||
|
if s.needMetaDataFile() {
|
||||||
|
if err := s.emitMetaDataFile(finalHash, finalMetaLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitCounterData emits the counter data output file for this coverage run.
|
||||||
|
// This entry point is intended to be invoked by the runtime when an
|
||||||
|
// instrumented program is terminating or calling os.Exit().
|
||||||
|
func emitCounterData() {
|
||||||
|
if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := emitCounterDataToDirectory(goCoverDir); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", err)
|
||||||
|
if os.Getenv("GOCOVERDEBUG") != "" {
|
||||||
|
panic("counter-data write failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitMetaData emits the counter-data output file for this coverage run.
|
||||||
|
func emitCounterDataToDirectory(outdir string) error {
|
||||||
|
// Ask the runtime for the list of coverage counter symbols.
|
||||||
|
cl := getCovCounterList()
|
||||||
|
if len(cl) == 0 {
|
||||||
|
// no work to do here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !finalHashComputed {
|
||||||
|
return fmt.Errorf("error: meta-data not available (binary not built with -cover?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the runtime for the list of coverage counter symbols.
|
||||||
|
pm := getCovPkgMap()
|
||||||
|
s := &emitState{
|
||||||
|
counterlist: cl,
|
||||||
|
pkgmap: pm,
|
||||||
|
outdir: outdir,
|
||||||
|
debug: os.Getenv("GOCOVERDEBUG") != "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open output file.
|
||||||
|
if err := s.openOutputFiles(finalHash, finalMetaLen, counterDataFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.cf == nil {
|
||||||
|
return fmt.Errorf("counter data output file open failed (no additional info")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit counter data file.
|
||||||
|
if err := s.emitCounterDataFile(finalHash, s.cf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.cf.Close(); err != nil {
|
||||||
|
return fmt.Errorf("closing counter data file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter file has now been closed. Rename the temp to the
|
||||||
|
// final desired path.
|
||||||
|
if err := os.Rename(s.cftmp, s.cfname); err != nil {
|
||||||
|
return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.cfname, s.cftmp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openMetaFile determines whether we need to emit a meta-data output
|
||||||
|
// file, or whether we can reuse the existing file in the coverage out
|
||||||
|
// dir. It updates mfname/mftmp/mf fields in 's', returning an error
|
||||||
|
// if something went wrong. See the comment on the emitState type
|
||||||
|
// definition above for more on how file opening is managed.
|
||||||
|
func (s *emitState) openMetaFile(metaHash [16]byte, metaLen uint64) error {
|
||||||
|
|
||||||
|
// Open meta-outfile for reading to see if it exists.
|
||||||
|
fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, metaHash)
|
||||||
|
s.mfname = filepath.Join(s.outdir, fn)
|
||||||
|
fi, err := os.Stat(s.mfname)
|
||||||
|
if err != nil || fi.Size() != int64(metaLen) {
|
||||||
|
// We need a new meta-file.
|
||||||
|
tname := "tmp." + fn + fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
s.mftmp = filepath.Join(s.outdir, tname)
|
||||||
|
s.mf, err = os.Create(s.mftmp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating meta-data file %s: %v", s.mftmp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openCounterFile opens an output file for the counter data portion
|
||||||
|
// of a test coverage run. If updates the 'cfname' and 'cf' fields in
|
||||||
|
// 's', returning an error if something went wrong.
|
||||||
|
func (s *emitState) openCounterFile(metaHash [16]byte) error {
|
||||||
|
processID := os.Getpid()
|
||||||
|
fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, processID, time.Now().UnixNano())
|
||||||
|
s.cfname = filepath.Join(s.outdir, fn)
|
||||||
|
s.cftmp = filepath.Join(s.outdir, "tmp."+fn)
|
||||||
|
var err error
|
||||||
|
s.cf, err = os.Create(s.cftmp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating counter data file %s: %v", s.cftmp, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openOutputFiles opens output files in preparation for emitting
|
||||||
|
// coverage data. In the case of the meta-data file, openOutputFiles
|
||||||
|
// may determine that we can reuse an existing meta-data file in the
|
||||||
|
// outdir, in which case it will leave the 'mf' field in the state
|
||||||
|
// struct as nil. If a new meta-file is needed, the field 'mfname'
|
||||||
|
// will be the final desired path of the meta file, 'mftmp' will be a
|
||||||
|
// temporary file, and 'mf' will be an open os.File pointer for
|
||||||
|
// 'mftmp'. The idea is that the client/caller will write content into
|
||||||
|
// 'mf', close it, and then rename 'mftmp' to 'mfname'. This function
|
||||||
|
// also opens the counter data output file, setting 'cf' and 'cfname'
|
||||||
|
// in the state struct.
|
||||||
|
func (s *emitState) openOutputFiles(metaHash [16]byte, metaLen uint64, which fileType) error {
|
||||||
|
fi, err := os.Stat(s.outdir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", s.outdir, err)
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return fmt.Errorf("output directory %q not a directory; no coverage data written", s.outdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (which & metaDataFile) != 0 {
|
||||||
|
if err := s.openMetaFile(metaHash, metaLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (which & counterDataFile) != 0 {
|
||||||
|
if err := s.openCounterFile(metaHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitMetaDataFile emits coverage meta-data to a previously opened
|
||||||
|
// temporary file (s.mftmp), then renames the generated file to the
|
||||||
|
// final path (s.mfname).
|
||||||
|
func (s *emitState) emitMetaDataFile(finalHash [16]byte, tlen uint64) error {
|
||||||
|
if err := writeMetaData(s.mf, s.metalist, cmode, cgran, finalHash); err != nil {
|
||||||
|
return fmt.Errorf("writing %s: %v\n", s.mftmp, err)
|
||||||
|
}
|
||||||
|
if err := s.mf.Close(); err != nil {
|
||||||
|
return fmt.Errorf("closing meta data temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp file has now been flushed and closed. Rename the temp to the
|
||||||
|
// final desired path.
|
||||||
|
if err := os.Rename(s.mftmp, s.mfname); err != nil {
|
||||||
|
return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.mfname, s.mftmp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// needMetaDataFile returns TRUE if we need to emit a meta-data file
|
||||||
|
// for this program run. It should be used only after
|
||||||
|
// openOutputFiles() has been invoked.
|
||||||
|
func (s *emitState) needMetaDataFile() bool {
|
||||||
|
return s.mf != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.CounterMode, gran coverage.CounterGranularity, finalHash [16]byte) error {
|
||||||
|
mfw := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", w)
|
||||||
|
|
||||||
|
// Note: "sd" is re-initialized on each iteration of the loop
|
||||||
|
// below, and would normally be declared inside the loop, but
|
||||||
|
// placed here escape analysis since we capture it in bufHdr.
|
||||||
|
var sd []byte
|
||||||
|
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
|
||||||
|
|
||||||
|
var blobs [][]byte
|
||||||
|
for _, e := range metalist {
|
||||||
|
bufHdr.Data = uintptr(unsafe.Pointer(e.P))
|
||||||
|
bufHdr.Len = int(e.Len)
|
||||||
|
bufHdr.Cap = int(e.Len)
|
||||||
|
blobs = append(blobs, sd)
|
||||||
|
}
|
||||||
|
return mfw.Write(finalHash, blobs, cmode, gran)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *emitState) NumFuncs() (int, error) {
|
||||||
|
var sd []uint32
|
||||||
|
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
|
||||||
|
|
||||||
|
totalFuncs := 0
|
||||||
|
for _, c := range s.counterlist {
|
||||||
|
bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
|
||||||
|
bufHdr.Len = int(c.Len)
|
||||||
|
bufHdr.Cap = int(c.Len)
|
||||||
|
for i := 0; i < len(sd); i++ {
|
||||||
|
// Skip ahead until the next non-zero value.
|
||||||
|
if sd[i] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a function that was executed.
|
||||||
|
nCtrs := sd[i]
|
||||||
|
totalFuncs++
|
||||||
|
|
||||||
|
// Skip over this function.
|
||||||
|
i += coverage.FirstCtrOffset + int(nCtrs) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalFuncs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
|
||||||
|
var sd []uint32
|
||||||
|
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
|
||||||
|
|
||||||
|
dpkg := uint32(0)
|
||||||
|
for _, c := range s.counterlist {
|
||||||
|
bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
|
||||||
|
bufHdr.Len = int(c.Len)
|
||||||
|
bufHdr.Cap = int(c.Len)
|
||||||
|
for i := 0; i < len(sd); i++ {
|
||||||
|
// Skip ahead until the next non-zero value.
|
||||||
|
if sd[i] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a function that was executed.
|
||||||
|
nCtrs := sd[i+coverage.NumCtrsOffset]
|
||||||
|
pkgId := sd[i+coverage.PkgIdOffset]
|
||||||
|
funcId := sd[i+coverage.FuncIdOffset]
|
||||||
|
cst := i + coverage.FirstCtrOffset
|
||||||
|
counters := sd[cst : cst+int(nCtrs)]
|
||||||
|
|
||||||
|
if s.debug {
|
||||||
|
if pkgId != dpkg {
|
||||||
|
dpkg = pkgId
|
||||||
|
fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn",
|
||||||
|
i, pkgId)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", i, funcId, nCtrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vet and/or fix up package ID. A package ID of zero
|
||||||
|
// indicates that there is some new package X that is a
|
||||||
|
// runtime dependency, and this package has code that
|
||||||
|
// executes before its corresponding init package runs.
|
||||||
|
// This is a fatal error that we should only see during
|
||||||
|
// Go development (e.g. tip).
|
||||||
|
ipk := int32(pkgId)
|
||||||
|
if ipk == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
|
||||||
|
} else if ipk < 0 {
|
||||||
|
if newId, ok := s.pkgmap[int(ipk)]; ok {
|
||||||
|
pkgId = uint32(newId)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The package ID value stored in the counter array
|
||||||
|
// has 1 added to it (so as to preclude the
|
||||||
|
// possibility of a zero value ; see
|
||||||
|
// runtime.addCovMeta), so subtract off 1 here to form
|
||||||
|
// the real package ID.
|
||||||
|
pkgId--
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f(pkgId, funcId, counters); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over this function.
|
||||||
|
i += coverage.FirstCtrOffset + int(nCtrs) - 1
|
||||||
|
}
|
||||||
|
if s.debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// captureOsArgs converts os.Args() into the format we use to store
|
||||||
|
// this info in the counter data file (counter data file "args"
|
||||||
|
// section is a generic key-value collection). See the 'args' section
|
||||||
|
// in internal/coverage/defs.go for more info. The args map
|
||||||
|
// is also used to capture GOOS + GOARCH values as well.
|
||||||
|
func captureOsArgs() map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
m["argc"] = fmt.Sprintf("%d", len(os.Args))
|
||||||
|
for k, a := range os.Args {
|
||||||
|
m[fmt.Sprintf("argv%d", k)] = a
|
||||||
|
}
|
||||||
|
m["GOOS"] = runtime.GOOS
|
||||||
|
m["GOARCH"] = runtime.GOARCH
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitCounterDataFile emits the counter data portion of a
|
||||||
|
// coverage output file (to the file 's.cf').
|
||||||
|
func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
|
||||||
|
cfw := encodecounter.NewCoverageDataWriter(w, coverage.CtrULeb128)
|
||||||
|
if err := cfw.Write(finalHash, capturedOsArgs, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// markProfileEmitted signals the runtime/coverage machinery that
|
||||||
|
// coverate data output files have already been written out, and there
|
||||||
|
// is no need to take any additional action at exit time. This
|
||||||
|
// function is called (via linknamed reference) from the
|
||||||
|
// coverage-related boilerplate code in _testmain.go emitted for go
|
||||||
|
// unit tests.
|
||||||
|
func markProfileEmitted(val bool) {
|
||||||
|
covProfileAlreadyEmitted = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportErrorInHardcodedList(slot, pkgID int32, fnID, nCtrs uint32) {
|
||||||
|
metaList := getCovMetaList()
|
||||||
|
pkgMap := getCovPkgMap()
|
||||||
|
|
||||||
|
println("internal error in coverage meta-data tracking:")
|
||||||
|
println("encountered bad pkgID:", pkgID, " at slot:", slot,
|
||||||
|
" fnID:", fnID, " numCtrs:", nCtrs)
|
||||||
|
println("list of hard-coded runtime package IDs needs revising.")
|
||||||
|
println("[see the comment on the 'rtPkgs' var in ")
|
||||||
|
println(" <goroot>/src/internal/coverage/pkid.go]")
|
||||||
|
println("registered list:")
|
||||||
|
for k, b := range metaList {
|
||||||
|
print("slot: ", k, " path='", b.PkgPath, "' ")
|
||||||
|
if b.PkgID != -1 {
|
||||||
|
print(" hard-coded id: ", b.PkgID)
|
||||||
|
}
|
||||||
|
println("")
|
||||||
|
}
|
||||||
|
println("remap table:")
|
||||||
|
for from, to := range pkgMap {
|
||||||
|
println("from ", from, " to ", to)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/runtime/coverage/hooks.go
Normal file
42
src/runtime/coverage/hooks.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import _ "unsafe"
|
||||||
|
|
||||||
|
// initHook is invoked from the main package "init" routine in
|
||||||
|
// programs built with "-cover". This function is intended to be
|
||||||
|
// called only by the compiler.
|
||||||
|
//
|
||||||
|
// If 'istest' is false, it indicates we're building a regular program
|
||||||
|
// ("go build -cover ..."), in which case we immediately try to write
|
||||||
|
// out the meta-data file, and register emitCounterData as an exit
|
||||||
|
// hook.
|
||||||
|
//
|
||||||
|
// If 'istest' is true (indicating that the program in question is a
|
||||||
|
// Go test binary), then we tentatively queue up both emitMetaData and
|
||||||
|
// emitCounterData as exit hooks. In the normal case (e.g. regular "go
|
||||||
|
// test -cover" run) the testmain.go boilerplate will run at the end
|
||||||
|
// of the test, write out the coverage percentage, and then invoke
|
||||||
|
// markProfileEmitted() to indicate that no more work needs to be
|
||||||
|
// done. If however that call is never made, this is a sign that the
|
||||||
|
// test binary is being used as a replacement binary for the tool
|
||||||
|
// being tested, hence we do want to run exit hooks when the program
|
||||||
|
// terminates.
|
||||||
|
func initHook(istest bool) {
|
||||||
|
// Note: hooks are run in reverse registration order, so
|
||||||
|
// register the counter data hook before the meta-data hook
|
||||||
|
// (in the case where two hooks are needed).
|
||||||
|
runOnNonZeroExit := true
|
||||||
|
runtime_addExitHook(emitCounterData, runOnNonZeroExit)
|
||||||
|
if istest {
|
||||||
|
runtime_addExitHook(emitMetaData, runOnNonZeroExit)
|
||||||
|
} else {
|
||||||
|
emitMetaData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_addExitHook runtime.addExitHook
|
||||||
|
func runtime_addExitHook(f func(), runOnNonZeroExit bool)
|
||||||
207
src/runtime/coverage/testsupport.go
Normal file
207
src/runtime/coverage/testsupport.go
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"internal/coverage"
|
||||||
|
"internal/coverage/calloc"
|
||||||
|
"internal/coverage/cformat"
|
||||||
|
"internal/coverage/cmerge"
|
||||||
|
"internal/coverage/decodecounter"
|
||||||
|
"internal/coverage/decodemeta"
|
||||||
|
"internal/coverage/pods"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processCoverTestDir is called (via a linknamed reference) from
|
||||||
|
// testmain code when "go test -cover" is in effect. It is not
|
||||||
|
// intended to be used other than internally by the Go command's
|
||||||
|
// generated code.
|
||||||
|
func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error {
|
||||||
|
cmode := coverage.ParseCounterMode(cm)
|
||||||
|
if cmode == coverage.CtrModeInvalid {
|
||||||
|
return fmt.Errorf("invalid counter mode %q", cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit meta-data and counter data.
|
||||||
|
ml := getCovMetaList()
|
||||||
|
if len(ml) == 0 {
|
||||||
|
// This corresponds to the case where we have a package that
|
||||||
|
// contains test code but no functions (which is fine). In this
|
||||||
|
// case there is no need to emit anything.
|
||||||
|
} else {
|
||||||
|
if err := emitMetaDataToDirectory(dir, ml); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := emitCounterDataToDirectory(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect pods from test run. For the majority of cases we would
|
||||||
|
// expect to see a single pod here, but allow for multiple pods in
|
||||||
|
// case the test harness is doing extra work to collect data files
|
||||||
|
// from builds that it kicks off as part of the testing.
|
||||||
|
podlist, err := pods.CollectPods([]string{dir}, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading from %s: %v", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open text output file if appropriate.
|
||||||
|
var tf *os.File
|
||||||
|
var tfClosed bool
|
||||||
|
if cfile != "" {
|
||||||
|
var err error
|
||||||
|
tf, err = os.Create(cfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if !tfClosed {
|
||||||
|
tfClosed = true
|
||||||
|
tf.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read/process the pods.
|
||||||
|
ts := &tstate{
|
||||||
|
cm: &cmerge.Merger{},
|
||||||
|
cf: cformat.NewFormatter(cmode),
|
||||||
|
cmode: cmode,
|
||||||
|
}
|
||||||
|
for _, p := range podlist {
|
||||||
|
if err := ts.processPod(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit percent.
|
||||||
|
if err := ts.cf.EmitPercent(os.Stdout, cpkg, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit text output.
|
||||||
|
if tf != nil {
|
||||||
|
if err := ts.cf.EmitTextual(tf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tfClosed = true
|
||||||
|
if err := tf.Close(); err != nil {
|
||||||
|
return fmt.Errorf("closing %s: %v", cfile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tstate struct {
|
||||||
|
calloc.BatchCounterAlloc
|
||||||
|
cm *cmerge.Merger
|
||||||
|
cf *cformat.Formatter
|
||||||
|
cmode coverage.CounterMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// processPod reads coverage counter data for a specific pod.
|
||||||
|
func (ts *tstate) processPod(p pods.Pod) error {
|
||||||
|
// Open meta-data file
|
||||||
|
f, err := os.Open(p.MetaFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
var mfr *decodemeta.CoverageMetaFileReader
|
||||||
|
mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err)
|
||||||
|
}
|
||||||
|
newmode := mfr.CounterMode()
|
||||||
|
if newmode != ts.cmode {
|
||||||
|
return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile)
|
||||||
|
}
|
||||||
|
newgran := mfr.CounterGranularity()
|
||||||
|
if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read counter data files.
|
||||||
|
pmm := make(map[pkfunc][]uint32)
|
||||||
|
for _, cdf := range p.CounterDataFiles {
|
||||||
|
cf, err := os.Open(cdf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening counter data file %s: %s", cdf, err)
|
||||||
|
}
|
||||||
|
var cdr *decodecounter.CounterDataReader
|
||||||
|
cdr, err = decodecounter.NewCounterDataReader(cdf, cf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading counter data file %s: %s", cdf, err)
|
||||||
|
}
|
||||||
|
var data decodecounter.FuncPayload
|
||||||
|
for {
|
||||||
|
ok, err := cdr.NextFunc(&data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading counter data file %s: %v", cdf, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: sanity check on pkg and func IDs?
|
||||||
|
key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
|
||||||
|
if prev, found := pmm[key]; found {
|
||||||
|
// Note: no overflow reporting here.
|
||||||
|
if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil {
|
||||||
|
return fmt.Errorf("processing counter data file %s: %v", cdf, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := ts.AllocateCounters(len(data.Counters))
|
||||||
|
copy(c, data.Counters)
|
||||||
|
pmm[key] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit meta-data file.
|
||||||
|
np := uint32(mfr.NumPackages())
|
||||||
|
payload := []byte{}
|
||||||
|
for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
|
||||||
|
var pd *decodemeta.CoverageMetaDataDecoder
|
||||||
|
pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
|
||||||
|
}
|
||||||
|
ts.cf.SetPackage(pd.PackagePath())
|
||||||
|
var fd coverage.FuncDesc
|
||||||
|
nf := pd.NumFuncs()
|
||||||
|
for fnIdx := uint32(0); fnIdx < nf; fnIdx++ {
|
||||||
|
if err := pd.ReadFunc(fnIdx, &fd); err != nil {
|
||||||
|
return fmt.Errorf("reading meta-data file %s: %v",
|
||||||
|
p.MetaFile, err)
|
||||||
|
}
|
||||||
|
key := pkfunc{pk: pkIdx, fcn: fnIdx}
|
||||||
|
counters, haveCounters := pmm[key]
|
||||||
|
for i := 0; i < len(fd.Units); i++ {
|
||||||
|
u := fd.Units[i]
|
||||||
|
// Skip units with non-zero parent (no way to represent
|
||||||
|
// these in the existing format).
|
||||||
|
if u.Parent != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count := uint32(0)
|
||||||
|
if haveCounters {
|
||||||
|
count = counters[i]
|
||||||
|
}
|
||||||
|
ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pkfunc struct {
|
||||||
|
pk, fcn uint32
|
||||||
|
}
|
||||||
26
src/runtime/covercounter.go
Normal file
26
src/runtime/covercounter.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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 runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"internal/coverage/rtcov"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname runtime_coverage_getCovCounterList runtime/coverage.getCovCounterList
|
||||||
|
func runtime_coverage_getCovCounterList() []rtcov.CovCounterBlob {
|
||||||
|
res := []rtcov.CovCounterBlob{}
|
||||||
|
u32sz := unsafe.Sizeof(uint32(0))
|
||||||
|
for datap := &firstmoduledata; datap != nil; datap = datap.next {
|
||||||
|
if datap.covctrs == datap.ecovctrs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res = append(res, rtcov.CovCounterBlob{
|
||||||
|
Counters: (*uint32)(unsafe.Pointer(datap.covctrs)),
|
||||||
|
Len: uint64((datap.ecovctrs - datap.covctrs) / u32sz),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
@ -24,26 +24,6 @@ var covMeta struct {
|
||||||
hardCodedListNeedsUpdating bool
|
hardCodedListNeedsUpdating bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportErrorInHardcodedList(slot int32, pkgId int32) {
|
|
||||||
println("internal error in coverage meta-data tracking:")
|
|
||||||
println("encountered bad pkg ID ", pkgId, " at slot ", slot)
|
|
||||||
println("list of hard-coded runtime package IDs needs revising.")
|
|
||||||
println("[see the comment on the 'rtPkgs' var in ")
|
|
||||||
println(" <goroot>/src/internal/coverage/pkid.go]")
|
|
||||||
println("registered list:")
|
|
||||||
for k, b := range covMeta.metaList {
|
|
||||||
print("slot: ", k, " path='", b.PkgPath, "' ")
|
|
||||||
if b.PkgID != -1 {
|
|
||||||
print(" hard-coded id: ", b.PkgID)
|
|
||||||
}
|
|
||||||
println("")
|
|
||||||
}
|
|
||||||
println("remap table:")
|
|
||||||
for from, to := range covMeta.pkgMap {
|
|
||||||
println("from ", from, " to ", to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addCovMeta is invoked during package "init" functions by the
|
// addCovMeta is invoked during package "init" functions by the
|
||||||
// compiler when compiling for coverage instrumentation; here 'p' is a
|
// compiler when compiling for coverage instrumentation; here 'p' is a
|
||||||
// meta-data blob of length 'dlen' for the package in question, 'hash'
|
// meta-data blob of length 'dlen' for the package in question, 'hash'
|
||||||
|
|
@ -80,3 +60,13 @@ func addCovMeta(p unsafe.Pointer, dlen uint32, hash [16]byte, pkpath string, pki
|
||||||
// ID zero is reserved as invalid.
|
// ID zero is reserved as invalid.
|
||||||
return uint32(slot + 1)
|
return uint32(slot + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_coverage_getCovMetaList runtime/coverage.getCovMetaList
|
||||||
|
func runtime_coverage_getCovMetaList() []rtcov.CovMetaBlob {
|
||||||
|
return covMeta.metaList
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_coverage_getCovPkgMap runtime/coverage.getCovPkgMap
|
||||||
|
func runtime_coverage_getCovPkgMap() map[int]int {
|
||||||
|
return covMeta.pkgMap
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue