Source file src/cmd/compile/internal/coverage/cover.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 coverage
     6  
     7  // This package contains support routines for coverage "fixup" in the
     8  // compiler, which happens when compiling a package whose source code
     9  // has been run through "cmd/cover" to add instrumentation. The two
    10  // important entry points are FixupVars (called prior to package init
    11  // generation) and FixupInit (called following package init
    12  // generation).
    13  
    14  import (
    15  	"cmd/compile/internal/base"
    16  	"cmd/compile/internal/ir"
    17  	"cmd/compile/internal/typecheck"
    18  	"cmd/compile/internal/types"
    19  	"cmd/internal/objabi"
    20  	"internal/coverage"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  // names records state information collected in the first fixup
    26  // phase so that it can be passed to the second fixup phase.
    27  type names struct {
    28  	MetaVar     *ir.Name
    29  	PkgIdVar    *ir.Name
    30  	InitFn      *ir.Func
    31  	CounterMode coverage.CounterMode
    32  	CounterGran coverage.CounterGranularity
    33  }
    34  
    35  // Fixup adds calls to the pkg init function as appropriate to
    36  // register coverage-related variables with the runtime.
    37  //
    38  // It also reclassifies selected variables (for example, tagging
    39  // coverage counter variables with flags so that they can be handled
    40  // properly downstream).
    41  func Fixup() {
    42  	if base.Flag.Cfg.CoverageInfo == nil {
    43  		return // not using coverage
    44  	}
    45  
    46  	metaVarName := base.Flag.Cfg.CoverageInfo.MetaVar
    47  	pkgIdVarName := base.Flag.Cfg.CoverageInfo.PkgIdVar
    48  	counterMode := base.Flag.Cfg.CoverageInfo.CounterMode
    49  	counterGran := base.Flag.Cfg.CoverageInfo.CounterGranularity
    50  	counterPrefix := base.Flag.Cfg.CoverageInfo.CounterPrefix
    51  	var metavar *ir.Name
    52  	var pkgidvar *ir.Name
    53  
    54  	ckTypSanity := func(nm *ir.Name, tag string) {
    55  		if nm.Type() == nil || nm.Type().HasPointers() {
    56  			base.Fatalf("unsuitable %s %q mentioned in coveragecfg, improper type '%v'", tag, nm.Sym().Name, nm.Type())
    57  		}
    58  	}
    59  
    60  	for _, nm := range typecheck.Target.Externs {
    61  		s := nm.Sym()
    62  		switch s.Name {
    63  		case metaVarName:
    64  			metavar = nm
    65  			ckTypSanity(nm, "metavar")
    66  			nm.MarkReadonly()
    67  			continue
    68  		case pkgIdVarName:
    69  			pkgidvar = nm
    70  			ckTypSanity(nm, "pkgidvar")
    71  			nm.SetCoverageAuxVar(true)
    72  			s := nm.Linksym()
    73  			s.Type = objabi.SCOVERAGE_AUXVAR
    74  			continue
    75  		}
    76  		if strings.HasPrefix(s.Name, counterPrefix) {
    77  			ckTypSanity(nm, "countervar")
    78  			nm.SetCoverageCounter(true)
    79  			s := nm.Linksym()
    80  			s.Type = objabi.SCOVERAGE_COUNTER
    81  		}
    82  	}
    83  	cm := coverage.ParseCounterMode(counterMode)
    84  	if cm == coverage.CtrModeInvalid {
    85  		base.Fatalf("bad setting %q for covermode in coveragecfg:",
    86  			counterMode)
    87  	}
    88  	var cg coverage.CounterGranularity
    89  	switch counterGran {
    90  	case "perblock":
    91  		cg = coverage.CtrGranularityPerBlock
    92  	case "perfunc":
    93  		cg = coverage.CtrGranularityPerFunc
    94  	default:
    95  		base.Fatalf("bad setting %q for covergranularity in coveragecfg:",
    96  			counterGran)
    97  	}
    98  
    99  	cnames := names{
   100  		MetaVar:     metavar,
   101  		PkgIdVar:    pkgidvar,
   102  		CounterMode: cm,
   103  		CounterGran: cg,
   104  	}
   105  
   106  	for _, fn := range typecheck.Target.Funcs {
   107  		if ir.FuncName(fn) == "init" {
   108  			cnames.InitFn = fn
   109  			break
   110  		}
   111  	}
   112  	if cnames.InitFn == nil {
   113  		panic("unexpected (no init func for -cover build)")
   114  	}
   115  
   116  	hashv, len := metaHashAndLen()
   117  	if cnames.CounterMode != coverage.CtrModeTestMain {
   118  		registerMeta(cnames, hashv, len)
   119  	}
   120  	if base.Ctxt.Pkgpath == "main" {
   121  		addInitHookCall(cnames.InitFn, cnames.CounterMode)
   122  	}
   123  }
   124  
   125  func metaHashAndLen() ([16]byte, int) {
   126  
   127  	// Read meta-data hash from config entry.
   128  	mhash := base.Flag.Cfg.CoverageInfo.MetaHash
   129  	if len(mhash) != 32 {
   130  		base.Fatalf("unexpected: got metahash length %d want 32", len(mhash))
   131  	}
   132  	var hv [16]byte
   133  	for i := 0; i < 16; i++ {
   134  		nib := string(mhash[i*2 : i*2+2])
   135  		x, err := strconv.ParseInt(nib, 16, 32)
   136  		if err != nil {
   137  			base.Fatalf("metahash bad byte %q", nib)
   138  		}
   139  		hv[i] = byte(x)
   140  	}
   141  
   142  	// Return hash and meta-data len
   143  	return hv, base.Flag.Cfg.CoverageInfo.MetaLen
   144  }
   145  
   146  func registerMeta(cnames names, hashv [16]byte, mdlen int) {
   147  	// Materialize expression for hash (an array literal)
   148  	pos := cnames.InitFn.Pos()
   149  	elist := make([]ir.Node, 0, 16)
   150  	for i := 0; i < 16; i++ {
   151  		elem := ir.NewInt(base.Pos, int64(hashv[i]))
   152  		elist = append(elist, elem)
   153  	}
   154  	ht := types.NewArray(types.Types[types.TUINT8], 16)
   155  	hashx := ir.NewCompLitExpr(pos, ir.OCOMPLIT, ht, elist)
   156  
   157  	// Materalize expression corresponding to address of the meta-data symbol.
   158  	mdax := typecheck.NodAddr(cnames.MetaVar)
   159  	mdauspx := typecheck.ConvNop(mdax, types.Types[types.TUNSAFEPTR])
   160  
   161  	// Materialize expression for length.
   162  	lenx := ir.NewInt(base.Pos, int64(mdlen)) // untyped
   163  
   164  	// Generate a call to runtime.addCovMeta, e.g.
   165  	//
   166  	//   pkgIdVar = runtime.addCovMeta(&sym, len, hash, pkgpath, pkid, cmode, cgran)
   167  	//
   168  	fn := typecheck.LookupRuntime("addCovMeta")
   169  	pkid := coverage.HardCodedPkgID(base.Ctxt.Pkgpath)
   170  	pkIdNode := ir.NewInt(base.Pos, int64(pkid))
   171  	cmodeNode := ir.NewInt(base.Pos, int64(cnames.CounterMode))
   172  	cgranNode := ir.NewInt(base.Pos, int64(cnames.CounterGran))
   173  	pkPathNode := ir.NewString(base.Pos, base.Ctxt.Pkgpath)
   174  	callx := typecheck.Call(pos, fn, []ir.Node{mdauspx, lenx, hashx,
   175  		pkPathNode, pkIdNode, cmodeNode, cgranNode}, false)
   176  	assign := callx
   177  	if pkid == coverage.NotHardCoded {
   178  		assign = typecheck.Stmt(ir.NewAssignStmt(pos, cnames.PkgIdVar, callx))
   179  	}
   180  
   181  	// Tack the call onto the start of our init function. We do this
   182  	// early in the init since it's possible that instrumented function
   183  	// bodies (with counter updates) might be inlined into init.
   184  	cnames.InitFn.Body.Prepend(assign)
   185  }
   186  
   187  // addInitHookCall generates a call to runtime/coverage.initHook() and
   188  // inserts it into the package main init function, which will kick off
   189  // the process for coverage data writing (emit meta data, and register
   190  // an exit hook to emit counter data).
   191  func addInitHookCall(initfn *ir.Func, cmode coverage.CounterMode) {
   192  	typecheck.InitCoverage()
   193  	pos := initfn.Pos()
   194  	istest := cmode == coverage.CtrModeTestMain
   195  	initf := typecheck.LookupCoverage("initHook")
   196  	istestNode := ir.NewBool(base.Pos, istest)
   197  	args := []ir.Node{istestNode}
   198  	callx := typecheck.Call(pos, initf, args, false)
   199  	initfn.Body.Append(callx)
   200  }
   201  

View as plain text