1
2
3
4
5 package coverage
6
7 import (
8 "crypto/md5"
9 "fmt"
10 "internal/coverage"
11 "internal/coverage/encodecounter"
12 "internal/coverage/encodemeta"
13 "internal/coverage/rtcov"
14 "io"
15 "os"
16 "path/filepath"
17 "runtime"
18 "strconv"
19 "sync/atomic"
20 "time"
21 "unsafe"
22 )
23
24
25
26
27
28
29
30
31 func getCovMetaList() []rtcov.CovMetaBlob
32
33
34
35
36 func getCovCounterList() []rtcov.CovCounterBlob
37
38
39
40
41
42 func getCovPkgMap() map[int]int
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 type emitState struct {
65 mfname string
66 mftmp string
67 mf *os.File
68 cfname string
69 cftmp string
70 cf *os.File
71 outdir string
72
73
74 metalist []rtcov.CovMetaBlob
75
76
77 counterlist []rtcov.CovCounterBlob
78
79
80 pkgmap map[int]int
81
82
83 debug bool
84 }
85
86 var (
87
88
89
90 finalHash [16]byte
91
92 finalHashComputed bool
93
94 finalMetaLen uint64
95
96 metaDataEmitAttempted bool
97
98 cmode coverage.CounterMode
99
100 cgran coverage.CounterGranularity
101
102 goCoverDir string
103
104 capturedOsArgs map[string]string
105
106 covProfileAlreadyEmitted bool
107 )
108
109
110
111 type fileType int
112
113 const (
114 noFile = 1 << iota
115 metaDataFile
116 counterDataFile
117 )
118
119
120
121
122 func emitMetaData() {
123 if covProfileAlreadyEmitted {
124 return
125 }
126 ml, err := prepareForMetaEmit()
127 if err != nil {
128 fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", err)
129 if os.Getenv("GOCOVERDEBUG") != "" {
130 panic("meta-data write failure")
131 }
132 }
133 if len(ml) == 0 {
134 fmt.Fprintf(os.Stderr, "program not built with -cover\n")
135 return
136 }
137
138 goCoverDir = os.Getenv("GOCOVERDIR")
139 if goCoverDir == "" {
140 fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n")
141 return
142 }
143
144 if err := emitMetaDataToDirectory(goCoverDir, ml); err != nil {
145 fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", err)
146 if os.Getenv("GOCOVERDEBUG") != "" {
147 panic("meta-data write failure")
148 }
149 }
150 }
151
152 func modeClash(m coverage.CounterMode) bool {
153 if m == coverage.CtrModeRegOnly || m == coverage.CtrModeTestMain {
154 return false
155 }
156 if cmode == coverage.CtrModeInvalid {
157 cmode = m
158 return false
159 }
160 return cmode != m
161 }
162
163 func granClash(g coverage.CounterGranularity) bool {
164 if cgran == coverage.CtrGranularityInvalid {
165 cgran = g
166 return false
167 }
168 return cgran != g
169 }
170
171
172
173
174 func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) {
175
176 ml := getCovMetaList()
177
178
179
180
181
182
183 if len(ml) == 0 {
184 return nil, nil
185 }
186
187 s := &emitState{
188 metalist: ml,
189 debug: os.Getenv("GOCOVERDEBUG") != "",
190 }
191
192
193
194 capturedOsArgs = captureOsArgs()
195
196 if s.debug {
197 fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR"))
198 fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n")
199 for k, b := range ml {
200 fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", k, b.PkgPath)
201 if b.PkgID != -1 {
202 fmt.Fprintf(os.Stderr, " hcid: %d", b.PkgID)
203 }
204 fmt.Fprintf(os.Stderr, "\n")
205 }
206 pm := getCovPkgMap()
207 fmt.Fprintf(os.Stderr, "=+= remap table:\n")
208 for from, to := range pm {
209 fmt.Fprintf(os.Stderr, "=+= from %d to %d\n",
210 uint32(from), uint32(to))
211 }
212 }
213
214 h := md5.New()
215 tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
216 for _, entry := range ml {
217 if _, err := h.Write(entry.Hash[:]); err != nil {
218 return nil, err
219 }
220 tlen += uint64(entry.Len)
221 ecm := coverage.CounterMode(entry.CounterMode)
222 if modeClash(ecm) {
223 return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", ml[0].PkgPath, cmode, entry.PkgPath, ecm)
224 }
225 ecg := coverage.CounterGranularity(entry.CounterGranularity)
226 if granClash(ecg) {
227 return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", ml[0].PkgPath, cgran, entry.PkgPath, ecg)
228 }
229 }
230
231
232 h.Write([]byte(cmode.String()))
233 h.Write([]byte(cgran.String()))
234
235
236 fh := h.Sum(nil)
237 copy(finalHash[:], fh)
238 finalHashComputed = true
239 finalMetaLen = tlen
240
241 return ml, nil
242 }
243
244
245
246 func emitMetaDataToDirectory(outdir string, ml []rtcov.CovMetaBlob) error {
247 ml, err := prepareForMetaEmit()
248 if err != nil {
249 return err
250 }
251 if len(ml) == 0 {
252 return nil
253 }
254
255 metaDataEmitAttempted = true
256
257 s := &emitState{
258 metalist: ml,
259 debug: os.Getenv("GOCOVERDEBUG") != "",
260 outdir: outdir,
261 }
262
263
264 if err := s.openOutputFiles(finalHash, finalMetaLen, metaDataFile); err != nil {
265 return err
266 }
267
268
269 if s.needMetaDataFile() {
270 if err := s.emitMetaDataFile(finalHash, finalMetaLen); err != nil {
271 return err
272 }
273 }
274 return nil
275 }
276
277
278
279
280 func emitCounterData() {
281 if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted {
282 return
283 }
284 if err := emitCounterDataToDirectory(goCoverDir); err != nil {
285 fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", err)
286 if os.Getenv("GOCOVERDEBUG") != "" {
287 panic("counter-data write failure")
288 }
289 }
290 }
291
292
293 func emitCounterDataToDirectory(outdir string) error {
294
295 cl := getCovCounterList()
296 if len(cl) == 0 {
297
298 return nil
299 }
300
301 if !finalHashComputed {
302 return fmt.Errorf("error: meta-data not available (binary not built with -cover?)")
303 }
304
305
306 pm := getCovPkgMap()
307 s := &emitState{
308 counterlist: cl,
309 pkgmap: pm,
310 outdir: outdir,
311 debug: os.Getenv("GOCOVERDEBUG") != "",
312 }
313
314
315 if err := s.openOutputFiles(finalHash, finalMetaLen, counterDataFile); err != nil {
316 return err
317 }
318 if s.cf == nil {
319 return fmt.Errorf("counter data output file open failed (no additional info")
320 }
321
322
323 if err := s.emitCounterDataFile(finalHash, s.cf); err != nil {
324 return err
325 }
326 if err := s.cf.Close(); err != nil {
327 return fmt.Errorf("closing counter data file: %v", err)
328 }
329
330
331
332 if err := os.Rename(s.cftmp, s.cfname); err != nil {
333 return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.cfname, s.cftmp, err)
334 }
335
336 return nil
337 }
338
339
340 func (s *emitState) emitCounterDataToWriter(w io.Writer) error {
341 if err := s.emitCounterDataFile(finalHash, w); err != nil {
342 return err
343 }
344 return nil
345 }
346
347
348
349
350
351
352 func (s *emitState) openMetaFile(metaHash [16]byte, metaLen uint64) error {
353
354
355 fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, metaHash)
356 s.mfname = filepath.Join(s.outdir, fn)
357 fi, err := os.Stat(s.mfname)
358 if err != nil || fi.Size() != int64(metaLen) {
359
360 tname := "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10)
361 s.mftmp = filepath.Join(s.outdir, tname)
362 s.mf, err = os.Create(s.mftmp)
363 if err != nil {
364 return fmt.Errorf("creating meta-data file %s: %v", s.mftmp, err)
365 }
366 }
367 return nil
368 }
369
370
371
372
373 func (s *emitState) openCounterFile(metaHash [16]byte) error {
374 processID := os.Getpid()
375 fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, processID, time.Now().UnixNano())
376 s.cfname = filepath.Join(s.outdir, fn)
377 s.cftmp = filepath.Join(s.outdir, "tmp."+fn)
378 var err error
379 s.cf, err = os.Create(s.cftmp)
380 if err != nil {
381 return fmt.Errorf("creating counter data file %s: %v", s.cftmp, err)
382 }
383 return nil
384 }
385
386
387
388
389
390
391
392
393
394
395
396
397 func (s *emitState) openOutputFiles(metaHash [16]byte, metaLen uint64, which fileType) error {
398 fi, err := os.Stat(s.outdir)
399 if err != nil {
400 return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", s.outdir, err)
401 }
402 if !fi.IsDir() {
403 return fmt.Errorf("output directory %q not a directory; no coverage data written", s.outdir)
404 }
405
406 if (which & metaDataFile) != 0 {
407 if err := s.openMetaFile(metaHash, metaLen); err != nil {
408 return err
409 }
410 }
411 if (which & counterDataFile) != 0 {
412 if err := s.openCounterFile(metaHash); err != nil {
413 return err
414 }
415 }
416 return nil
417 }
418
419
420
421
422 func (s *emitState) emitMetaDataFile(finalHash [16]byte, tlen uint64) error {
423 if err := writeMetaData(s.mf, s.metalist, cmode, cgran, finalHash); err != nil {
424 return fmt.Errorf("writing %s: %v\n", s.mftmp, err)
425 }
426 if err := s.mf.Close(); err != nil {
427 return fmt.Errorf("closing meta data temp file: %v", err)
428 }
429
430
431
432 if err := os.Rename(s.mftmp, s.mfname); err != nil {
433 return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.mfname, s.mftmp, err)
434 }
435
436 return nil
437 }
438
439
440
441
442 func (s *emitState) needMetaDataFile() bool {
443 return s.mf != nil
444 }
445
446 func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.CounterMode, gran coverage.CounterGranularity, finalHash [16]byte) error {
447 mfw := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", w)
448
449 var blobs [][]byte
450 for _, e := range metalist {
451 sd := unsafe.Slice(e.P, int(e.Len))
452 blobs = append(blobs, sd)
453 }
454 return mfw.Write(finalHash, blobs, cmode, gran)
455 }
456
457 func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
458 var tcounters []uint32
459
460 rdCounters := func(actrs []atomic.Uint32, ctrs []uint32) []uint32 {
461 ctrs = ctrs[:0]
462 for i := range actrs {
463 ctrs = append(ctrs, actrs[i].Load())
464 }
465 return ctrs
466 }
467
468 dpkg := uint32(0)
469 for _, c := range s.counterlist {
470 sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), int(c.Len))
471 for i := 0; i < len(sd); i++ {
472
473 sdi := sd[i].Load()
474 if sdi == 0 {
475 continue
476 }
477
478
479 nCtrs := sd[i+coverage.NumCtrsOffset].Load()
480 pkgId := sd[i+coverage.PkgIdOffset].Load()
481 funcId := sd[i+coverage.FuncIdOffset].Load()
482 cst := i + coverage.FirstCtrOffset
483 counters := sd[cst : cst+int(nCtrs)]
484
485
486
487
488 isLive := false
489 for i := 0; i < len(counters); i++ {
490 if counters[i].Load() != 0 {
491 isLive = true
492 break
493 }
494 }
495 if !isLive {
496
497 i += coverage.FirstCtrOffset + int(nCtrs) - 1
498 continue
499 }
500
501 if s.debug {
502 if pkgId != dpkg {
503 dpkg = pkgId
504 fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn",
505 i, pkgId)
506 }
507 fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", i, funcId, nCtrs)
508 }
509
510
511
512
513
514
515
516 ipk := int32(pkgId)
517 if ipk == 0 {
518 fmt.Fprintf(os.Stderr, "\n")
519 reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
520 } else if ipk < 0 {
521 if newId, ok := s.pkgmap[int(ipk)]; ok {
522 pkgId = uint32(newId)
523 } else {
524 fmt.Fprintf(os.Stderr, "\n")
525 reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
526 }
527 } else {
528
529
530
531
532
533 pkgId--
534 }
535
536 tcounters = rdCounters(counters, tcounters)
537 if err := f(pkgId, funcId, tcounters); err != nil {
538 return err
539 }
540
541
542 i += coverage.FirstCtrOffset + int(nCtrs) - 1
543 }
544 if s.debug {
545 fmt.Fprintf(os.Stderr, "\n")
546 }
547 }
548 return nil
549 }
550
551
552
553
554
555
556 func captureOsArgs() map[string]string {
557 m := make(map[string]string)
558 m["argc"] = strconv.Itoa(len(os.Args))
559 for k, a := range os.Args {
560 m[fmt.Sprintf("argv%d", k)] = a
561 }
562 m["GOOS"] = runtime.GOOS
563 m["GOARCH"] = runtime.GOARCH
564 return m
565 }
566
567
568
569 func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
570 cfw := encodecounter.NewCoverageDataWriter(w, coverage.CtrULeb128)
571 if err := cfw.Write(finalHash, capturedOsArgs, s); err != nil {
572 return err
573 }
574 return nil
575 }
576
577
578
579
580
581
582
583 func markProfileEmitted(val bool) {
584 covProfileAlreadyEmitted = val
585 }
586
587 func reportErrorInHardcodedList(slot, pkgID int32, fnID, nCtrs uint32) {
588 metaList := getCovMetaList()
589 pkgMap := getCovPkgMap()
590
591 println("internal error in coverage meta-data tracking:")
592 println("encountered bad pkgID:", pkgID, " at slot:", slot,
593 " fnID:", fnID, " numCtrs:", nCtrs)
594 println("list of hard-coded runtime package IDs needs revising.")
595 println("[see the comment on the 'rtPkgs' var in ")
596 println(" <goroot>/src/internal/coverage/pkid.go]")
597 println("registered list:")
598 for k, b := range metaList {
599 print("slot: ", k, " path='", b.PkgPath, "' ")
600 if b.PkgID != -1 {
601 print(" hard-coded id: ", b.PkgID)
602 }
603 println("")
604 }
605 println("remap table:")
606 for from, to := range pkgMap {
607 println("from ", from, " to ", to)
608 }
609 }
610
View as plain text