• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5package 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
14import (
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.
27type 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).
41func 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.SetCoverageAuxVar(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
125func 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
146func 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).
191func 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