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