Skip to content

Commit 39957b5

Browse files
committed
coverage: fix count vs emit discrepancy in coverage counter data writing
This patch revises the way coverage counter data writing takes place to avoid problems where useful counter data (for user-written functions) is skipped in favor of counter data from stdlib functions that are executed "late in the game", during the counter writing process itself. Reading counter values from a running "--coverpkg=all" program is an inherently racy operation; while the the code that scans the coverage counter segment is reading values, the program is still executing, potentially updating those values, and updates can include execution of previously un-executed functions. The existing counter data writing code was using a two-pass model (initial sweep over the counter segment to count live functions, second sweep to actually write data), and wasn't properly accounting for the fact that the second pass could see more functions than the first. In the bug in question, the first pass discovered an initial set of 1240 functions, but by the time the second pass kicked in, several additional new functions were also live. The second pass scanned the counter segment again to write out exactly 1240 functions, but since some of the counters for the newly executed functions were earlier in the segment (due to linker layout quirks) than the user's selected function, the sweep terminated before writing out counters for the function of interest. The fix rewrites the counter data file encoder to make a single sweep over the counter segment instead of using a two-pass scheme. Fixes #59563. Change-Id: I5e908e226bb224adb90a2fb783013e52deb341da Reviewed-on: https://go-review.googlesource.com/c/go/+/484535 Reviewed-by: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Than McIntosh <[email protected]>
1 parent 7b89531 commit 39957b5

File tree

7 files changed

+924
-92
lines changed

7 files changed

+924
-92
lines changed

src/cmd/covdata/metamerge.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -273,17 +273,6 @@ func (mm *metaMerge) emitCounters(outdir string, metaHash [16]byte) {
273273
mm.astate = &argstate{}
274274
}
275275

276-
// NumFuncs is used while writing the counter data files; it
277-
// implements the 'NumFuncs' method required by the interface
278-
// internal/coverage/encodecounter/CounterVisitor.
279-
func (mm *metaMerge) NumFuncs() (int, error) {
280-
rval := 0
281-
for _, p := range mm.pkgs {
282-
rval += len(p.ctab)
283-
}
284-
return rval, nil
285-
}
286-
287276
// VisitFuncs is used while writing the counter data files; it
288277
// implements the 'VisitFuncs' method required by the interface
289278
// internal/coverage/encodecounter/CounterVisitor.

