Skip to content

Commit 61e5ea4

Browse files
committed
runtime/coverage: restrict use of all counter-related APIs to atomic mode
The existing runtime/coverage API set includes a "ClearCounters()" function that zeros out the counter values in a running process so as enable capturing of a coverage profile from a specific execution time segment. Calling this function is only permitted if the program is built with "-covermode=atomic", due (in part) to concerns about processors with relaxed memory models in which normal stores can be reordered. In the bug in question, a test that stresses a different set of counter-related APIs was hitting an invalid counter segment when running on a machine (ppc64) which does indeed have a relaxed memory consistency model. From a post-mortem examination of the counter array for the harness from the ppc64 test run, it was clear that the thread reading values from the counter array was seeing the sort of inconsistency that could result from stores being reordered (specifically the prolog "packageID" and "number-of-counters" stores). To preclude the possibility of future similar problems, this patch extends the "atomic mode only" restriction from ClearCounters to the other APIs that deal with counters (WriteCounters, WriteCountersDir). Fixes #56197. Change-Id: Idb85d67a84d69ead508e0902ab46ab4dc82af466 Reviewed-on: https://go-review.googlesource.com/c/go/+/463695 Reviewed-by: David Chase <drchase@google.com> Run-TryBot: Than McIntosh <thanm@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent d20e688 commit 61e5ea4

File tree

2 files changed

+131
-78
lines changed

2 files changed

+131
-78
lines changed

