1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package cfile
6 7 import (
8 "fmt"
9 "internal/coverage"
10 "internal/coverage/rtcov"
11 "io"
12 "sync/atomic"
13 "unsafe"
14 )
15 16 // WriteMetaDir implements [runtime/coverage.WriteMetaDir].
17 func WriteMetaDir(dir []byte) error {
18 if !finalHashComputed {
19 return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
20 }
21 return emitMetaDataToDirectory(dir, rtcov.Meta.List)
22 }
23 24 // WriteMeta implements [runtime/coverage.WriteMeta].
25 func WriteMeta(w io.Writer) error {
26 if w == nil {
27 return fmt.Errorf("error: nil writer in WriteMeta")
28 }
29 if !finalHashComputed {
30 return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
31 }
32 ml := rtcov.Meta.List
33 return writeMetaData(w, ml, cmode, cgran, finalHash)
34 }
35 36 // WriteCountersDir implements [runtime/coverage.WriteCountersDir].
37 func WriteCountersDir(dir []byte) error {
38 if cmode != coverage.CtrModeAtomic {
39 return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
40 }
41 return emitCounterDataToDirectory(dir)
42 }
43 44 // WriteCounters implements [runtime/coverage.WriteCounters].
45 func WriteCounters(w io.Writer) error {
46 if w == nil {
47 return fmt.Errorf("error: nil writer in WriteCounters")
48 }
49 if cmode != coverage.CtrModeAtomic {
50 return fmt.Errorf("WriteCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
51 }
52 // Ask the runtime for the list of coverage counter symbols.
53 cl := getCovCounterList()
54 if len(cl) == 0 {
55 return fmt.Errorf("program not built with -cover")
56 }
57 if !finalHashComputed {
58 return fmt.Errorf("meta-data not written yet, unable to write counter data")
59 }
60 61 pm := rtcov.Meta.PkgMap
62 s := &emitState{
63 counterlist: cl,
64 pkgmap: pm,
65 }
66 return s.emitCounterDataToWriter(w)
67 }
68 69 // ClearCounters implements [runtime/coverage.ClearCounters].
70 func ClearCounters() error {
71 cl := getCovCounterList()
72 if len(cl) == 0 {
73 return fmt.Errorf("program not built with -cover")
74 }
75 if cmode != coverage.CtrModeAtomic {
76 return fmt.Errorf("ClearCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
77 }
78 79 // Implementation note: this function would be faster and simpler
80 // if we could just zero out the entire counter array, but for the
81 // moment we go through and zero out just the slots in the array
82 // corresponding to the counter values. We do this to avoid the
83 // following bad scenario: suppose that a user builds their Go
84 // program with "-cover", and that program has a function (call it
85 // main.XYZ) that invokes ClearCounters:
86 //
87 // func XYZ() {
88 // ... do some stuff ...
89 // coverage.ClearCounters()
90 // if someCondition { <<--- HERE
91 // ...
92 // }
93 // }
94 //
95 // At the point where ClearCounters executes, main.XYZ has not yet
96 // finished running, thus as soon as the call returns the line
97 // marked "HERE" above will trigger the writing of a non-zero
98 // value into main.XYZ's counter slab. However since we've just
99 // finished clearing the entire counter segment, we will have lost
100 // the values in the prolog portion of main.XYZ's counter slab
101 // (nctrs, pkgid, funcid). This means that later on at the end of
102 // program execution as we walk through the entire counter array
103 // for the program looking for executed functions, we'll zoom past
104 // main.XYZ's prolog (which was zero'd) and hit the non-zero
105 // counter value corresponding to the "HERE" block, which will
106 // then be interpreted as the start of another live function.
107 // Things will go downhill from there.
108 //
109 // This same scenario is also a potential risk if the program is
110 // running on an architecture that permits reordering of
111 // writes/stores, since the inconsistency described above could
112 // arise here. Example scenario:
113 //
114 // func ABC() {
115 // ... // prolog
116 // if alwaysTrue() {
117 // XYZ() // counter update here
118 // }
119 // }
120 //
121 // In the instrumented version of ABC, the prolog of the function
122 // will contain a series of stores to the initial portion of the
123 // counter array to write number-of-counters, pkgid, funcid. Later
124 // in the function there is also a store to increment a counter
125 // for the block containing the call to XYZ(). If the CPU is
126 // allowed to reorder stores and decides to issue the XYZ store
127 // before the prolog stores, this could be observable as an
128 // inconsistency similar to the one above. Hence the requirement
129 // for atomic counter mode: according to package atomic docs,
130 // "...operations that happen in a specific order on one thread,
131 // will always be observed to happen in exactly that order by
132 // another thread". Thus we can be sure that there will be no
133 // inconsistency when reading the counter array from the thread
134 // running ClearCounters.
135 136 for _, c := range cl {
137 sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), int(c.Len))
138 for i := 0; i < len(sd); i++ {
139 // Skip ahead until the next non-zero value.
140 sdi := sd[i].Load()
141 if sdi == 0 {
142 continue
143 }
144 // We found a function that was executed; clear its counters.
145 nCtrs := sdi
146 for j := 0; j < int(nCtrs); j++ {
147 sd[i+coverage.FirstCtrOffset+j].Store(0)
148 }
149 // Move to next function.
150 i += coverage.FirstCtrOffset + int(nCtrs) - 1
151 }
152 }
153 return nil
154 }
155