src/internal/coverage/encodecounter/encode.go

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ import (
2626
type CoverageDataWriter struct {
2727
stab *stringtab.Writer
2828
w *bufio.Writer
29+
csh coverage.CounterSegmentHeader
2930
tmp []byte
31+
nfuncs uint64
3032
cflavor coverage.CounterFlavor
3133
segs uint32
3234
debug bool
@@ -47,13 +49,10 @@ func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDa
4749

4850
// CounterVisitor describes a helper object used during counter file
4951
// writing; when writing counter data files, clients pass a
50-
// CounterVisitor to the write/emit routines. The writers will then
51-
// first invoke the visitor's NumFuncs() method to find out how many
52-
// function's worth of data to write, then it will invoke VisitFuncs.
53-
// The expectation is that the VisitFuncs method will then invoke the
54-
// callback "f" with data for each function to emit to the file.
52+
// CounterVisitor to the write/emit routines, then the expectation is
53+
// that the VisitFuncs method will then invoke the callback "f" with
54+
// data for each function to emit to the file.
5555
type CounterVisitor interface {
56-
NumFuncs() (int, error)
5756
VisitFuncs(f CounterVisitorFn) error
5857
}
5958

@@ -86,23 +85,35 @@ func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error {
8685
return nil
8786
}
8887

89-
func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, visitor CounterVisitor) error {
90-
var csh coverage.CounterSegmentHeader
91-
if nf, err := visitor.NumFuncs(); err != nil {
88+
func (cfw *CoverageDataWriter) patchSegmentHeader(ws *slicewriter.WriteSeeker) error {
89+
if _, err := ws.Seek(0, io.SeekStart); err != nil {
90+
return fmt.Errorf("error seeking in patchSegmentHeader: %v", err)
91+
}
92+
cfw.csh.FcnEntries = cfw.nfuncs
93+
cfw.nfuncs = 0
94+
if cfw.debug {
95+
fmt.Fprintf(os.Stderr, "=-= writing counter segment header: %+v", cfw.csh)
96+
}
97+
if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil {
9298
return err
93-
} else {
94-
csh.FcnEntries = uint64(nf)
9599
}
100+
return nil
101+
}
102+
103+
func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, ws *slicewriter.WriteSeeker) error {
104+
if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil {
105+
return err
106+
}
107+
hdrsz := uint32(len(ws.BytesWritten()))
96108

97109
// Write string table and args to a byte slice (since we need
98110
// to capture offsets at various points), then emit the slice
99111
// once we are done.
100112
cfw.stab.Freeze()
101-
ws := &slicewriter.WriteSeeker{}
102113
if err := cfw.stab.Write(ws); err != nil {
103114
return err
104115
}
105-
csh.StrTabLen = uint32(len(ws.BytesWritten()))
116+
cfw.csh.StrTabLen = uint32(len(ws.BytesWritten())) - hdrsz
106117

107118
akeys := make([]string, 0, len(args))
108119
for k := range args {
@@ -138,21 +149,8 @@ func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, visi
138149
if err := padToFourByteBoundary(ws); err != nil {
139150
return err
140151
}
141-
csh.ArgsLen = uint32(len(ws.BytesWritten())) - csh.StrTabLen
142-
143-
if cfw.debug {
144-
fmt.Fprintf(os.Stderr, "=-= counter segment header: %+v", csh)
145-
fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n",
146-
csh.FcnEntries, csh.StrTabLen, csh.ArgsLen)
147-
}
152+
cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz)
148153

149-
// At this point we can now do the actual write.
150-
if err := binary.Write(cfw.w, binary.LittleEndian, csh); err != nil {
151-
return err
152-
}
153-
if err := cfw.writeBytes(ws.BytesWritten()); err != nil {
154-
return err
155-
}
156154
return nil
157155
}
158156

@@ -169,10 +167,18 @@ func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor Cou
169167
cfw.stab.Lookup(v)
170168
}
171169