src/runtime/coverage/apis.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ func WriteMeta(w io.Writer) error {
5050
// counter data written will be a snapshot taken at the point of the
5151
// call.
5252
func WriteCountersDir(dir string) error {
53+
if cmode != coverage.CtrModeAtomic {
54+
return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
55+
}
5356
return emitCounterDataToDirectory(dir)
5457
}
5558

56-
// WriteCounters writes coverage counter-data content for
57-
// the currently running program to the writer 'w'. An error will be
59+
// WriteCounters writes coverage counter-data content for the
60+
// currently running program to the writer 'w'. An error will be
5861
// returned if the operation can't be completed successfully (for
5962
// example, if the currently running program was not built with
6063
// "-cover", or if a write fails). The counter data written will be a
@@ -63,6 +66,9 @@ func WriteCounters(w io.Writer) error {
6366
if w == nil {
6467
return fmt.Errorf("error: nil writer in WriteCounters")
6568
}
69+
if cmode != coverage.CtrModeAtomic {
70+
return fmt.Errorf("WriteCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
71+
}
6672
// Ask the runtime for the list of coverage counter symbols.
6773
cl := getCovCounterList()
6874
if len(cl) == 0 {
@@ -92,7 +98,7 @@ func ClearCounters() error {
9298
return fmt.Errorf("program not built with -cover")
9399
}
94100
if cmode != coverage.CtrModeAtomic {
95-
return fmt.Errorf("ClearCounters invoked for program build with -covermode=%s (please use -covermode=atomic)", cmode.String())
101+
return fmt.Errorf("ClearCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
96102
}
97103

98104
// Implementation note: this function would be faster and simpler

src/runtime/coverage/emitdata_test.go

Lines changed: 122 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -36,43 +36,60 @@ func TestCoverageApis(t *testing.T) {
3636
mkdir(t, dir)
3737
}
3838

39-
// Build harness.
40-
bdir := mkdir(t, filepath.Join(dir, "build"))
41-
hargs := []string{"-cover", "-coverpkg=all"}
42-
if testing.CoverMode() != "" {
43-
hargs = append(hargs, "-covermode="+testing.CoverMode())
39+
// Build harness. We need two copies of the harness, one built
40+
// with -covermode=atomic and one built non-atomic.
41+
bdir1 := mkdir(t, filepath.Join(dir, "build1"))
42+
hargs1 := []string{"-covermode=atomic", "-coverpkg=all"}
43+
atomicHarnessPath := buildHarness(t, bdir1, hargs1)
44+
nonAtomicMode := testing.CoverMode()
45+
if testing.CoverMode() == "atomic" {
46+
nonAtomicMode = "set"
4447
}
45-
harnessPath := buildHarness(t, bdir, hargs)
48+
bdir2 := mkdir(t, filepath.Join(dir, "build2"))
49+
hargs2 := []string{"-coverpkg=all", "-covermode=" + nonAtomicMode}
50+
nonAtomicHarnessPath := buildHarness(t, bdir2, hargs2)
4651

47-
t.Logf("harness path is %s", harnessPath)
52+
t.Logf("atomic harness path is %s", atomicHarnessPath)
53+
t.Logf("non-atomic harness path is %s", nonAtomicHarnessPath)
4854

4955
// Sub-tests for each API we want to inspect, plus
5056
// extras for error testing.
5157
t.Run("emitToDir", func(t *testing.T) {
5258
t.Parallel()
53-
testEmitToDir(t, harnessPath, dir)
59+
testEmitToDir(t, atomicHarnessPath, dir)
5460
})
5561
t.Run("emitToWriter", func(t *testing.T) {
5662
t.Parallel()
57-
testEmitToWriter(t, harnessPath, dir)
63+
testEmitToWriter(t, atomicHarnessPath, dir)
5864
})
5965
t.Run("emitToNonexistentDir", func(t *testing.T) {
6066
t.Parallel()
61-
testEmitToNonexistentDir(t, harnessPath, dir)
67+
testEmitToNonexistentDir(t, atomicHarnessPath, dir)
6268
})
6369
t.Run("emitToNilWriter", func(t *testing.T) {
6470
t.Parallel()
65-
testEmitToNilWriter(t, harnessPath, dir)
71+
testEmitToNilWriter(t, atomicHarnessPath, dir)
6672
})
6773
t.Run("emitToFailingWriter", func(t *testing.T) {
6874
t.Parallel()
69-
testEmitToFailingWriter(t, harnessPath, dir)
75+
testEmitToFailingWriter(t, atomicHarnessPath, dir)
7076
})
7177
t.Run("emitWithCounterClear", func(t *testing.T) {
7278
t.Parallel()
73-
testEmitWithCounterClear(t, harnessPath, dir)
79+
testEmitWithCounterClear(t, atomicHarnessPath, dir)
80+
})
81+
t.Run("emitToDirNonAtomic", func(t *testing.T) {
82+
t.Parallel()
83+
testEmitToDirNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
84+
})
85+
t.Run("emitToWriterNonAtomic", func(t *testing.T) {
86+
t.Parallel()
87+
testEmitToWriterNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
88+
})
89+
t.Run("emitWithCounterClearNonAtomic", func(t *testing.T) {
90+
t.Parallel()
91+
testEmitWithCounterClearNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
7492
})
75-
7693
}
7794

7895
// upmergeCoverData helps improve coverage data for this package
@@ -82,8 +99,8 @@ func TestCoverageApis(t *testing.T) {
8299
// run from the "harness.exe" runs we've just done. We can accomplish
83100
// this by doing a merge from the harness gocoverdir's to the test
84101
// gocoverdir.
85-
func upmergeCoverData(t *testing.T, gocoverdir string) {
86-
if testing.CoverMode() == "" {
102+
func upmergeCoverData(t *testing.T, gocoverdir string, mode string) {
103+
if testing.CoverMode() != mode {
87104
return
88105
}
89106
testGoCoverDir := os.Getenv("GOCOVERDIR")
@@ -243,8 +260,8 @@ func testEmitToDir(t *testing.T, harnessPath string, dir string) {
243260
if cdc != wantcf {
244261
t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc)
245262
}
246-
upmergeCoverData(t, edir)
247-
upmergeCoverData(t, rdir)
263+
upmergeCoverData(t, edir, "atomic")
264+
upmergeCoverData(t, rdir, "atomic")
248265
})
249266
}
250267

@@ -262,8 +279,8 @@ func testEmitToWriter(t *testing.T, harnessPath string, dir string) {
262279
if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
263280
t.Errorf("coverage data from %q output match failed: %s", tp, msg)
264281
}
265-
upmergeCoverData(t, edir)
266-
upmergeCoverData(t, rdir)
282+
upmergeCoverData(t, edir, "atomic")
283+
upmergeCoverData(t, rdir, "atomic")
267284
})
268285
}
269286

@@ -276,8 +293,8 @@ func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) {
276293
t.Logf("%s", output)
277294
t.Fatalf("running 'harness -tp %s': %v", tp, err)
278295
}
279-
upmergeCoverData(t, edir)
280-
upmergeCoverData(t, rdir)
296+
upmergeCoverData(t, edir, "atomic")
297+
upmergeCoverData(t, rdir, "atomic")
281298
})
282299
}
283300

