blob: 51f934f060099fe4e6523ae21cc535f35c2ff89a [file] [log] [blame] [edit]
// 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
// This package contains support routines for coverage "fixup" in the
// compiler, which happens when compiling a package whose source code
// has been run through "cmd/cover" to add instrumentation. The two
// important entry points are FixupVars (called prior to package init
// generation) and FixupInit (called following package init
// generation).
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/objabi"
"internal/coverage"
"strconv"
"strings"
)
// names records state information collected in the first fixup
// phase so that it can be passed to the second fixup phase.
type names struct {
MetaVar *ir.Name
PkgIdVar *ir.Name
InitFn *ir.Func
CounterMode coverage.CounterMode
CounterGran coverage.CounterGranularity
}
// Fixup adds calls to the pkg init function as appropriate to
// register coverage-related variables with the runtime.
//
// It also reclassifies selected variables (for example, tagging
// coverage counter variables with flags so that they can be handled
// properly downstream).
func Fixup() {
if base.Flag.Cfg.CoverageInfo == nil {
return // not using coverage
}
metaVarName := base.Flag.Cfg.CoverageInfo.MetaVar
pkgIdVarName := base.Flag.Cfg.CoverageInfo.PkgIdVar
counterMode := base.Flag.Cfg.CoverageInfo.CounterMode
counterGran := base.Flag.Cfg.CoverageInfo.CounterGranularity
counterPrefix := base.Flag.Cfg.CoverageInfo.CounterPrefix
var metavar *ir.Name
var pkgidvar *ir.Name
ckTypSanity := func(nm *ir.Name, tag string) {
if nm.Type() == nil || nm.Type().HasPointers() {
base.Fatalf("unsuitable %s %q mentioned in coveragecfg, improper type '%v'", tag, nm.Sym().Name, nm.Type())
}
}
for _, nm := range typecheck.Target.Externs {
s := nm.Sym()
switch s.Name {
case metaVarName:
metavar = nm
ckTypSanity(nm, "metavar")
nm.MarkReadonly()
continue
case pkgIdVarName:
pkgidvar = nm
ckTypSanity(nm, "pkgidvar")
nm.SetCoverageAuxVar(true)
s := nm.Linksym()
s.Type = objabi.SCOVERAGE_AUXVAR
continue
}
if strings.HasPrefix(s.Name, counterPrefix) {
ckTypSanity(nm, "countervar")
nm.SetCoverageAuxVar(true)
s := nm.Linksym()
s.Type = objabi.SCOVERAGE_COUNTER
}
}
cm := coverage.ParseCounterMode(counterMode)
if cm == coverage.CtrModeInvalid {
base.Fatalf("bad setting %q for covermode in coveragecfg:",
counterMode)
}
var cg coverage.CounterGranularity
switch counterGran {
case "perblock":
cg = coverage.CtrGranularityPerBlock
case "perfunc":
cg = coverage.CtrGranularityPerFunc
default:
base.Fatalf("bad setting %q for covergranularity in coveragecfg:",
counterGran)
}
cnames := names{
MetaVar: metavar,
PkgIdVar: pkgidvar,
CounterMode: cm,
CounterGran: cg,
}
for _, fn := range typecheck.Target.Funcs {
if ir.FuncName(fn) == "init" {
cnames.InitFn = fn
break
}
}
if cnames.InitFn == nil {
panic("unexpected (no init func for -cover build)")
}
hashv, len := metaHashAndLen()
if cnames.CounterMode != coverage.CtrModeTestMain {
registerMeta(cnames, hashv, len)
}
if base.Ctxt.Pkgpath == "main" {
addInitHookCall(cnames.InitFn, cnames.CounterMode)
}
}
func metaHashAndLen() ([16]byte, int) {
// Read meta-data hash from config entry.
mhash := base.Flag.Cfg.CoverageInfo.MetaHash
if len(mhash) != 32 {
base.Fatalf("unexpected: got metahash length %d want 32", len(mhash))
}
var hv [16]byte
for i := 0; i < 16; i++ {
nib := string(mhash[i*2 : i*2+2])
x, err := strconv.ParseInt(nib, 16, 32)
if err != nil {
base.Fatalf("metahash bad byte %q", nib)
}
hv[i] = byte(x)
}
// Return hash and meta-data len
return hv, base.Flag.Cfg.CoverageInfo.MetaLen
}
func registerMeta(cnames names, hashv [16]byte, mdlen int) {
// Materialize expression for hash (an array literal)
pos := cnames.InitFn.Pos()
elist := make([]ir.Node, 0, 16)
for i := 0; i < 16; i++ {
elem := ir.NewInt(base.Pos, int64(hashv[i]))
elist = append(elist, elem)
}
ht := types.NewArray(types.Types[types.TUINT8], 16)
hashx := ir.NewCompLitExpr(pos, ir.OCOMPLIT, ht, elist)
// Materalize expression corresponding to address of the meta-data symbol.
mdax := typecheck.NodAddr(cnames.MetaVar)
mdauspx := typecheck.ConvNop(mdax, types.Types[types.TUNSAFEPTR])
// Materialize expression for length.
lenx := ir.NewInt(base.Pos, int64(mdlen)) // untyped
// Generate a call to runtime.addCovMeta, e.g.
//
// pkgIdVar = runtime.addCovMeta(&sym, len, hash, pkgpath, pkid, cmode, cgran)
//
fn := typecheck.LookupRuntime("addCovMeta")
pkid := coverage.HardCodedPkgID(base.Ctxt.Pkgpath)
pkIdNode := ir.NewInt(base.Pos, int64(pkid))
cmodeNode := ir.NewInt(base.Pos, int64(cnames.CounterMode))
cgranNode := ir.NewInt(base.Pos, int64(cnames.CounterGran))
pkPathNode := ir.NewString(base.Pos, base.Ctxt.Pkgpath)
callx := typecheck.Call(pos, fn, []ir.Node{mdauspx, lenx, hashx,
pkPathNode, pkIdNode, cmodeNode, cgranNode}, false)
assign := callx
if pkid == coverage.NotHardCoded {
assign = typecheck.Stmt(ir.NewAssignStmt(pos, cnames.PkgIdVar, callx))
}
// Tack the call onto the start of our init function. We do this
// early in the init since it's possible that instrumented function
// bodies (with counter updates) might be inlined into init.
cnames.InitFn.Body.Prepend(assign)
}
// addInitHookCall generates a call to runtime/coverage.initHook() and
// inserts it into the package main init function, which will kick off
// the process for coverage data writing (emit meta data, and register
// an exit hook to emit counter data).
func addInitHookCall(initfn *ir.Func, cmode coverage.CounterMode) {
typecheck.InitCoverage()
pos := initfn.Pos()
istest := cmode == coverage.CtrModeTestMain
initf := typecheck.LookupCoverage("initHook")
istestNode := ir.NewBool(base.Pos, istest)
args := []ir.Node{istestNode}
callx := typecheck.Call(pos, initf, args, false)
initfn.Body.Append(callx)
}