summaryrefslogtreecommitdiff
path: root/src/cmd/internal/cov/readcovdata.go
blob: 7e90e9e808927148283bb6b717aaad12ffde23af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// 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 cov

import (
	"cmd/internal/bio"
	"fmt"
	"internal/coverage"
	"internal/coverage/decodecounter"
	"internal/coverage/decodemeta"
	"internal/coverage/pods"
	"io"
	"os"
)

// CovDataReader is a general-purpose helper/visitor object for
// reading coverage data files in a structured way. Clients create a
// CovDataReader to process a given collection of coverage data file
// directories, then pass in a visitor object with methods that get
// invoked at various important points. CovDataReader is intended
// to facilitate common coverage data file operations such as
// merging or intersecting data files, analyzing data files, or
// dumping data files.
type CovDataReader struct {
	vis            CovDataVisitor
	indirs         []string
	matchpkg       func(name string) bool
	flags          CovDataReaderFlags
	err            error
	verbosityLevel int
}

// MakeCovDataReader creates a CovDataReader object to process the
// given set of input directories. Here 'vis' is a visitor object
// providing methods to be invoked as we walk through the data,
// 'indirs' is the set of coverage data directories to examine,
// 'verbosityLevel' controls the level of debugging trace messages
// (zero for off, higher for more output), 'flags' stores flags that
// indicate what to do if errors are detected, and 'matchpkg' is a
// caller-provided function that can be used to select specific
// packages by name (if nil, then all packages are included).
func MakeCovDataReader(vis CovDataVisitor, indirs []string, verbosityLevel int, flags CovDataReaderFlags, matchpkg func(name string) bool) *CovDataReader {
	return &CovDataReader{
		vis:            vis,
		indirs:         indirs,
		matchpkg:       matchpkg,
		verbosityLevel: verbosityLevel,
		flags:          flags,
	}
}

// CovDataVisitor defines hooks for clients of CovDataReader. When the
// coverage data reader makes its way through a coverage meta-data
// file and counter data files, it will invoke the methods below to
// hand off info to the client. The normal sequence of expected
// visitor method invocations is:
//
//	for each pod P {
//		BeginPod(p)
//		let MF be the meta-data file for P
//		VisitMetaDataFile(MF)
//		for each counter data file D in P {
//			BeginCounterDataFile(D)
//			for each live function F in D {
//				VisitFuncCounterData(F)
//			}
//			EndCounterDataFile(D)
//		}
//		EndCounters(MF)
//		for each package PK in MF {
//			BeginPackage(PK)
//			if <PK matched according to package pattern and/or modpath> {
//				for each function PF in PK {
//					VisitFunc(PF)
//				}
//			}
//			EndPackage(PK)
//		}
//		EndPod(p)
//	}
//	Finish()

type CovDataVisitor interface {
	// Invoked at the start and end of a given pod (a pod here is a
	// specific coverage meta-data files with the counter data files
	// that correspond to it).
	BeginPod(p pods.Pod)
	EndPod(p pods.Pod)

	// Invoked when the reader is starting to examine the meta-data
	// file for a pod. Here 'mdf' is the path of the file, and 'mfr'
	// is an open meta-data reader.
	VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader)

	// Invoked when the reader processes a counter data file, first
	// the 'begin' method at the start, then the 'end' method when
	// we're done with the file.
	BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
	EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)

	// Invoked once for each live function in the counter data file.
	VisitFuncCounterData(payload decodecounter.FuncPayload)

	// Invoked when we've finished processing the counter files in a
	// POD (e.g. no more calls to VisitFuncCounterData).
	EndCounters()

	// Invoked for each package in the meta-data file for the pod,
	// first the 'begin' method when processinf of hte package starts,
	// then the 'end' method when we're done
	BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
	EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)

	// Invoked for each function  the package being visited.
	VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc)

	// Invoked when all counter + meta-data file processing is complete.
	Finish()
}

type CovDataReaderFlags uint32

const (
	CovDataReaderNoFlags CovDataReaderFlags = 0
	PanicOnError                            = 1 << iota
	PanicOnWarning
)

func (r *CovDataReader) Visit() error {
	podlist, err := pods.CollectPods(r.indirs, false)
	if err != nil {
		return fmt.Errorf("reading inputs: %v", err)
	}
	if len(podlist) == 0 {
		r.warn("no applicable files found in input directories")
	}
	for _, p := range podlist {
		if err := r.visitPod(p); err != nil {
			return err
		}
	}
	r.vis.Finish()
	return nil
}

