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 "subtract" and 8// "intersect" subcommands of "go tool covdata". 9 10import ( 11 "flag" 12 "fmt" 13 "internal/coverage" 14 "internal/coverage/decodecounter" 15 "internal/coverage/decodemeta" 16 "internal/coverage/pods" 17 "os" 18 "strings" 19) 20 21// makeSubtractIntersectOp creates a subtract or intersect operation. 22// 'mode' here must be either "subtract" or "intersect". 23func makeSubtractIntersectOp(mode string) covOperation { 24 outdirflag = flag.String("o", "", "Output directory to write") 25 s := &sstate{ 26 mode: mode, 27 mm: newMetaMerge(), 28 inidx: -1, 29 } 30 return s 31} 32 33// sstate holds state needed to implement subtraction and intersection 34// operations on code coverage data files. This type provides methods 35// to implement the CovDataVisitor interface, and is designed to be 36// used in concert with the CovDataReader utility, which abstracts 37// away most of the grubby details of reading coverage data files. 38type sstate struct { 39 mm *metaMerge 40 inidx int 41 mode string 42 // Used only for intersection; keyed by pkg/fn ID, it keeps track of 43 // just the set of functions for which we have data in the current 44 // input directory. 45 imm map[pkfunc]struct{} 46} 47 48func (s *sstate) Usage(msg string) { 49 if len(msg) > 0 { 50 fmt.Fprintf(os.Stderr, "error: %s\n", msg) 51 } 52 fmt.Fprintf(os.Stderr, "usage: go tool covdata %s -i=dir1,dir2 -o=<dir>\n\n", s.mode) 53 flag.PrintDefaults() 54 fmt.Fprintf(os.Stderr, "\nExamples:\n\n") 55 op := "from" 56 if s.mode == intersectMode { 57 op = "with" 58 } 59 fmt.Fprintf(os.Stderr, " go tool covdata %s -i=dir1,dir2 -o=outdir\n\n", s.mode) 60 fmt.Fprintf(os.Stderr, " \t%ss dir2 %s dir1, writing result\n", s.mode, op) 61 fmt.Fprintf(os.Stderr, " \tinto output dir outdir.\n") 62 os.Exit(2) 63} 64 65func (s *sstate) Setup() { 66 if *indirsflag == "" { 67 usage("select input directories with '-i' option") 68 } 69 indirs := strings.Split(*indirsflag, ",") 70 if s.mode == subtractMode && len(indirs) != 2 { 71 usage("supply exactly two input dirs for subtract operation") 72 } 73 if *outdirflag == "" { 74 usage("select output directory with '-o' option") 75 } 76} 77 78func (s *sstate) BeginPod(p pods.Pod) { 79 s.mm.beginPod() 80} 81 82func (s *sstate) EndPod(p pods.Pod) { 83 const pcombine = false 84 s.mm.endPod(pcombine) 85} 86 87func (s *sstate) EndCounters() { 88 if s.imm != nil { 89 s.pruneCounters() 90 } 91} 92 93// pruneCounters performs a function-level partial intersection using the 94// current POD counter data (s.mm.pod.pmm) and the intersected data from 95// PODs in previous dirs (s.imm). 96func (s *sstate) pruneCounters() { 97 pkeys := make([]pkfunc, 0, len(s.mm.pod.pmm)) 98 for k := range s.mm.pod.pmm { 99 pkeys = append(pkeys, k) 100 } 101 // Remove anything from pmm not found in imm. We don't need to 102 // go the other way (removing things from imm not found in pmm) 103 // since we don't add anything to imm if there is no pmm entry. 104 for _, k := range pkeys { 105 if _, found := s.imm[k]; !found { 106 delete(s.mm.pod.pmm, k) 107 } 108 } 109 s.imm = nil 110} 111 112func (s *sstate) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) { 113 dbgtrace(2, "visiting counter data file %s diridx %d", cdf, dirIdx) 114 if s.inidx != dirIdx { 115 if s.inidx > dirIdx { 116 // We're relying on having data files presented in 117 // the order they appear in the inputs (e.g. first all 118 // data files from input dir 0, then dir 1, etc). 119 panic("decreasing dir index, internal error") 120 } 121 if dirIdx == 0 { 122 // No need to keep track of the functions in the first 123 // directory, since that info will be replicated in 124 // s.mm.pod.pmm. 125 s.imm = nil 126 } else { 127 // We're now starting to visit the Nth directory, N != 0. 128 if s.mode == intersectMode { 129 if s.imm != nil { 130 s.pruneCounters() 131 } 132 s.imm = make(map[pkfunc]struct{}) 133 } 134 } 135 s.inidx = dirIdx 136 } 137} 138 139func (s *sstate) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) { 140} 141 142func (s *sstate) VisitFuncCounterData(data decodecounter.FuncPayload) { 143 key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} 144 145 if *verbflag >= 5 { 146 fmt.Printf("ctr visit fid=%d pk=%d inidx=%d data.Counters=%+v\n", data.FuncIdx, data.PkgIdx, s.inidx, data.Counters) 147 } 148 149 // If we're processing counter data from the initial (first) input 150 // directory, then just install it into the counter data map 151 // as usual. 152 if s.inidx == 0 { 153 s.mm.visitFuncCounterData(data) 154 return 155 } 156 157 // If we're looking at counter data from a dir other than 158 // the first, then perform the intersect/subtract. 159 if val, ok := s.mm.pod.pmm[key]; ok { 160 if s.mode == subtractMode { 161 for i := 0; i < len(data.Counters); i++ { 162 if data.Counters[i] != 0 { 163 val.Counters[i] = 0 164 } 165 } 166 } else if s.mode == intersectMode { 167 s.imm[key] = struct{}{} 168 for i := 0; i < len(data.Counters); i++ { 169 if data.Counters[i] == 0 { 170 val.Counters[i] = 0 171 } 172 } 173 } 174 } 175} 176 177func (s *sstate) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) { 178 if s.mode == intersectMode { 179 s.imm = make(map[pkfunc]struct{}) 180 } 181 s.mm.visitMetaDataFile(mdf, mfr) 182} 183 184func (s *sstate) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) { 185 s.mm.visitPackage(pd, pkgIdx, false) 186} 187 188func (s *sstate) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) { 189} 190 191func (s *sstate) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) { 192 s.mm.visitFunc(pkgIdx, fnIdx, fd, s.mode, false) 193} 194 195func (s *sstate) Finish() { 196} 197