@@ -298,8 +315,8 @@ func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) {
298315
t.Logf("%s", output)
299316
t.Fatalf("running 'harness -tp %s': %v", tp, err)
300317
}
301-
upmergeCoverData(t, edir)
302-
upmergeCoverData(t, rdir)
318+
upmergeCoverData(t, edir, "atomic")
319+
upmergeCoverData(t, rdir, "atomic")
303320
})
304321
}
305322

@@ -312,8 +329,8 @@ func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) {
312329
t.Logf("%s", output)
313330
t.Fatalf("running 'harness -tp %s': %v", tp, err)
314331
}
315-
upmergeCoverData(t, edir)
316-
upmergeCoverData(t, rdir)
332+
upmergeCoverData(t, edir, "atomic")
333+
upmergeCoverData(t, rdir, "atomic")
317334
})
318335
}
319336

@@ -326,73 +343,103 @@ func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) {
326343
t.Logf("%s", output)
327344
t.Fatalf("running 'harness -tp %s': %v", tp, err)
328345
}
329-
upmergeCoverData(t, edir)
330-
upmergeCoverData(t, rdir)
346+
upmergeCoverData(t, edir, "atomic")
347+
upmergeCoverData(t, rdir, "atomic")
331348
})
332349
}
333350

334351
func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) {
335-
// Ensure that we have two versions of the harness: one built with
336-
// -covermode=atomic and one built with -covermode=set (we need
337-
// both modes to test all of the functionality).
338-
var nonatomicHarnessPath, atomicHarnessPath string
339-
if testing.CoverMode() != "atomic" {
340-
nonatomicHarnessPath = harnessPath
341-
bdir2 := mkdir(t, filepath.Join(dir, "build2"))
342-
hargs := []string{"-covermode=atomic", "-coverpkg=all"}
343-
atomicHarnessPath = buildHarness(t, bdir2, hargs)
344-
} else {
345-
atomicHarnessPath = harnessPath
346-
mode := "set"
347-
if testing.CoverMode() != "" && testing.CoverMode() != "atomic" {
348-
mode = testing.CoverMode()
349-
}
350-
// Build a special nonatomic covermode version of the harness
351-
// (we need both modes to test all of the functionality).
352-
bdir2 := mkdir(t, filepath.Join(dir, "build2"))
353-
hargs := []string{"-covermode=" + mode, "-coverpkg=all"}
354-
nonatomicHarnessPath = buildHarness(t, bdir2, hargs)
355-
}
356-
357352
withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
358-
// First a run with the nonatomic harness path, which we
359-
// expect to fail.
360353
tp := "emitWithCounterClear"
361-
rdir1, edir1 := mktestdirs(t, tag, tp+"1", dir)
362-
output, err := runHarness(t, nonatomicHarnessPath, tp,
363-
setGoCoverDir, rdir1, edir1)
364-
if err == nil {
365-
t.Logf("%s", output)
366-
t.Fatalf("running '%s -tp %s': unexpected success",
367-
nonatomicHarnessPath, tp)
368-
}
369-
370-
// Next a run with the atomic harness path, which we
371-
// expect to succeed.
372-
rdir2, edir2 := mktestdirs(t, tag, tp+"2", dir)
373-
output, err = runHarness(t, atomicHarnessPath, tp,
374-
setGoCoverDir, rdir2, edir2)
354+
rdir, edir := mktestdirs(t, tag, tp, dir)
355+
output, err := runHarness(t, harnessPath, tp,
356+
setGoCoverDir, rdir, edir)
375357
if err != nil {
376358
t.Logf("%s", output)
377359
t.Fatalf("running 'harness -tp %s': %v", tp, err)
378360
}
379361
want := []string{tp, "postClear"}
380362
avoid := []string{"preClear", "main", "final"}
381-
if msg := testForSpecificFunctions(t, edir2, want, avoid); msg != "" {
363+
if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
382364
t.Logf("%s", output)
383365
t.Errorf("coverage data from %q output match failed: %s", tp, msg)
384366
}
385-
386-
if testing.CoverMode() == "atomic" {
387-
upmergeCoverData(t, edir2)
388-
upmergeCoverData(t, rdir2)
389-
} else {
390-
upmergeCoverData(t, edir1)
391-
upmergeCoverData(t, rdir1)
392-
}
367+
upmergeCoverData(t, edir, "atomic")
368+
upmergeCoverData(t, rdir, "atomic")
393369
})
394370
}
395371

