// 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 implements management of coverage files. // It provides functionality exported in runtime/coverage as well as // additional functionality used directly by package testing // through testing/internal/testdeps.
package cfile import ( ) // This file contains functions that support the writing of data files // emitted at the end of code coverage testing runs, from instrumented // executables. // getCovCounterList returns a list of counter-data blobs registered // for the currently executing instrumented program. It is defined in the // runtime. // //go:linkname getCovCounterList func getCovCounterList() []rtcov.CovCounterBlob // emitState holds useful state information during the emit process. // // When an instrumented program finishes execution and starts the // process of writing out coverage data, it's possible that an // existing meta-data file already exists in the output directory. In // this case openOutputFiles() below will leave the 'mf' field below // as nil. If a new meta-data file is needed, field 'mfname' will be // the final desired path of the meta file, 'mftmp' will be a // temporary file, and 'mf' will be an open os.File pointer for // 'mftmp'. The meta-data file payload will be written to 'mf', the // temp file will be then closed and renamed (from 'mftmp' to // 'mfname'), so as to insure that the meta-data file is created // atomically; we want this so that things work smoothly in cases // where there are several instances of a given instrumented program // all terminating at the same time and trying to create meta-data // files simultaneously. // // For counter data files there is less chance of a collision, hence // the openOutputFiles() stores the counter data file in 'cfname' and // then places the *io.File into 'cf'. type emitState struct { mfname string // path of final meta-data output file mftmp string // path to meta-data temp file (if needed) mf *os.File // open os.File for meta-data temp file cfname string // path of final counter data file cftmp string // path to counter data temp file cf *os.File // open os.File for counter data file outdir string // output directory // List of meta-data symbols obtained from the runtime metalist []rtcov.CovMetaBlob // List of counter-data symbols obtained from the runtime counterlist []rtcov.CovCounterBlob // Table to use for remapping hard-coded pkg ids. pkgmap map[int]int // emit debug trace output debug bool } var ( // finalHash is computed at init time from the list of meta-data // symbols registered during init. It is used both for writing the // meta-data file and counter-data files. finalHash [16]byte // Set to true when we've computed finalHash + finalMetaLen. finalHashComputed bool // Total meta-data length. finalMetaLen uint64 // Records whether we've already attempted to write meta-data. metaDataEmitAttempted bool // Counter mode for this instrumented program run. cmode coverage.CounterMode // Counter granularity for this instrumented program run. cgran coverage.CounterGranularity // Cached value of GOCOVERDIR environment variable. goCoverDir string // Copy of os.Args made at init time, converted into map format. capturedOsArgs map[string]string // Flag used in tests to signal that coverage data already written. covProfileAlreadyEmitted bool ) // fileType is used to select between counter-data files and // meta-data files. type fileType int const ( noFile = 1 << iota metaDataFile counterDataFile ) // emitMetaData emits the meta-data output file for this coverage run. // This entry point is intended to be invoked by the compiler from // an instrumented program's main package init func. func emitMetaData() { if covProfileAlreadyEmitted { return } , := prepareForMetaEmit() if != nil { fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", ) if os.Getenv("GOCOVERDEBUG") != "" { panic("meta-data write failure") } } if len() == 0 { fmt.Fprintf(os.Stderr, "program not built with -cover\n") return } goCoverDir = os.Getenv("GOCOVERDIR") if goCoverDir == "" { fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n") return } if := emitMetaDataToDirectory(goCoverDir, ); != nil { fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", ) if os.Getenv("GOCOVERDEBUG") != "" { panic("meta-data write failure") } } } func modeClash( coverage.CounterMode) bool { if == coverage.CtrModeRegOnly || == coverage.CtrModeTestMain { return false } if cmode == coverage.CtrModeInvalid { cmode = return false } return cmode != } func granClash( coverage.CounterGranularity) bool { if cgran == coverage.CtrGranularityInvalid { cgran = return false } return cgran != } // prepareForMetaEmit performs preparatory steps needed prior to // emitting a meta-data file, notably computing a final hash of // all meta-data blobs and capturing os args. func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) { // Ask the runtime for the list of coverage meta-data symbols. := rtcov.Meta.List // In the normal case (go build -o prog.exe ... ; ./prog.exe) // len(ml) will always be non-zero, but we check here since at // some point this function will be reachable via user-callable // APIs (for example, to write out coverage data from a server // program that doesn't ever call os.Exit). if len() == 0 { return nil, nil } := &emitState{ metalist: , debug: os.Getenv("GOCOVERDEBUG") != "", } // Capture os.Args() now so as to avoid issues if args // are rewritten during program execution. capturedOsArgs = captureOsArgs() if .debug { fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR")) fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n") for , := range { fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", , .PkgPath) if .PkgID != -1 { fmt.Fprintf(os.Stderr, " hcid: %d", .PkgID) } fmt.Fprintf(os.Stderr, "\n") } := rtcov.Meta.PkgMap fmt.Fprintf(os.Stderr, "=+= remap table:\n") for , := range { fmt.Fprintf(os.Stderr, "=+= from %d to %d\n", uint32(), uint32()) } } := md5.New() := uint64(unsafe.Sizeof(coverage.MetaFileHeader{})) for , := range { if , := .Write(.Hash[:]); != nil { return nil, } += uint64(.Len) := coverage.CounterMode(.CounterMode) if modeClash() { return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", [0].PkgPath, cmode, .PkgPath, ) } := coverage.CounterGranularity(.CounterGranularity) if granClash() { return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", [0].PkgPath, cgran, .PkgPath, ) } } // Hash mode and granularity as well. .Write([]byte(cmode.String())) .Write([]byte(cgran.String())) // Compute final digest. := .Sum(nil) copy(finalHash[:], ) finalHashComputed = true finalMetaLen = return , nil } // emitMetaDataToDirectory emits the meta-data output file to the specified // directory, returning an error if something went wrong. func emitMetaDataToDirectory( string, []rtcov.CovMetaBlob) error { , := prepareForMetaEmit() if != nil { return } if len() == 0 { return nil } metaDataEmitAttempted = true := &emitState{ metalist: , debug: os.Getenv("GOCOVERDEBUG") != "", outdir: , } // Open output files. if := .openOutputFiles(finalHash, finalMetaLen, metaDataFile); != nil { return } // Emit meta-data file only if needed (may already be present). if .needMetaDataFile() { if := .emitMetaDataFile(finalHash, finalMetaLen); != nil { return } } return nil } // emitCounterData emits the counter data output file for this coverage run. // This entry point is intended to be invoked by the runtime when an // instrumented program is terminating or calling os.Exit(). func emitCounterData() { if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted { return } if := emitCounterDataToDirectory(goCoverDir); != nil { fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", ) if os.Getenv("GOCOVERDEBUG") != "" { panic("counter-data write failure") } } } // emitCounterDataToDirectory emits the counter-data output file for this coverage run. func emitCounterDataToDirectory( string) error { // Ask the runtime for the list of coverage counter symbols. := getCovCounterList() if len() == 0 { // no work to do here. return nil } if !finalHashComputed { return fmt.Errorf("error: meta-data not available (binary not built with -cover?)") } // Ask the runtime for the list of coverage counter symbols. := rtcov.Meta.PkgMap := &emitState{ counterlist: , pkgmap: , outdir: , debug: os.Getenv("GOCOVERDEBUG") != "", } // Open output file. if := .openOutputFiles(finalHash, finalMetaLen, counterDataFile); != nil { return } if .cf == nil { return fmt.Errorf("counter data output file open failed (no additional info") } // Emit counter data file. if := .emitCounterDataFile(finalHash, .cf); != nil { return } if := .cf.Close(); != nil { return fmt.Errorf("closing counter data file: %v", ) } // Counter file has now been closed. Rename the temp to the // final desired path. if := os.Rename(.cftmp, .cfname); != nil { return fmt.Errorf("writing %s: rename from %s failed: %v\n", .cfname, .cftmp, ) } return nil } // emitCounterDataToWriter emits counter data for this coverage run to an io.Writer. func ( *emitState) ( io.Writer) error { if := .emitCounterDataFile(finalHash, ); != nil { return } return nil } // openMetaFile determines whether we need to emit a meta-data output // file, or whether we can reuse the existing file in the coverage out // dir. It updates mfname/mftmp/mf fields in 's', returning an error // if something went wrong. See the comment on the emitState type // definition above for more on how file opening is managed. func ( *emitState) ( [16]byte, uint64) error { // Open meta-outfile for reading to see if it exists. := fmt.Sprintf("%s.%x", coverage.MetaFilePref, ) .mfname = filepath.Join(.outdir, ) , := os.Stat(.mfname) if != nil || .Size() != int64() { // We need a new meta-file. := "tmp." + + strconv.FormatInt(time.Now().UnixNano(), 10) .mftmp = filepath.Join(.outdir, ) .mf, = os.Create(.mftmp) if != nil { return fmt.Errorf("creating meta-data file %s: %v", .mftmp, ) } } return nil } // openCounterFile opens an output file for the counter data portion // of a test coverage run. If updates the 'cfname' and 'cf' fields in // 's', returning an error if something went wrong. func ( *emitState) ( [16]byte) error { := os.Getpid() := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, , , time.Now().UnixNano()) .cfname = filepath.Join(.outdir, ) .cftmp = filepath.Join(.outdir, "tmp."+) var error .cf, = os.Create(.cftmp) if != nil { return fmt.Errorf("creating counter data file %s: %v", .cftmp, ) } return nil } // openOutputFiles opens output files in preparation for emitting // coverage data. In the case of the meta-data file, openOutputFiles // may determine that we can reuse an existing meta-data file in the // outdir, in which case it will leave the 'mf' field in the state // struct as nil. If a new meta-file is needed, the field 'mfname' // will be the final desired path of the meta file, 'mftmp' will be a // temporary file, and 'mf' will be an open os.File pointer for // 'mftmp'. The idea is that the client/caller will write content into // 'mf', close it, and then rename 'mftmp' to 'mfname'. This function // also opens the counter data output file, setting 'cf' and 'cfname' // in the state struct. func ( *emitState) ( [16]byte, uint64, fileType) error { , := os.Stat(.outdir) if != nil { return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", .outdir, ) } if !.IsDir() { return fmt.Errorf("output directory %q not a directory; no coverage data written", .outdir) } if ( & metaDataFile) != 0 { if := .openMetaFile(, ); != nil { return } } if ( & counterDataFile) != 0 { if := .openCounterFile(); != nil { return } } return nil } // emitMetaDataFile emits coverage meta-data to a previously opened // temporary file (s.mftmp), then renames the generated file to the // final path (s.mfname). func ( *emitState) ( [16]byte, uint64) error { if := writeMetaData(.mf, .metalist, cmode, cgran, ); != nil { return fmt.Errorf("writing %s: %v\n", .mftmp, ) } if := .mf.Close(); != nil { return fmt.Errorf("closing meta data temp file: %v", ) } // Temp file has now been flushed and closed. Rename the temp to the // final desired path. if := os.Rename(.mftmp, .mfname); != nil { return fmt.Errorf("writing %s: rename from %s failed: %v\n", .mfname, .mftmp, ) } return nil } // needMetaDataFile returns TRUE if we need to emit a meta-data file // for this program run. It should be used only after // openOutputFiles() has been invoked. func ( *emitState) () bool { return .mf != nil } func writeMetaData( io.Writer, []rtcov.CovMetaBlob, coverage.CounterMode, coverage.CounterGranularity, [16]byte) error { := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", ) var [][]byte for , := range { := unsafe.Slice(.P, int(.Len)) = append(, ) } return .Write(, , , ) } func ( *emitState) ( encodecounter.CounterVisitorFn) error { var []uint32 := func( []atomic.Uint32, []uint32) []uint32 { = [:0] for := range { = append(, [].Load()) } return } := uint32(0) for , := range .counterlist { := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(.Counters)), int(.Len)) for := 0; < len(); ++ { // Skip ahead until the next non-zero value. := [].Load() if == 0 { continue } // We found a function that was executed. := [+coverage.NumCtrsOffset].Load() := [+coverage.PkgIdOffset].Load() := [+coverage.FuncIdOffset].Load() := + coverage.FirstCtrOffset := [ : +int()] // Check to make sure that we have at least one live // counter. See the implementation note in ClearCoverageCounters // for a description of why this is needed. := false for := 0; < len(); ++ { if [].Load() != 0 { = true break } } if ! { // Skip this function. += coverage.FirstCtrOffset + int() - 1 continue } if .debug { if != { = fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn", , ) } fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", , , ) } // Vet and/or fix up package ID. A package ID of zero // indicates that there is some new package X that is a // runtime dependency, and this package has code that // executes before its corresponding init package runs. // This is a fatal error that we should only see during // Go development (e.g. tip). := int32() if == 0 { fmt.Fprintf(os.Stderr, "\n") reportErrorInHardcodedList(int32(), , , ) } else if < 0 { if , := .pkgmap[int()]; { = uint32() } else { fmt.Fprintf(os.Stderr, "\n") reportErrorInHardcodedList(int32(), , , ) } } else { // The package ID value stored in the counter array // has 1 added to it (so as to preclude the // possibility of a zero value ; see // runtime.addCovMeta), so subtract off 1 here to form // the real package ID. -- } = (, ) if := (, , ); != nil { return } // Skip over this function. += coverage.FirstCtrOffset + int() - 1 } if .debug { fmt.Fprintf(os.Stderr, "\n") } } return nil } // captureOsArgs converts os.Args() into the format we use to store // this info in the counter data file (counter data file "args" // section is a generic key-value collection). See the 'args' section // in internal/coverage/defs.go for more info. The args map // is also used to capture GOOS + GOARCH values as well. func captureOsArgs() map[string]string { := make(map[string]string) ["argc"] = strconv.Itoa(len(os.Args)) for , := range os.Args { [fmt.Sprintf("argv%d", )] = } ["GOOS"] = runtime.GOOS ["GOARCH"] = runtime.GOARCH return } // emitCounterDataFile emits the counter data portion of a // coverage output file (to the file 's.cf'). func ( *emitState) ( [16]byte, io.Writer) error { := encodecounter.NewCoverageDataWriter(, coverage.CtrULeb128) if := .Write(, capturedOsArgs, ); != nil { return } return nil } // MarkProfileEmitted signals the coverage machinery that // coverage data output files have already been written out, and there // is no need to take any additional action at exit time. This // function is called from the coverage-related boilerplate code in _testmain.go // emitted for go unit tests. func ( bool) { covProfileAlreadyEmitted = } func reportErrorInHardcodedList(, int32, , uint32) { := rtcov.Meta.List := rtcov.Meta.PkgMap println("internal error in coverage meta-data tracking:") println("encountered bad pkgID:", , " at slot:", , " fnID:", , " numCtrs:", ) println("list of hard-coded runtime package IDs needs revising.") println("[see the comment on the 'rtPkgs' var in ") println(" <goroot>/src/internal/coverage/pkid.go]") println("registered list:") for , := range { print("slot: ", , " path='", .PkgPath, "' ") if .PkgID != -1 { print(" hard-coded id: ", .PkgID) } println("") } println("remap table:") for , := range { println("from ", , " to ", ) } }