func (r *CovDataReader) verb(vlevel int, s string, a ...interface{}) {
	if r.verbosityLevel >= vlevel {
		fmt.Fprintf(os.Stderr, s, a...)
		fmt.Fprintf(os.Stderr, "\n")
	}
}

func (r *CovDataReader) warn(s string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, "warning: ")
	fmt.Fprintf(os.Stderr, s, a...)
	fmt.Fprintf(os.Stderr, "\n")
	if (r.flags & PanicOnWarning) != 0 {
		panic("unexpected warning")
	}
}

func (r *CovDataReader) fatal(s string, a ...interface{}) error {
	if r.err != nil {
		return nil
	}
	errstr := "error: " + fmt.Sprintf(s, a...) + "\n"
	if (r.flags & PanicOnError) != 0 {
		fmt.Fprintf(os.Stderr, "%s", errstr)
		panic("fatal error")
	}
	r.err = fmt.Errorf("%s", errstr)
	return r.err
}

// visitPod examines a coverage data 'pod', that is, a meta-data file and
// zero or more counter data files that refer to that meta-data file.
func (r *CovDataReader) visitPod(p pods.Pod) error {
	r.verb(1, "visiting pod: metafile %s with %d counter files",
		p.MetaFile, len(p.CounterDataFiles))
	r.vis.BeginPod(p)

	// Open meta-file
	f, err := os.Open(p.MetaFile)
	if err != nil {
		return r.fatal("unable to open meta-file %s", p.MetaFile)
	}
	defer f.Close()
	br := bio.NewReader(f)
	fi, err := f.Stat()
	if err != nil {
		return r.fatal("unable to stat metafile %s: %v", p.MetaFile, err)
	}
	fileView := br.SliceRO(uint64(fi.Size()))
	br.MustSeek(0, io.SeekStart)

	r.verb(1, "fileView for pod is length %d", len(fileView))

	var mfr *decodemeta.CoverageMetaFileReader
	mfr, err = decodemeta.NewCoverageMetaFileReader(f, fileView)
	if err != nil {
		return r.fatal("decoding meta-file %s: %s", p.MetaFile, err)
	}
	r.vis.VisitMetaDataFile(p.MetaFile, mfr)

	// Read counter data files.
	for k, cdf := range p.CounterDataFiles {
		cf, err := os.Open(cdf)
		if err != nil {
			return r.fatal("opening counter data file %s: %s", cdf, err)
		}
		defer func(f *os.File) {
			f.Close()
		}(cf)
		var mr *MReader
		mr, err = NewMreader(cf)
		if err != nil {
			return r.fatal("creating reader for counter data file %s: %s", cdf, err)
		}
		var cdr *decodecounter.CounterDataReader
		cdr, err = decodecounter.NewCounterDataReader(cdf, mr)
		if err != nil {
			return r.fatal("reading counter data file %s: %s", cdf, err)
		}
		r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k])
		var data decodecounter.FuncPayload
		for {
			ok, err := cdr.NextFunc(&data)
			if err != nil {
				return r.fatal("reading counter data file %s: %v", cdf, err)
			}
			if !ok {
				break
			}
			r.vis.VisitFuncCounterData(data)
		}
		r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k])
	}
	r.vis.EndCounters()

	// NB: packages in the meta-file will be in dependency order (basically
	// the order in which init files execute). Do we want an additional sort
	// pass here, say by packagepath?
	np := uint32(mfr.NumPackages())
	payload := []byte{}
	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
		var pd *decodemeta.CoverageMetaDataDecoder
		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
		if err != nil {
			return r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
		}
		r.processPackage(p.MetaFile, pd, pkIdx)
	}
	r.vis.EndPod(p)

	return nil
}

func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error {
	if r.matchpkg != nil {
		if !r.matchpkg(pd.PackagePath()) {
			return nil
		}
	}
	r.vis.BeginPackage(pd, pkgIdx)
	nf := pd.NumFuncs()
	var fd coverage.FuncDesc
	for fidx := uint32(0); fidx < nf; fidx++ {
		if err := pd.ReadFunc(fidx, &fd); err != nil {
			return r.fatal("reading meta-data file %s: %v", mfname, err)
		}
		r.vis.VisitFunc(pkgIdx, fidx, &fd)
	}
	r.vis.EndPackage(pd, pkgIdx)
	return nil
}