372+
func testEmitToDirNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
373+
tp := "emitToDir"
374+
tag := "nonatomdir"
375+
rdir, edir := mktestdirs(t, tag, tp, dir)
376+
output, err := runHarness(t, harnessPath, tp,
377+
true, rdir, edir)
378+
379+
// We expect an error here.
380+
if err == nil {
381+
t.Logf("%s", output)
382+
t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
383+
}
384+
385+
got := strings.TrimSpace(string(output))
386+
want := "WriteCountersDir invoked for program built"
387+
if !strings.Contains(got, want) {
388+
t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
389+
tp, got, want)
390+
}
391+
upmergeCoverData(t, edir, naMode)
392+
upmergeCoverData(t, rdir, naMode)
393+
}
394+
395+
func testEmitToWriterNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
396+
tp := "emitToWriter"
397+
tag := "nonatomw"
398+
rdir, edir := mktestdirs(t, tag, tp, dir)
399+
output, err := runHarness(t, harnessPath, tp,
400+
true, rdir, edir)
401+
402+
// We expect an error here.
403+
if err == nil {
404+
t.Logf("%s", output)
405+
t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
406+
}
407+
408+
got := strings.TrimSpace(string(output))
409+
want := "WriteCounters invoked for program built"
410+
if !strings.Contains(got, want) {
411+
t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
412+
tp, got, want)
413+
}
414+
415+
upmergeCoverData(t, edir, naMode)
416+
upmergeCoverData(t, rdir, naMode)
417+
}
418+
419+
func testEmitWithCounterClearNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
420+
tp := "emitWithCounterClear"
421+
tag := "cclear"
422+
rdir, edir := mktestdirs(t, tag, tp, dir)
423+
output, err := runHarness(t, harnessPath, tp,
424+
true, rdir, edir)
425+
426+
// We expect an error here.
427+
if err == nil {
428+
t.Logf("%s", output)
429+
t.Fatalf("running 'harness -tp %s' nonatomic: did not get expected error", tp)
430+
}
431+
432+
got := strings.TrimSpace(string(output))
433+
want := "ClearCounters invoked for program built"
434+
if !strings.Contains(got, want) {
435+
t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
436+
tp, got, want)
437+
}
438+
439+
upmergeCoverData(t, edir, naMode)
440+
upmergeCoverData(t, rdir, naMode)
441+
}
442+
396443
func TestApisOnNocoverBinary(t *testing.T) {
397444
if testing.Short() {
398445
t.Skipf("skipping test: too long for short mode")

0 commit comments

Comments
 (0)