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 main 6 7// This file contains functions and apis to support the "go tool 8// covdata" sub-commands that relate to dumping text format summaries 9// and reports: "pkglist", "func", "debugdump", "percent", and 10// "textfmt". 11 12import ( 13 "flag" 14 "fmt" 15 "internal/coverage" 16 "internal/coverage/calloc" 17 "internal/coverage/cformat" 18 "internal/coverage/cmerge" 19 "internal/coverage/decodecounter" 20 "internal/coverage/decodemeta" 21 "internal/coverage/pods" 22 "os" 23 "sort" 24 "strings" 25) 26 27var textfmtoutflag *string 28var liveflag *bool 29 30func makeDumpOp(cmd string) covOperation { 31 if cmd == textfmtMode || cmd == percentMode { 32 textfmtoutflag = flag.String("o", "", "Output text format to file") 33 } 34 if cmd == debugDumpMode { 35 liveflag = flag.Bool("live", false, "Select only live (executed) functions for dump output.") 36 } 37 d := &dstate{ 38 cmd: cmd, 39 cm: &cmerge.Merger{}, 40 } 41 // For these modes (percent, pkglist, func, etc), use a relaxed 42 // policy when it comes to counter mode clashes. For a percent 43 // report, for example, we only care whether a given line is 44 // executed at least once, so it's ok to (effectively) merge 45 // together runs derived from different counter modes. 46 if d.cmd == percentMode || d.cmd == funcMode || d.cmd == pkglistMode { 47 d.cm.SetModeMergePolicy(cmerge.ModeMergeRelaxed) 48 } 49 if d.cmd == pkglistMode { 50 d.pkgpaths = make(map[string]struct{}) 51 } 52 return d 53} 54 55// dstate encapsulates state and provides methods for implementing 56// various dump operations. Specifically, dstate implements the 57// CovDataVisitor interface, and is designed to be used in 58// concert with the CovDataReader utility, which abstracts away most 59// of the grubby details of reading coverage data files. 60type dstate struct { 61 // for batch allocation of counter arrays 62 calloc.BatchCounterAlloc 63 64 // counter merging state + methods 65 cm *cmerge.Merger 66 67 // counter data formatting helper 68 format *cformat.Formatter 69 70 // 'mm' stores values read from a counter data file; the pkfunc key 71 // is a pkgid/funcid pair that uniquely identifies a function in 72 // instrumented application. 73 mm map[pkfunc]decodecounter.FuncPayload 74 75 // pkm maps package ID to the number of functions in the package 76 // with that ID. It is used to report inconsistencies in counter 77 // data (for example, a counter data entry with pkgid=N funcid=10 78 // where package N only has 3 functions). 79 pkm map[uint32]uint32 80 81 // pkgpaths records all package import paths encountered while 82 // visiting coverage data files (used to implement the "pkglist" 83 // subcommand). 84 pkgpaths map[string]struct{} 85 86 // Current package name and import path. 87 pkgName string 88 pkgImportPath string 89 90 // Module path for current package (may be empty). 91 modulePath string 92 93 // Dump subcommand (ex: "textfmt", "debugdump", etc). 94 cmd string 95 96 // File to which we will write text format output, if enabled. 97 textfmtoutf *os.File 98 99 // Total and covered statements (used by "debugdump" subcommand). 100 totalStmts, coveredStmts int 101 102 // Records whether preamble has been emitted for current pkg 103 // (used when in "debugdump" mode) 104 preambleEmitted bool 105} 106 107func (d *dstate) Usage(msg string) { 108 if len(msg) > 0 { 109 fmt.Fprintf(os.Stderr, "error: %s\n", msg) 110 } 111 fmt.Fprintf(os.Stderr, "usage: go tool covdata %s -i=<directories>\n\n", d.cmd) 112 flag.PrintDefaults() 113 fmt.Fprintf(os.Stderr, "\nExamples:\n\n") 114 switch d.cmd { 115 case pkglistMode: 116 fmt.Fprintf(os.Stderr, " go tool covdata pkglist -i=dir1,dir2\n\n") 117 fmt.Fprintf(os.Stderr, " \treads coverage data files from dir1+dirs2\n") 118 fmt.Fprintf(os.Stderr, " \tand writes out a list of the import paths\n") 119 fmt.Fprintf(os.Stderr, " \tof all compiled packages.\n") 120 case textfmtMode: 121 fmt.Fprintf(os.Stderr, " go tool covdata textfmt -i=dir1,dir2 -o=out.txt\n\n") 122 fmt.Fprintf(os.Stderr, " \tmerges data from input directories dir1+dir2\n") 123 fmt.Fprintf(os.Stderr, " \tand emits text format into file 'out.txt'\n") 124 case percentMode: 125 fmt.Fprintf(os.Stderr, " go tool covdata percent -i=dir1,dir2\n\n") 126 fmt.Fprintf(os.Stderr, " \tmerges data from input directories dir1+dir2\n") 127 fmt.Fprintf(os.Stderr, " \tand emits percentage of statements covered\n\n") 128 case funcMode: 129 fmt.Fprintf(os.Stderr, " go tool covdata func -i=dir1,dir2\n\n") 130 fmt.Fprintf(os.Stderr, " \treads coverage data files from dir1+dirs2\n") 131 fmt.Fprintf(os.Stderr, " \tand writes out coverage profile data for\n") 132 fmt.Fprintf(os.Stderr, " \teach function.\n") 133 case debugDumpMode: 134 fmt.Fprintf(os.Stderr, " go tool covdata debugdump [flags] -i=dir1,dir2\n\n") 135 fmt.Fprintf(os.Stderr, " \treads coverage data from dir1+dir2 and dumps\n") 136 fmt.Fprintf(os.Stderr, " \tcontents in human-readable form to stdout, for\n") 137 fmt.Fprintf(os.Stderr, " \tdebugging purposes.\n") 138 default: 139 panic("unexpected") 140 } 141 Exit(2) 142} 143 144// Setup is called once at program startup time to vet flag values 145// and do any necessary setup operations. 146func (d *dstate) Setup() { 147 if *indirsflag == "" { 148 d.Usage("select input directories with '-i' option") 149 } 150 if d.cmd == textfmtMode || (d.cmd == percentMode && *textfmtoutflag != "") { 151 if *textfmtoutflag == "" { 152 d.Usage("select output file name with '-o' option") 153 } 154 var err error 155 d.textfmtoutf, err = os.Create(*textfmtoutflag) 156 if err != nil { 157 d.Usage(fmt.Sprintf("unable to open textfmt output file %q: %v", *textfmtoutflag, err)) 158 } 159 } 160 if d.cmd == debugDumpMode { 161 fmt.Printf("/* WARNING: the format of this dump is not stable and is\n") 162 fmt.Printf(" * expected to change from one Go release to the next.\n") 163 fmt.Printf(" *\n") 164 fmt.Printf(" * produced by:\n") 165 args := append([]string{os.Args[0]}, debugDumpMode) 166 args = append(args, os.Args[1:]...) 167 fmt.Printf(" *\t%s\n", strings.Join(args, " ")) 168 fmt.Printf(" */\n") 169 } 170} 171 172func (d *dstate) BeginPod(p pods.Pod) { 173 d.mm = make(map[pkfunc]decodecounter.FuncPayload) 174} 175 176func (d *dstate) EndPod(p pods.Pod) { 177 if d.cmd == debugDumpMode { 178 d.cm.ResetModeAndGranularity() 179 } 180} 181 182func (d *dstate) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) { 183 dbgtrace(2, "visit counter data file %s dirIdx %d", cdf, dirIdx) 184 if d.cmd == debugDumpMode { 185 fmt.Printf("data file %s", cdf) 186 if cdr.Goos() != "" { 187 fmt.Printf(" GOOS=%s", cdr.Goos()) 188 } 189 if cdr.Goarch() != "" { 190 fmt.Printf(" GOARCH=%s", cdr.Goarch()) 191 } 192 if len(cdr.OsArgs()) != 0 { 193 fmt.Printf(" program args: %+v\n", cdr.OsArgs()) 194 } 195 fmt.Printf("\n") 196 } 197} 198 199func (d *dstate) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) { 200} 201 202func (d *dstate) VisitFuncCounterData(data decodecounter.FuncPayload) { 203 if nf, ok := d.pkm[data.PkgIdx]; !ok || data.FuncIdx > nf { 204 warn("func payload inconsistency: id [p=%d,f=%d] nf=%d len(ctrs)=%d in VisitFuncCounterData, ignored", data.PkgIdx, data.FuncIdx, nf, len(data.Counters)) 205 return 206 } 207 key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} 208 val, found := d.mm[key] 209 210 dbgtrace(5, "ctr visit pk=%d fid=%d found=%v len(val.ctrs)=%d len(data.ctrs)=%d", data.PkgIdx, data.FuncIdx, found, len(val.Counters), len(data.Counters)) 211 212 if len(val.Counters) < len(data.Counters) { 213 t := val.Counters 214 val.Counters = d.AllocateCounters(len(data.Counters)) 215 copy(val.Counters, t) 216 } 217 err, overflow := d.cm.MergeCounters(val.Counters, data.Counters) 218 if err != nil { 219 fatal("%v", err) 220 } 221 if overflow { 222 warn("uint32 overflow during counter merge") 223 } 224 d.mm[key] = val 225} 226 227func (d *dstate) EndCounters() { 228} 229 230func (d *dstate) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) { 231 newgran := mfr.CounterGranularity() 232 newmode := mfr.CounterMode() 233 if err := d.cm.SetModeAndGranularity(mdf, newmode, newgran); err != nil { 234 fatal("%v", err) 235 } 236 if d.cmd == debugDumpMode { 237 fmt.Printf("Cover mode: %s\n", newmode.String()) 238 fmt.Printf("Cover granularity: %s\n", newgran.String()) 239 } 240 if d.format == nil { 241 d.format = cformat.NewFormatter(mfr.CounterMode()) 242 } 243 244 // To provide an additional layer of checking when reading counter 245 // data, walk the meta-data file to determine the set of legal 246 // package/function combinations. This will help catch bugs in the 247 // counter file reader. 248 d.pkm = make(map[uint32]uint32) 249 np := uint32(mfr.NumPackages()) 250 payload := []byte{} 251 for pkIdx := uint32(0); pkIdx < np; pkIdx++ { 252 var pd *decodemeta.CoverageMetaDataDecoder 253 var err error 254 pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload) 255 if err != nil { 256 fatal("reading pkg %d from meta-file %s: %s", pkIdx, mdf, err) 257 } 258 d.pkm[pkIdx] = pd.NumFuncs() 259 } 260} 261 262func (d *dstate) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) { 263 d.preambleEmitted = false 264 d.pkgImportPath = pd.PackagePath() 265 d.pkgName = pd.PackageName() 266 d.modulePath = pd.ModulePath() 267 if d.cmd == pkglistMode { 268 d.pkgpaths[d.pkgImportPath] = struct{}{} 269 } 270 d.format.SetPackage(pd.PackagePath()) 271} 272 273func (d *dstate) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) { 274} 275 276func (d *dstate) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) { 277 var counters []uint32 278 key := pkfunc{pk: pkgIdx, fcn: fnIdx} 279 v, haveCounters := d.mm[key] 280 281 dbgtrace(5, "meta visit pk=%d fid=%d fname=%s file=%s found=%v len(val.ctrs)=%d", pkgIdx, fnIdx, fd.Funcname, fd.Srcfile, haveCounters, len(v.Counters)) 282 283 suppressOutput := false 284 if haveCounters { 285 counters = v.Counters 286 } else if d.cmd == debugDumpMode && *liveflag { 287 suppressOutput = true 288 } 289 290 if d.cmd == debugDumpMode && !suppressOutput { 291 if !d.preambleEmitted { 292 fmt.Printf("\nPackage path: %s\n", d.pkgImportPath) 293 fmt.Printf("Package name: %s\n", d.pkgName) 294 fmt.Printf("Module path: %s\n", d.modulePath) 295 d.preambleEmitted = true 296 } 297 fmt.Printf("\nFunc: %s\n", fd.Funcname) 298 fmt.Printf("Srcfile: %s\n", fd.Srcfile) 299 fmt.Printf("Literal: %v\n", fd.Lit) 300 } 301 for i := 0; i < len(fd.Units); i++ { 302 u := fd.Units[i] 303 var count uint32 304 if counters != nil { 305 count = counters[i] 306 } 307 d.format.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count) 308 if d.cmd == debugDumpMode && !suppressOutput { 309 fmt.Printf("%d: L%d:C%d -- L%d:C%d ", 310 i, u.StLine, u.StCol, u.EnLine, u.EnCol) 311 if u.Parent != 0 { 312 fmt.Printf("Parent:%d = %d\n", u.Parent, count) 313 } else { 314 fmt.Printf("NS=%d = %d\n", u.NxStmts, count) 315 } 316 } 317 d.totalStmts += int(u.NxStmts) 318 if count != 0 { 319 d.coveredStmts += int(u.NxStmts) 320 } 321 } 322} 323 324func (d *dstate) Finish() { 325 // d.format maybe nil here if the specified input dir was empty. 326 if d.format != nil { 327 if d.cmd == percentMode { 328 d.format.EmitPercent(os.Stdout, nil, "", false, false) 329 } 330 if d.cmd == funcMode { 331 d.format.EmitFuncs(os.Stdout) 332 } 333 if d.textfmtoutf != nil { 334 if err := d.format.EmitTextual(d.textfmtoutf); err != nil { 335 fatal("writing to %s: %v", *textfmtoutflag, err) 336 } 337 } 338 } 339 if d.textfmtoutf != nil { 340 if err := d.textfmtoutf.Close(); err != nil { 341 fatal("closing textfmt output file %s: %v", *textfmtoutflag, err) 342 } 343 } 344 if d.cmd == debugDumpMode { 345 fmt.Printf("totalStmts: %d coveredStmts: %d\n", d.totalStmts, d.coveredStmts) 346 } 347 if d.cmd == pkglistMode { 348 pkgs := make([]string, 0, len(d.pkgpaths)) 349 for p := range d.pkgpaths { 350 pkgs = append(pkgs, p) 351 } 352 sort.Strings(pkgs) 353 for _, p := range pkgs { 354 fmt.Printf("%s\n", p) 355 } 356 } 357} 358