github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/coverage/cover.go (about)

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