diff options
Diffstat (limited to 'src/runtime/coverage/apis.go')
-rw-r--r-- | src/runtime/coverage/apis.go | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/src/runtime/coverage/apis.go b/src/runtime/coverage/apis.go new file mode 100644 index 0000000..15ba04a --- /dev/null +++ b/src/runtime/coverage/apis.go @@ -0,0 +1,178 @@ +// 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 coverage + +import ( + "fmt" + "internal/coverage" + "io" + "sync/atomic" + "unsafe" +) + +// WriteMetaDir writes a coverage meta-data file for the currently +// running program to the directory specified in 'dir'. An error will +// be returned if the operation can't be completed successfully (for +// example, if the currently running program was not built with +// "-cover", or if the directory does not exist). +func WriteMetaDir(dir string) error { + if !finalHashComputed { + return fmt.Errorf("error: no meta-data available (binary not built with -cover?)") + } + return emitMetaDataToDirectory(dir, getCovMetaList()) +} + +// WriteMeta writes the meta-data content (the payload that would +// normally be emitted to a meta-data file) for the currently running +// program to the writer 'w'. An error will be returned if the +// operation can't be completed successfully (for example, if the +// currently running program was not built with "-cover", or if a +// write fails). +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 := getCovMetaList() + return writeMetaData(w, ml, cmode, cgran, finalHash) +} + +// WriteCountersDir writes a coverage counter-data file for the +// currently running program to the directory specified in 'dir'. An +// error will be returned if the operation can't be completed +// successfully (for example, if the currently running program was not +// built with "-cover", or if the directory does not exist). The +// counter data written will be a snapshot taken at the point of the +// call. +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 writes coverage counter-data content for the +// currently running program to the writer 'w'. An error will be +// returned if the operation can't be completed successfully (for +// example, if the currently running program was not built with +// "-cover", or if a write fails). The counter data written will be a +// snapshot taken at the point of the invocation. +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 := getCovPkgMap() + s := &emitState{ + counterlist: cl, + pkgmap: pm, + } + return s.emitCounterDataToWriter(w) +} + +// ClearCounters clears/resets all coverage counter variables in the +// currently running program. It returns an error if the program in +// question was not built with the "-cover" flag. Clearing of coverage +// counters is also not supported for programs not using atomic +// counter mode (see more detailed comments below for the rationale +// here). +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 +} |