Source file
src/cmd/cover/cover.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "cmd/internal/cov/covcmd"
10 "cmp"
11 "encoding/json"
12 "flag"
13 "fmt"
14 "go/ast"
15 "go/parser"
16 "go/scanner"
17 "go/token"
18 "internal/coverage"
19 "internal/coverage/encodemeta"
20 "internal/coverage/slicewriter"
21 "io"
22 "log"
23 "os"
24 "path/filepath"
25 "slices"
26 "strconv"
27 "strings"
28
29 "cmd/internal/edit"
30 "cmd/internal/objabi"
31 "cmd/internal/telemetry/counter"
32 )
33
34 const usageMessage = "" +
35 `Usage of 'go tool cover':
36 Given a coverage profile produced by 'go test':
37 go test -coverprofile=c.out
38
39 Open a web browser displaying annotated source code:
40 go tool cover -html=c.out
41
42 Write out an HTML file instead of launching a web browser:
43 go tool cover -html=c.out -o coverage.html
44
45 Display coverage percentages to stdout for each function:
46 go tool cover -func=c.out
47
48 Finally, to generate modified source code with coverage annotations
49 for a package (what go test -cover does):
50 go tool cover -mode=set -var=CoverageVariableName \
51 -pkgcfg=<config> -outfilelist=<file> file1.go ... fileN.go
52
53 where -pkgcfg points to a file containing the package path,
54 package name, module path, and related info from "go build",
55 and -outfilelist points to a file containing the filenames
56 of the instrumented output files (one per input file).
57 See https://pkg.go.dev/cmd/internal/cov/covcmd#CoverPkgConfig for
58 more on the package config.
59 `
60
61 func usage() {
62 fmt.Fprint(os.Stderr, usageMessage)
63 fmt.Fprintln(os.Stderr, "\nFlags:")
64 flag.PrintDefaults()
65 fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.")
66 os.Exit(2)
67 }
68
69 var (
70 mode = flag.String("mode", "", "coverage mode: set, count, atomic")
71 varVar = flag.String("var", "GoCover", "name of coverage variable to generate")
72 output = flag.String("o", "", "file for output")
73 outfilelist = flag.String("outfilelist", "", "file containing list of output files (one per line) if -pkgcfg is in use")
74 htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
75 funcOut = flag.String("func", "", "output coverage profile information for each function")
76 pkgcfg = flag.String("pkgcfg", "", "enable full-package instrumentation mode using params from specified config file")
77 pkgconfig covcmd.CoverPkgConfig
78 outputfiles []string
79 profile string
80 counterStmt func(*File, string) string
81 covervarsoutfile string
82 cmode coverage.CounterMode
83 cgran coverage.CounterGranularity
84 )
85
86 const (
87 atomicPackagePath = "sync/atomic"
88 atomicPackageName = "_cover_atomic_"
89 )
90
91 func main() {
92 counter.Open()
93
94 objabi.AddVersionFlag()
95 flag.Usage = usage
96 objabi.Flagparse(usage)
97 counter.Inc("cover/invocations")
98 counter.CountFlags("cover/flag:", *flag.CommandLine)
99
100
101 if flag.NFlag() == 0 && flag.NArg() == 0 {
102 flag.Usage()
103 }
104
105 err := parseFlags()
106 if err != nil {
107 fmt.Fprintln(os.Stderr, err)
108 fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
109 os.Exit(2)
110 }
111
112
113 if *mode != "" {
114 annotate(flag.Args())
115 return
116 }
117
118
119 if *htmlOut != "" {
120 err = htmlOutput(profile, *output)
121 } else {
122 err = funcOutput(profile, *output)
123 }
124
125 if err != nil {
126 fmt.Fprintf(os.Stderr, "cover: %v\n", err)
127 os.Exit(2)
128 }
129 }
130
131
132 func parseFlags() error {
133 profile = *htmlOut
134 if *funcOut != "" {
135 if profile != "" {
136 return fmt.Errorf("too many options")
137 }
138 profile = *funcOut
139 }
140
141
142 if (profile == "") == (*mode == "") {
143 return fmt.Errorf("too many options")
144 }
145
146 if *varVar != "" && !token.IsIdentifier(*varVar) {
147 return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
148 }
149
150 if *mode != "" {
151 switch *mode {
152 case "set":
153 counterStmt = setCounterStmt
154 cmode = coverage.CtrModeSet
155 case "count":
156 counterStmt = incCounterStmt
157 cmode = coverage.CtrModeCount
158 case "atomic":
159 counterStmt = atomicCounterStmt
160 cmode = coverage.CtrModeAtomic
161 case "regonly":
162 counterStmt = nil
163 cmode = coverage.CtrModeRegOnly
164 case "testmain":
165 counterStmt = nil
166 cmode = coverage.CtrModeTestMain
167 default:
168 return fmt.Errorf("unknown -mode %v", *mode)
169 }
170
171 if flag.NArg() == 0 {
172 return fmt.Errorf("missing source file(s)")
173 } else {
174 if *pkgcfg != "" {
175 if *output != "" {
176 return fmt.Errorf("please use '-outfilelist' flag instead of '-o'")
177 }
178 var err error
179 if outputfiles, err = readOutFileList(*outfilelist); err != nil {
180 return err
181 }
182 covervarsoutfile = outputfiles[0]
183 outputfiles = outputfiles[1:]
184 numInputs := len(flag.Args())
185 numOutputs := len(outputfiles)
186 if numOutputs != numInputs {
187 return fmt.Errorf("number of output files (%d) not equal to number of input files (%d)", numOutputs, numInputs)
188 }
189 if err := readPackageConfig(*pkgcfg); err != nil {
190 return err
191 }
192 return nil
193 } else {
194 if *outfilelist != "" {
195 return fmt.Errorf("'-outfilelist' flag applicable only when -pkgcfg used")
196 }
197 }
198 if flag.NArg() == 1 {
199 return nil
200 }
201 }
202 } else if flag.NArg() == 0 {
203 return nil
204 }
205 return fmt.Errorf("too many arguments")
206 }
207
208 func readOutFileList(path string) ([]string, error) {
209 data, err := os.ReadFile(path)
210 if err != nil {
211 return nil, fmt.Errorf("error reading -outfilelist file %q: %v", path, err)
212 }
213 return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
214 }
215
216 func readPackageConfig(path string) error {
217 data, err := os.ReadFile(path)
218 if err != nil {
219 return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)
220 }
221 if err := json.Unmarshal(data, &pkgconfig); err != nil {
222 return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)
223 }
224 switch pkgconfig.Granularity {
225 case "perblock":
226 cgran = coverage.CtrGranularityPerBlock
227 case "perfunc":
228 cgran = coverage.CtrGranularityPerFunc
229 default:
230 return fmt.Errorf(`%s: pkgconfig requires perblock/perfunc value`, path)
231 }
232 return nil
233 }
234
235
236
237
238 type Block struct {
239 startByte token.Pos
240 endByte token.Pos
241 numStmt int
242 }
243
244
245 type Package struct {
246 mdb *encodemeta.CoverageMetaDataBuilder
247 counterLengths []int
248 }
249
250
251 type Func struct {
252 units []coverage.CoverableUnit
253 counterVar string
254 }
255
256
257
258 type File struct {
259 fset *token.FileSet
260 name string
261 astFile *ast.File
262 blocks []Block
263 content []byte
264 edit *edit.Buffer
265 mdb *encodemeta.CoverageMetaDataBuilder
266 fn Func
267 pkg *Package
268 }
269
270
271 type Range struct {
272 pos token.Pos
273 end token.Pos
274 }
275
276
277
278
279
280
281 func (f *File) codeRanges(start, end token.Pos) []Range {
282 var (
283 startOffset = f.offset(start)
284 endOffset = f.offset(end)
285 src = f.content[startOffset:endOffset]
286 origFile = f.fset.File(start)
287 )
288
289
290
291
292
293 scanFile := token.NewFileSet().AddFile("", -1, len(src))
294
295 var s scanner.Scanner
296 s.Init(scanFile, src, nil, 0)
297
298
299
300
301
302
303
304
305 var ranges []Range
306 var codeStart token.Pos
307 prevEndLine := 0
308
309 for {
310 pos, tok, lit := s.Scan()
311 if tok == token.EOF {
312 break
313 }
314
315
316
317
318
319
320
321
322
323 if tok == token.LBRACE || tok == token.RBRACE {
324 continue
325 }
326 if tok == token.SEMICOLON && lit == "\n" {
327 continue
328 }
329
330
331 startLine := scanFile.PositionFor(pos, false).Line
332 endLine := startLine
333 if tok == token.STRING {
334
335 endLine = scanFile.PositionFor(s.End(), false).Line
336 }
337
338 if prevEndLine == 0 {
339
340 codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))
341 } else if startLine > prevEndLine+1 {
342
343 codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))
344 ranges = append(ranges, Range{pos: codeStart, end: codeEnd})
345 codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))
346 }
347
348 if endLine > prevEndLine {
349 prevEndLine = endLine
350 }
351 }
352
353
354 if prevEndLine > 0 {
355 if prevEndLine < scanFile.LineCount() {
356
357
358 codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))
359 ranges = append(ranges, Range{pos: codeStart, end: codeEnd})
360 } else {
361 ranges = append(ranges, Range{pos: codeStart, end: end})
362 }
363 }
364
365
366
367
368 if len(ranges) == 0 {
369 return []Range{{pos: start, end: start}}
370 }
371
372 return ranges
373 }
374
375
376
377 func insideStatement(pos token.Pos, stmts []ast.Stmt) bool {
378
379 i, _ := slices.BinarySearchFunc(stmts, pos, func(s ast.Stmt, p token.Pos) int {
380 return cmp.Compare(s.Pos(), p)
381 })
382
383 return i > 0 && pos < stmts[i-1].End()
384 }
385
386
387
388
389 type rangeWithStatements struct {
390 Range
391 numStmt int
392 }
393
394 func mergeRangesWithinStatements(ranges []Range, stmts []ast.Stmt) []rangeWithStatements {
395 merged := make([]rangeWithStatements, 0, len(ranges))
396 for _, r := range ranges {
397
398
399 first, _ := slices.BinarySearchFunc(stmts, r.pos, func(s ast.Stmt, p token.Pos) int {
400 return cmp.Compare(s.Pos(), p)
401 })
402 last, _ := slices.BinarySearchFunc(stmts, r.end, func(s ast.Stmt, p token.Pos) int {
403 return cmp.Compare(s.Pos(), p)
404 })
405 numStmt := last - first
406
407 if len(merged) > 0 && insideStatement(r.pos, stmts) {
408
409 last := &merged[len(merged)-1]
410 last.end = r.end
411 last.numStmt += numStmt
412 } else {
413 merged = append(merged, rangeWithStatements{Range: r, numStmt: numStmt})
414 }
415 }
416 return merged
417 }
418
419
420
421
422
423 func (f *File) findText(pos token.Pos, text string) int {
424 b := []byte(text)
425 start := f.offset(pos)
426 i := start
427 s := f.content
428 for i < len(s) {
429 if bytes.HasPrefix(s[i:], b) {
430 return i
431 }
432 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
433 for i < len(s) && s[i] != '\n' {
434 i++
435 }
436 continue
437 }
438 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
439 for i += 2; ; i++ {
440 if i+2 > len(s) {
441 return 0
442 }
443 if s[i] == '*' && s[i+1] == '/' {
444 i += 2
445 break
446 }
447 }
448 continue
449 }
450 i++
451 }
452 return -1
453 }
454
455
456 func (f *File) Visit(node ast.Node) ast.Visitor {
457 switch n := node.(type) {
458 case *ast.BlockStmt:
459
460 if len(n.List) > 0 {
461 switch n.List[0].(type) {
462 case *ast.CaseClause:
463 for _, n := range n.List {
464 clause := n.(*ast.CaseClause)
465 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
466 }
467 return f
468 case *ast.CommClause:
469 for _, n := range n.List {
470 clause := n.(*ast.CommClause)
471 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
472 }
473 return f
474 }
475 }
476 f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true)
477 case *ast.IfStmt:
478 if n.Init != nil {
479 ast.Walk(f, n.Init)
480 }
481 ast.Walk(f, n.Cond)
482 ast.Walk(f, n.Body)
483 if n.Else == nil {
484 return nil
485 }
486
487
488
489
490
491
492
493
494
495
496
497 elseOffset := f.findText(n.Body.End(), "else")
498 if elseOffset < 0 {
499 panic("lost else")
500 }
501 f.edit.Insert(elseOffset+4, "{")
502 f.edit.Insert(f.offset(n.Else.End()), "}")
503
504
505
506
507
508 pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
509 switch stmt := n.Else.(type) {
510 case *ast.IfStmt:
511 block := &ast.BlockStmt{
512 Lbrace: pos,
513 List: []ast.Stmt{stmt},
514 Rbrace: stmt.End(),
515 }
516 n.Else = block
517 case *ast.BlockStmt:
518 stmt.Lbrace = pos
519 default:
520 panic("unexpected node type in if")
521 }
522 ast.Walk(f, n.Else)
523 return nil
524 case *ast.SelectStmt:
525
526 if n.Body == nil || len(n.Body.List) == 0 {
527 return nil
528 }
529 case *ast.SwitchStmt:
530
531 if n.Body == nil || len(n.Body.List) == 0 {
532 if n.Init != nil {
533 ast.Walk(f, n.Init)
534 }
535 if n.Tag != nil {
536 ast.Walk(f, n.Tag)
537 }
538 return nil
539 }
540 case *ast.TypeSwitchStmt:
541
542 if n.Body == nil || len(n.Body.List) == 0 {
543 if n.Init != nil {
544 ast.Walk(f, n.Init)
545 }
546 ast.Walk(f, n.Assign)
547 return nil
548 }
549 case *ast.FuncDecl:
550
551
552 if n.Name.Name == "_" || n.Body == nil {
553 return nil
554 }
555 fname := n.Name.Name
556
557
558
559
560
561
562
563
564
565
566
567
568 if atomicOnAtomic() && (fname == "AddUint32" || fname == "StoreUint32") {
569 return nil
570 }
571
572 if r := n.Recv; r != nil && len(r.List) == 1 {
573 t := r.List[0].Type
574 star := ""
575 if p, _ := t.(*ast.StarExpr); p != nil {
576 t = p.X
577 star = "*"
578 }
579 if p, _ := t.(*ast.Ident); p != nil {
580 fname = star + p.Name + "." + fname
581 }
582 }
583 walkBody := true
584 if *pkgcfg != "" {
585 f.preFunc(n, fname)
586 if pkgconfig.Granularity == "perfunc" {
587 walkBody = false
588 }
589 }
590 if walkBody {
591 ast.Walk(f, n.Body)
592 }
593 if *pkgcfg != "" {
594 flit := false
595 f.postFunc(n, fname, flit, n.Body)
596 }
597 return nil
598 case *ast.FuncLit:
599
600
601 if f.fn.counterVar != "" {
602 return f
603 }
604
605
606
607
608 pos := n.Pos()
609 p := f.fset.File(pos).Position(pos)
610 fname := fmt.Sprintf("func.L%d.C%d", p.Line, p.Column)
611 if *pkgcfg != "" {
612 f.preFunc(n, fname)
613 }
614 if pkgconfig.Granularity != "perfunc" {
615 ast.Walk(f, n.Body)
616 }
617 if *pkgcfg != "" {
618 flit := true
619 f.postFunc(n, fname, flit, n.Body)
620 }
621 return nil
622 }
623 return f
624 }
625
626 func mkCounterVarName(idx int) string {
627 return fmt.Sprintf("%s_%d", *varVar, idx)
628 }
629
630 func mkPackageIdVar() string {
631 return *varVar + "P"
632 }
633
634 func mkMetaVar() string {
635 return *varVar + "M"
636 }
637
638 func mkPackageIdExpression() string {
639 ppath := pkgconfig.PkgPath
640 if hcid := coverage.HardCodedPkgID(ppath); hcid != -1 {
641 return fmt.Sprintf("uint32(%d)", uint32(hcid))
642 }
643 return mkPackageIdVar()
644 }
645
646 func (f *File) preFunc(fn ast.Node, fname string) {
647 f.fn.units = f.fn.units[:0]
648
649
650 cv := mkCounterVarName(len(f.pkg.counterLengths))
651 f.fn.counterVar = cv
652 }
653
654 func (f *File) postFunc(fn ast.Node, funcname string, flit bool, body *ast.BlockStmt) {
655
656
657 singleCtr := ""
658 if pkgconfig.Granularity == "perfunc" {
659 singleCtr = "; " + f.newCounter(fn.Pos(), fn.Pos(), 1)
660 }
661
662
663 nc := len(f.fn.units) + coverage.FirstCtrOffset
664 f.pkg.counterLengths = append(f.pkg.counterLengths, nc)
665
666
667
668 fnpos := f.fset.Position(fn.Pos())
669 ppath := pkgconfig.PkgPath
670 filename := ppath + "/" + filepath.Base(fnpos.Filename)
671
672
673
674
675
676
677
678 if pkgconfig.Local {
679 filename = f.name
680 }
681
682
683 fd := coverage.FuncDesc{
684 Funcname: funcname,
685 Srcfile: filename,
686 Units: f.fn.units,
687 Lit: flit,
688 }
689 funcId := f.mdb.AddFunc(fd)
690
691 hookWrite := func(cv string, which int, val string) string {
692 return fmt.Sprintf("%s[%d] = %s", cv, which, val)
693 }
694 if *mode == "atomic" {
695 hookWrite = func(cv string, which int, val string) string {
696 return fmt.Sprintf("%sStoreUint32(&%s[%d], %s)",
697 atomicPackagePrefix(), cv, which, val)
698 }
699 }
700
701
702
703
704
705
706
707
708 cv := f.fn.counterVar
709 regHook := hookWrite(cv, 0, strconv.Itoa(len(f.fn.units))) + " ; " +
710 hookWrite(cv, 1, mkPackageIdExpression()) + " ; " +
711 hookWrite(cv, 2, strconv.Itoa(int(funcId))) + singleCtr
712
713
714
715
716
717 boff := f.offset(body.Pos())
718 ipos := f.fset.File(body.Pos()).Pos(boff)
719 ip := f.offset(ipos)
720 f.edit.Replace(ip, ip+1, string(f.content[ipos-1])+regHook+" ; ")
721
722 f.fn.counterVar = ""
723 }
724
725 func annotate(names []string) {
726 var p *Package
727 if *pkgcfg != "" {
728 pp := pkgconfig.PkgPath
729 pn := pkgconfig.PkgName
730 mp := pkgconfig.ModulePath
731 mdb, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)
732 if err != nil {
733 log.Fatalf("creating coverage meta-data builder: %v\n", err)
734 }
735 p = &Package{
736 mdb: mdb,
737 }
738 }
739
740 for k, name := range names {
741 if strings.ContainsAny(name, "\r\n") {
742
743 log.Fatalf("cover: input path contains newline character: %q", name)
744 }
745
746 fd := os.Stdout
747 isStdout := true
748 if *pkgcfg != "" {
749 var err error
750 fd, err = os.Create(outputfiles[k])
751 if err != nil {
752 log.Fatalf("cover: %s", err)
753 }
754 isStdout = false
755 } else if *output != "" {
756 var err error
757 fd, err = os.Create(*output)
758 if err != nil {
759 log.Fatalf("cover: %s", err)
760 }
761 isStdout = false
762 }
763 p.annotateFile(name, fd)
764 if !isStdout {
765 if err := fd.Close(); err != nil {
766 log.Fatalf("cover: %s", err)
767 }
768 }
769 }
770
771 if *pkgcfg != "" {
772 fd, err := os.Create(covervarsoutfile)
773 if err != nil {
774 log.Fatalf("cover: %s", err)
775 }
776 p.emitMetaData(fd)
777 if err := fd.Close(); err != nil {
778 log.Fatalf("cover: %s", err)
779 }
780 }
781 }
782
783 func (p *Package) annotateFile(name string, fd io.Writer) {
784 fset := token.NewFileSet()
785 content, err := os.ReadFile(name)
786 if err != nil {
787 log.Fatalf("cover: %s: %s", name, err)
788 }
789 parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments|parser.SkipObjectResolution)
790 if err != nil {
791 log.Fatalf("cover: %s: %s", name, err)
792 }
793
794 file := &File{
795 fset: fset,
796 name: name,
797 content: content,
798 edit: edit.NewBuffer(content),
799 astFile: parsedFile,
800 }
801 if p != nil {
802 file.mdb = p.mdb
803 file.pkg = p
804 }
805
806 if *mode == "atomic" {
807
808
809
810
811
812
813
814 if pkgconfig.PkgPath != "sync/atomic" {
815 file.edit.Insert(file.offset(file.astFile.Name.End()),
816 fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
817 }
818 }
819 if pkgconfig.PkgName == "main" {
820 file.edit.Insert(file.offset(file.astFile.Name.End()),
821 "; import _ \"runtime/coverage\"")
822 }
823
824 if counterStmt != nil {
825 ast.Walk(file, file.astFile)
826 }
827 newContent := file.edit.Bytes()
828
829 if strings.ContainsAny(name, "\r\n") {
830
831
832 panic(fmt.Sprintf("annotateFile: name contains unexpected newline character: %q", name))
833 }
834 fmt.Fprintf(fd, "//line %s:1:1\n", name)
835 fd.Write(newContent)
836
837
838
839
840 file.addVariables(fd)
841
842
843
844 if *mode == "atomic" {
845 fmt.Fprintf(fd, "\nvar _ = %sLoadUint32\n", atomicPackagePrefix())
846 }
847 }
848
849
850 func setCounterStmt(f *File, counter string) string {
851 return fmt.Sprintf("%s = 1", counter)
852 }
853
854
855 func incCounterStmt(f *File, counter string) string {
856 return fmt.Sprintf("%s++", counter)
857 }
858
859
860 func atomicCounterStmt(f *File, counter string) string {
861 return fmt.Sprintf("%sAddUint32(&%s, 1)", atomicPackagePrefix(), counter)
862 }
863
864
865 func (f *File) newCounter(start, end token.Pos, numStmt int) string {
866 var stmt string
867 if *pkgcfg != "" {
868 slot := len(f.fn.units) + coverage.FirstCtrOffset
869 if f.fn.counterVar == "" {
870 panic("internal error: counter var unset")
871 }
872 stmt = counterStmt(f, fmt.Sprintf("%s[%d]", f.fn.counterVar, slot))
873
874 stpos := f.position(start)
875 enpos := f.position(end)
876 stpos, enpos = dedup(stpos, enpos)
877 unit := coverage.CoverableUnit{
878 StLine: uint32(stpos.Line),
879 StCol: uint32(stpos.Column),
880 EnLine: uint32(enpos.Line),
881 EnCol: uint32(enpos.Column),
882 NxStmts: uint32(numStmt),
883 }
884 f.fn.units = append(f.fn.units, unit)
885 } else {
886 stmt = counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar,
887 len(f.blocks)))
888 f.blocks = append(f.blocks, Block{start, end, numStmt})
889 }
890 return stmt
891 }
892
893
894
895
896
897
898
899
900
901
902
903
904
905 func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
906
907
908 if len(list) == 0 {
909 r := f.codeRanges(insertPos, blockEnd)[0]
910 f.edit.Insert(f.offset(r.pos), f.newCounter(r.pos, r.end, 0)+";")
911 return
912 }
913
914
915 list = append([]ast.Stmt(nil), list...)
916
917
918 for {
919
920
921 var last int
922 end := blockEnd
923 for last = 0; last < len(list); last++ {
924 stmt := list[last]
925 end = f.statementBoundary(stmt)
926 if f.endsBasicSourceBlock(stmt) {
927
928
929
930
931
932
933
934
935
936
937
938 if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
939 newLabel := *label
940 newLabel.Stmt = &ast.EmptyStmt{
941 Semicolon: label.Stmt.Pos(),
942 Implicit: true,
943 }
944 end = label.Pos()
945 list[last] = &newLabel
946
947 list = append(list, nil)
948 copy(list[last+1:], list[last:])
949 list[last+1] = label.Stmt
950 }
951 last++
952 extendToClosingBrace = false
953 break
954 }
955 }
956 if extendToClosingBrace {
957 end = blockEnd
958 }
959 if pos != end {
960
961
962
963 for i, r := range mergeRangesWithinStatements(f.codeRanges(pos, end), list[:last]) {
964 insertOffset := f.offset(r.pos)
965 if i == 0 {
966 insertOffset = f.offset(insertPos)
967 }
968 f.edit.Insert(insertOffset, f.newCounter(r.pos, r.end, r.numStmt)+";")
969 }
970 }
971 list = list[last:]
972 if len(list) == 0 {
973 break
974 }
975 pos = list[0].Pos()
976 insertPos = pos
977 }
978 }
979
980
981
982
983
984
985 func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
986 if n == nil {
987 return false, 0
988 }
989 var literal funcLitFinder
990 ast.Walk(&literal, n)
991 return literal.found(), token.Pos(literal)
992 }
993
994
995
996 func (f *File) statementBoundary(s ast.Stmt) token.Pos {
997
998 switch s := s.(type) {
999 case *ast.BlockStmt:
1000
1001 return s.Lbrace
1002 case *ast.IfStmt:
1003 found, pos := hasFuncLiteral(s.Init)
1004 if found {
1005 return pos
1006 }
1007 found, pos = hasFuncLiteral(s.Cond)
1008 if found {
1009 return pos
1010 }
1011 return s.Body.Lbrace
1012 case *ast.ForStmt:
1013 found, pos := hasFuncLiteral(s.Init)
1014 if found {
1015 return pos
1016 }
1017 found, pos = hasFuncLiteral(s.Cond)
1018 if found {
1019 return pos
1020 }
1021 found, pos = hasFuncLiteral(s.Post)
1022 if found {
1023 return pos
1024 }
1025 return s.Body.Lbrace
1026 case *ast.LabeledStmt:
1027 return f.statementBoundary(s.Stmt)
1028 case *ast.RangeStmt:
1029 found, pos := hasFuncLiteral(s.X)
1030 if found {
1031 return pos
1032 }
1033 return s.Body.Lbrace
1034 case *ast.SwitchStmt:
1035 found, pos := hasFuncLiteral(s.Init)
1036 if found {
1037 return pos
1038 }
1039 found, pos = hasFuncLiteral(s.Tag)
1040 if found {
1041 return pos
1042 }
1043 return s.Body.Lbrace
1044 case *ast.SelectStmt:
1045 return s.Body.Lbrace
1046 case *ast.TypeSwitchStmt:
1047 found, pos := hasFuncLiteral(s.Init)
1048 if found {
1049 return pos
1050 }
1051 return s.Body.Lbrace
1052 }
1053
1054
1055
1056
1057 found, pos := hasFuncLiteral(s)
1058 if found {
1059 return pos
1060 }
1061 return s.End()
1062 }
1063
1064
1065
1066
1067 func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
1068 switch s := s.(type) {
1069 case *ast.BlockStmt:
1070
1071 return true
1072 case *ast.BranchStmt:
1073 return true
1074 case *ast.ForStmt:
1075 return true
1076 case *ast.IfStmt:
1077 return true
1078 case *ast.LabeledStmt:
1079 return true
1080 case *ast.RangeStmt:
1081 return true
1082 case *ast.SwitchStmt:
1083 return true
1084 case *ast.SelectStmt:
1085 return true
1086 case *ast.TypeSwitchStmt:
1087 return true
1088 case *ast.ExprStmt:
1089
1090
1091
1092
1093 if call, ok := s.X.(*ast.CallExpr); ok {
1094 if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
1095 return true
1096 }
1097 }
1098 }
1099 found, _ := hasFuncLiteral(s)
1100 return found
1101 }
1102
1103
1104
1105 func (f *File) isControl(s ast.Stmt) bool {
1106 switch s.(type) {
1107 case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
1108 return true
1109 }
1110 return false
1111 }
1112
1113
1114
1115 type funcLitFinder token.Pos
1116
1117 func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
1118 if f.found() {
1119 return nil
1120 }
1121 switch n := node.(type) {
1122 case *ast.FuncLit:
1123 *f = funcLitFinder(n.Body.Lbrace)
1124 return nil
1125 }
1126 return f
1127 }
1128
1129 func (f *funcLitFinder) found() bool {
1130 return token.Pos(*f) != token.NoPos
1131 }
1132
1133
1134
1135 type block1 struct {
1136 Block
1137 index int
1138 }
1139
1140
1141 func (f *File) position(pos token.Pos) token.Position {
1142 return f.fset.PositionFor(pos, false)
1143 }
1144
1145
1146 func (f *File) offset(pos token.Pos) int {
1147 return f.position(pos).Offset
1148 }
1149
1150
1151 func (f *File) addVariables(w io.Writer) {
1152 if *pkgcfg != "" {
1153 return
1154 }
1155
1156 t := make([]block1, len(f.blocks))
1157 for i := range f.blocks {
1158 t[i].Block = f.blocks[i]
1159 t[i].index = i
1160 }
1161 slices.SortFunc(t, func(a, b block1) int {
1162 return cmp.Compare(a.startByte, b.startByte)
1163 })
1164 for i := 1; i < len(t); i++ {
1165 if t[i-1].endByte > t[i].startByte {
1166 fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
1167
1168 fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
1169 f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
1170 f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
1171 }
1172 }
1173
1174
1175 fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
1176 fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
1177 fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
1178 fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
1179 fmt.Fprintf(w, "} {\n")
1180
1181
1182 fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
1183
1184
1185
1186
1187
1188 for i, block := range f.blocks {
1189
1190 start := f.position(block.startByte)
1191 end := f.position(block.endByte)
1192
1193 start, end = dedup(start, end)
1194
1195 fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
1196 }
1197
1198
1199 fmt.Fprintf(w, "\t},\n")
1200
1201
1202 fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
1203
1204
1205
1206
1207 for i, block := range f.blocks {
1208 n := block.numStmt
1209 if n > 1<<16-1 {
1210 n = 1<<16 - 1
1211 }
1212 fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
1213 }
1214
1215
1216 fmt.Fprintf(w, "\t},\n")
1217
1218
1219 fmt.Fprintf(w, "}\n")
1220 }
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 type pos2 struct {
1231 p1, p2 token.Position
1232 }
1233
1234
1235 var seenPos2 = make(map[pos2]bool)
1236
1237
1238
1239
1240 func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
1241 key := pos2{
1242 p1: p1,
1243 p2: p2,
1244 }
1245
1246
1247
1248 key.p1.Offset = 0
1249 key.p2.Offset = 0
1250
1251 for seenPos2[key] {
1252 key.p2.Column++
1253 }
1254 seenPos2[key] = true
1255
1256 return key.p1, key.p2
1257 }
1258
1259 func (p *Package) emitMetaData(w io.Writer) {
1260 if *pkgcfg == "" {
1261 return
1262 }
1263
1264
1265
1266
1267
1268 if pkgconfig.EmitMetaFile != "" {
1269 p.emitMetaFile(pkgconfig.EmitMetaFile)
1270 }
1271
1272
1273
1274 if counterStmt == nil && len(p.counterLengths) != 0 {
1275 panic("internal error: seen functions with regonly/testmain")
1276 }
1277
1278
1279 fmt.Fprintf(w, "\npackage %s\n\n", pkgconfig.PkgName)
1280
1281
1282 fmt.Fprintf(w, "\nvar %sP uint32\n", *varVar)
1283
1284
1285 for k := range p.counterLengths {
1286 cvn := mkCounterVarName(k)
1287 fmt.Fprintf(w, "var %s [%d]uint32\n", cvn, p.counterLengths[k])
1288 }
1289
1290
1291 var sws slicewriter.WriteSeeker
1292 digest, err := p.mdb.Emit(&sws)
1293 if err != nil {
1294 log.Fatalf("encoding meta-data: %v", err)
1295 }
1296 p.mdb = nil
1297 fmt.Fprintf(w, "var %s = [...]byte{\n", mkMetaVar())
1298 payload := sws.BytesWritten()
1299 for k, b := range payload {
1300 fmt.Fprintf(w, " 0x%x,", b)
1301 if k != 0 && k%8 == 0 {
1302 fmt.Fprintf(w, "\n")
1303 }
1304 }
1305 fmt.Fprintf(w, "}\n")
1306
1307 fixcfg := covcmd.CoverFixupConfig{
1308 Strategy: "normal",
1309 MetaVar: mkMetaVar(),
1310 MetaLen: len(payload),
1311 MetaHash: fmt.Sprintf("%x", digest),
1312 PkgIdVar: mkPackageIdVar(),
1313 CounterPrefix: *varVar,
1314 CounterGranularity: pkgconfig.Granularity,
1315 CounterMode: *mode,
1316 }
1317 fixdata, err := json.Marshal(fixcfg)
1318 if err != nil {
1319 log.Fatalf("marshal fixupcfg: %v", err)
1320 }
1321 if err := os.WriteFile(pkgconfig.OutConfig, fixdata, 0666); err != nil {
1322 log.Fatalf("error writing %s: %v", pkgconfig.OutConfig, err)
1323 }
1324 }
1325
1326
1327
1328 func atomicOnAtomic() bool {
1329 return *mode == "atomic" && pkgconfig.PkgPath == "sync/atomic"
1330 }
1331
1332
1333
1334
1335
1336 func atomicPackagePrefix() string {
1337 if atomicOnAtomic() {
1338 return ""
1339 }
1340 return atomicPackageName + "."
1341 }
1342
1343 func (p *Package) emitMetaFile(outpath string) {
1344
1345 of, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
1346 if err != nil {
1347 log.Fatalf("opening covmeta %s: %v", outpath, err)
1348 }
1349
1350 if len(p.counterLengths) == 0 {
1351
1352
1353
1354 if err = of.Close(); err != nil {
1355 log.Fatalf("closing meta-data file: %v", err)
1356 }
1357 return
1358 }
1359
1360
1361 var sws slicewriter.WriteSeeker
1362 digest, err := p.mdb.Emit(&sws)
1363 if err != nil {
1364 log.Fatalf("encoding meta-data: %v", err)
1365 }
1366 payload := sws.BytesWritten()
1367 blobs := [][]byte{payload}
1368
1369
1370 mfw := encodemeta.NewCoverageMetaFileWriter(outpath, of)
1371 err = mfw.Write(digest, blobs, cmode, cgran)
1372 if err != nil {
1373 log.Fatalf("writing meta-data file: %v", err)
1374 }
1375 if err = of.Close(); err != nil {
1376 log.Fatalf("closing meta-data file: %v", err)
1377 }
1378 }
1379
View as plain text