Source file src/internal/coverage/cfile/apis.go
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 string) 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 string) 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