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