172-
if err = cfw.writeSegmentPreamble(args, visitor); err != nil {
170+
var swws slicewriter.WriteSeeker
171+
ws := &swws
172+
if err = cfw.writeSegmentPreamble(args, ws); err != nil {
173173
return err
174174
}
175-
if err = cfw.writeCounters(visitor); err != nil {
175+
if err = cfw.writeCounters(visitor, ws); err != nil {
176+
return err
177+
}
178+
if err = cfw.patchSegmentHeader(ws); err != nil {
179+
return err
180+
}
181+
if err := cfw.writeBytes(ws.BytesWritten()); err != nil {
176182
return err
177183
}
178184
if err = cfw.writeFooter(); err != nil {
@@ -214,7 +220,7 @@ func (cfw *CoverageDataWriter) writeBytes(b []byte) error {
214220
return nil
215221
}
216222

217-
func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor) error {
223+
func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor, ws *slicewriter.WriteSeeker) error {
218224
// Notes:
219225
// - this version writes everything little-endian, which means
220226
// a call is needed to encode every value (expensive)
@@ -237,7 +243,7 @@ func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor) error {
237243
} else {
238244
panic("internal error: bad counter flavor")
239245
}
240-
if sz, err := cfw.w.Write(buf); err != nil {
246+
if sz, err := ws.Write(buf); err != nil {
241247
return err
242248
} else if sz != towr {
243249
return fmt.Errorf("writing counters: short write")
@@ -247,6 +253,7 @@ func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor) error {
247253

248254
// Write out entries for each live function.
249255
emitter := func(pkid uint32, funcid uint32, counters []uint32) error {
256+
cfw.nfuncs++
250257
if err := wrval(uint32(len(counters))); err != nil {
251258
return err
252259
}

src/internal/coverage/test/counter_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ type ctrVis struct {
1919
funcs []decodecounter.FuncPayload
2020
}
2121

22-
func (v *ctrVis) NumFuncs() (int, error) {
23-
return len(v.funcs), nil
24-
}
25-
2622
func (v *ctrVis) VisitFuncs(f encodecounter.CounterVisitorFn) error {
2723
for _, fn := range v.funcs {
2824
if err := f(fn.PkgIdx, fn.FuncIdx, fn.Counters); err != nil {

src/runtime/coverage/emit.go

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -463,52 +463,6 @@ func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.Cou
463463
return mfw.Write(finalHash, blobs, cmode, gran)
464464
}
465465

466-
func (s *emitState) NumFuncs() (int, error) {
467-
var sd []atomic.Uint32
468-
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
469-
470-
totalFuncs := 0
471-
for _, c := range s.counterlist {
472-
bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
473-
bufHdr.Len = int(c.Len)
474-
bufHdr.Cap = int(c.Len)
475-
for i := 0; i < len(sd); i++ {
476-
// Skip ahead until the next non-zero value.
477-
sdi := sd[i].Load()
478-
if sdi == 0 {
479-
continue
480-
}
481-
482-
// We found a function that was executed.
483-
nCtrs := sdi
484-
485-
// Check to make sure that we have at least one live
486-
// counter. See the implementation note in ClearCoverageCounters
487-
// for a description of why this is needed.
488-
isLive := false
489-
st := i + coverage.FirstCtrOffset
490-
counters := sd[st : st+int(nCtrs)]
491-
for i := 0; i < len(counters); i++ {
492-
if counters[i].Load() != 0 {
493-
isLive = true
494-
break
495-
}
496-
}
497-
if !isLive {
498-
// Skip this function.
499-
i += coverage.FirstCtrOffset + int(nCtrs) - 1
500-
continue
501-
}
502-
503-
totalFuncs++
504-
505-
// Move to the next function.
506-
i += coverage.FirstCtrOffset + int(nCtrs) - 1
507-
}
508-
}
509-
return totalFuncs, nil
510-
}
511-
512466
func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
513467
var sd []atomic.Uint32
514468
var tcounters []uint32

src/runtime/coverage/emitdata_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,3 +496,52 @@ func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
496496
}
497497
}
498498
}
499+
500+
func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
501+
if testing.Short() {
502+
t.Skipf("skipping test: too long for short mode")
503+
}
504+
testenv.MustHaveGoRun(t)
505+
506+
tmpdir := t.TempDir()
507+
ppath := filepath.Join(tmpdir, "foo.cov")
508+
509+
cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverpkg=all", "-coverprofile="+ppath)
510+
cmd.Dir = filepath.Join("testdata", "issue59563")
511+
b, err := cmd.CombinedOutput()
512+
if err != nil {
513+
t.Fatalf("go test -cover failed: %v", err)
514+
}
515+
516+
cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath)
517+
b, err = cmd.CombinedOutput()
518+
if err != nil {
519+
t.Fatalf("go tool cover -func failed: %v", err)
520+
}
521+
522+
lines := strings.Split(string(b), "\n")
523+
nfound := 0
524+
bad := false
525+
for _, line := range lines {
526+
f := strings.Fields(line)
527+
if len(f) == 0 {
528+
continue
529+
}
530+
if !strings.HasPrefix(f[0], "runtime/coverage/testdata/issue59563/repro.go") {
531+
continue
532+
}
533+
nfound++
534+
want := "100.0%"
535+
if f[len(f)-1] != want {
536+
t.Errorf("wanted %s got: %q\n", want, line)
537+
bad = true
538+
}
539+
}
540+
if nfound != 2 {
541+
t.Errorf("wanted 2 found, got %d\n", nfound)
542+
bad = true
543+
}
544+
if bad {
545+
t.Logf("func output:\n%s\n", string(b))
546+
}
547+
}

0 commit comments

Comments
 (0)