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 cov
6
7import (
8	"cmd/internal/bio"
9	"fmt"
10	"internal/coverage"
11	"internal/coverage/decodecounter"
12	"internal/coverage/decodemeta"
13	"internal/coverage/pods"
14	"io"
15	"os"
16)
17
18// CovDataReader is a general-purpose helper/visitor object for
19// reading coverage data files in a structured way. Clients create a
20// CovDataReader to process a given collection of coverage data file
21// directories, then pass in a visitor object with methods that get
22// invoked at various important points. CovDataReader is intended
23// to facilitate common coverage data file operations such as
24// merging or intersecting data files, analyzing data files, or
25// dumping data files.
26type CovDataReader struct {
27	vis            CovDataVisitor
28	indirs         []string
29	matchpkg       func(name string) bool
30	flags          CovDataReaderFlags
31	err            error
32	verbosityLevel int
33}
34
35// MakeCovDataReader creates a CovDataReader object to process the
36// given set of input directories. Here 'vis' is a visitor object
37// providing methods to be invoked as we walk through the data,
38// 'indirs' is the set of coverage data directories to examine,
39// 'verbosityLevel' controls the level of debugging trace messages
40// (zero for off, higher for more output), 'flags' stores flags that
41// indicate what to do if errors are detected, and 'matchpkg' is a
42// caller-provided function that can be used to select specific
43// packages by name (if nil, then all packages are included).
44func MakeCovDataReader(vis CovDataVisitor, indirs []string, verbosityLevel int, flags CovDataReaderFlags, matchpkg func(name string) bool) *CovDataReader {
45	return &CovDataReader{
46		vis:            vis,
47		indirs:         indirs,
48		matchpkg:       matchpkg,
49		verbosityLevel: verbosityLevel,
50		flags:          flags,
51	}
52}
53
54// CovDataVisitor defines hooks for clients of CovDataReader. When the
55// coverage data reader makes its way through a coverage meta-data
56// file and counter data files, it will invoke the methods below to
57// hand off info to the client. The normal sequence of expected
58// visitor method invocations is:
59//
60//	for each pod P {
61//		BeginPod(p)
62//		let MF be the meta-data file for P
63//		VisitMetaDataFile(MF)
64//		for each counter data file D in P {
65//			BeginCounterDataFile(D)
66//			for each live function F in D {
67//				VisitFuncCounterData(F)
68//			}
69//			EndCounterDataFile(D)
70//		}
71//		EndCounters(MF)
72//		for each package PK in MF {
73//			BeginPackage(PK)
74//			if <PK matched according to package pattern and/or modpath> {
75//				for each function PF in PK {
76//					VisitFunc(PF)
77//				}
78//			}
79//			EndPackage(PK)
80//		}
81//		EndPod(p)
82//	}
83//	Finish()
84
85type CovDataVisitor interface {
86	// Invoked at the start and end of a given pod (a pod here is a
87	// specific coverage meta-data files with the counter data files
88	// that correspond to it).
89	BeginPod(p pods.Pod)
90	EndPod(p pods.Pod)
91
92	// Invoked when the reader is starting to examine the meta-data
93	// file for a pod. Here 'mdf' is the path of the file, and 'mfr'
94	// is an open meta-data reader.
95	VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader)
96
97	// Invoked when the reader processes a counter data file, first
98	// the 'begin' method at the start, then the 'end' method when
99	// we're done with the file.
100	BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
101	EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
102
103	// Invoked once for each live function in the counter data file.
104	VisitFuncCounterData(payload decodecounter.FuncPayload)
105
106	// Invoked when we've finished processing the counter files in a
107	// POD (e.g. no more calls to VisitFuncCounterData).
108	EndCounters()
109
110	// Invoked for each package in the meta-data file for the pod,
111	// first the 'begin' method when processing of the package starts,
112	// then the 'end' method when we're done
113	BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
114	EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
115
116	// Invoked for each function  the package being visited.
117	VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc)
118
119	// Invoked when all counter + meta-data file processing is complete.
120	Finish()
121}
122
123type CovDataReaderFlags uint32
124
125const (
126	CovDataReaderNoFlags CovDataReaderFlags = 0
127	PanicOnError                            = 1 << iota
128	PanicOnWarning
129)
130
131func (r *CovDataReader) Visit() error {
132	podlist, err := pods.CollectPods(r.indirs, false)
133	if err != nil {
134		return fmt.Errorf("reading inputs: %v", err)
135	}
136	if len(podlist) == 0 {
137		r.warn("no applicable files found in input directories")
138	}
139	for _, p := range podlist {
140		if err := r.visitPod(p); err != nil {
141			return err
142		}
143	}
144	r.vis.Finish()
145	return nil
146}
147
148func (r *CovDataReader) verb(vlevel int, s string, a ...interface{}) {
149	if r.verbosityLevel >= vlevel {
150		fmt.Fprintf(os.Stderr, s, a...)
151		fmt.Fprintf(os.Stderr, "\n")
152	}
153}
154
155func (r *CovDataReader) warn(s string, a ...interface{}) {
156	fmt.Fprintf(os.Stderr, "warning: ")
157	fmt.Fprintf(os.Stderr, s, a...)
158	fmt.Fprintf(os.Stderr, "\n")
159	if (r.flags & PanicOnWarning) != 0 {
160		panic("unexpected warning")
161	}
162}
163
164func (r *CovDataReader) fatal(s string, a ...interface{}) error {
165	if r.err != nil {
166		return nil
167	}
168	errstr := "error: " + fmt.Sprintf(s, a...) + "\n"
169	if (r.flags & PanicOnError) != 0 {
170		fmt.Fprintf(os.Stderr, "%s", errstr)
171		panic("fatal error")
172	}
173	r.err = fmt.Errorf("%s", errstr)
174	return r.err
175}
176
177// visitPod examines a coverage data 'pod', that is, a meta-data file and
178// zero or more counter data files that refer to that meta-data file.
179func (r *CovDataReader) visitPod(p pods.Pod) error {
180	r.verb(1, "visiting pod: metafile %s with %d counter files",
181		p.MetaFile, len(p.CounterDataFiles))
182	r.vis.BeginPod(p)
183
184	// Open meta-file
185	f, err := os.Open(p.MetaFile)
186	if err != nil {
187		return r.fatal("unable to open meta-file %s", p.MetaFile)
188	}
189	defer f.Close()
190	br := bio.NewReader(f)
191	fi, err := f.Stat()
192	if err != nil {
193		return r.fatal("unable to stat metafile %s: %v", p.MetaFile, err)
194	}
195	fileView := br.SliceRO(uint64(fi.Size()))
196	br.MustSeek(0, io.SeekStart)
197
198	r.verb(1, "fileView for pod is length %d", len(fileView))
199
200	var mfr *decodemeta.CoverageMetaFileReader
201	mfr, err = decodemeta.NewCoverageMetaFileReader(f, fileView)
202	if err != nil {
203		return r.fatal("decoding meta-file %s: %s", p.MetaFile, err)
204	}
205	r.vis.VisitMetaDataFile(p.MetaFile, mfr)
206
207	processCounterDataFile := func(cdf string, k int) error {
208		cf, err := os.Open(cdf)
209		if err != nil {
210			return r.fatal("opening counter data file %s: %s", cdf, err)
211		}
212		defer cf.Close()
213		var mr *MReader
214		mr, err = NewMreader(cf)
215		if err != nil {
216			return r.fatal("creating reader for counter data file %s: %s", cdf, err)
217		}
218		var cdr *decodecounter.CounterDataReader
219		cdr, err = decodecounter.NewCounterDataReader(cdf, mr)
220		if err != nil {
221			return r.fatal("reading counter data file %s: %s", cdf, err)
222		}
223		r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k])
224		var data decodecounter.FuncPayload
225		for {
226			ok, err := cdr.NextFunc(&data)
227			if err != nil {
228				return r.fatal("reading counter data file %s: %v", cdf, err)
229			}
230			if !ok {
231				break
232			}
233			r.vis.VisitFuncCounterData(data)
234		}
235		r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k])
236		return nil
237	}
238
239	// Read counter data files.
240	for k, cdf := range p.CounterDataFiles {
241		if err := processCounterDataFile(cdf, k); err != nil {
242			return err
243		}
244	}
245	r.vis.EndCounters()
246
247	// NB: packages in the meta-file will be in dependency order (basically
248	// the order in which init files execute). Do we want an additional sort
249	// pass here, say by packagepath?
250	np := uint32(mfr.NumPackages())
251	payload := []byte{}
252	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
253		var pd *decodemeta.CoverageMetaDataDecoder
254		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
255		if err != nil {
256			return r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
257		}
258		r.processPackage(p.MetaFile, pd, pkIdx)
259	}
260	r.vis.EndPod(p)
261
262	return nil
263}
264
265func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error {
266	if r.matchpkg != nil {
267		if !r.matchpkg(pd.PackagePath()) {
268			return nil
269		}
270	}
271	r.vis.BeginPackage(pd, pkgIdx)
272	nf := pd.NumFuncs()
273	var fd coverage.FuncDesc
274	for fidx := uint32(0); fidx < nf; fidx++ {
275		if err := pd.ReadFunc(fidx, &fd); err != nil {
276			return r.fatal("reading meta-data file %s: %v", mfname, err)
277		}
278		r.vis.VisitFunc(pkgIdx, fidx, &fd)
279	}
280	r.vis.EndPackage(pd, pkgIdx)
281	return nil
282}
283