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
336 endLine = scanFile.PositionFor(pos+token.Pos(len(lit)), false).Line
337 }
338
339 if prevEndLine == 0 {
340
341 codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))
342 } else if startLine > prevEndLine+1 {
343
344 codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))
345 ranges = append(ranges, Range{pos: codeStart, end: codeEnd})
346 codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))
347 }
348
349 if endLine > prevEndLine {
350 prevEndLine = endLine
351 }
352 }
353
354
355 if prevEndLine > 0 {
356 if prevEndLine < scanFile.LineCount() {
357
358
359 codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))
360 ranges = append(ranges, Range{pos: codeStart, end: codeEnd})
361 } else {
362 ranges = append(ranges, Range{pos: codeStart, end: end})
363 }
364 }
365
366
367
368
369 if len(ranges) == 0 {
370 return []Range{{pos: start, end: start}}
371 }
372
373 return ranges
374 }
375
376
377
378 func insideStatement(pos token.Pos, stmts []ast.Stmt) bool {
379
380 i, _ := slices.BinarySearchFunc(stmts, pos, func(s ast.Stmt, p token.Pos) int {
381 return cmp.Compare(s.Pos(), p)
382 })
383
384 return i > 0 && pos < stmts[i-1].End()
385 }
386
387
388
389
390 type rangeWithStatements struct {
391 Range
392 numStmt int
393 }
394
395 func mergeRangesWithinStatements(ranges []Range, stmts []ast.Stmt) []rangeWithStatements {
396 merged := make([]rangeWithStatements, 0, len(ranges))
397 for _, r := range ranges {
398
399
400 first, _ := slices.BinarySearchFunc(stmts, r.pos, func(s ast.Stmt, p token.Pos) int {
401 return cmp.Compare(s.Pos(), p)
402 })
403 last, _ := slices.BinarySearchFunc(stmts, r.end, func(s ast.Stmt, p token.Pos) int {
404 return cmp.Compare(s.Pos(), p)
405 })
406 numStmt := last - first
407
408 if len(merged) > 0 && insideStatement(r.pos, stmts) {
409
410 last := &merged[len(merged)-1]
411 last.end = r.end
412 last.numStmt += numStmt
413 } else {
414 merged = append(merged, rangeWithStatements{Range: r, numStmt: numStmt})
415 }
416 }
417 return merged
418 }
419
420
421
422
423
424 func (f *File) findText(pos token.Pos, text string) int {
425 b := []byte(text)
426 start := f.offset(pos)
427 i := start
428 s := f.content
429 for i < len(s) {
430 if bytes.HasPrefix(s[i:], b) {
431 return i
432 }
433 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
434 for i < len(s) && s[i] != '\n' {
435 i++
436 }
437 continue
438 }
439 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
440 for i += 2; ; i++ {
441 if i+2 > len(s) {
442 return 0
443 }
444 if s[i] == '*' && s[i+1] == '/' {
445 i += 2
446 break
447 }
448 }
449 continue
450 }
451 i++
452 }
453 return -1
454 }
455
456
457 func (f *File) Visit(node ast.Node) ast.Visitor {
458 switch n := node.(type) {
459 case *ast.BlockStmt:
460
461 if len(n.List) > 0 {
462 switch n.List[0].(type) {
463 case *ast.CaseClause:
464 for _, n := range n.List {
465 clause := n.(*ast.CaseClause)
466 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
467 }
468 return f
469 case *ast.CommClause:
470 for _, n := range n.List {
471 clause := n.(*ast.CommClause)
472 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
473 }
474 return f
475 }
476 }
477 f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true)
478 case *ast.IfStmt:
479 if n.Init != nil {
480 ast.Walk(f, n.Init)
481 }
482 ast.Walk(f, n.Cond)
483 ast.Walk(f, n.Body)
484 if n.Else == nil {
485 return nil
486 }
487
488
489
490
491
492
493
494
495
496
497
498 elseOffset := f.findText(n.Body.End(), "else")
499 if elseOffset < 0 {
500 panic("lost else")
501 }
502 f.edit.Insert(elseOffset+4, "{")
503 f.edit.Insert(f.offset(n.Else.End()), "}")
504
505
506
507
508
509 pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
510 switch stmt := n.Else.(type) {
511 case *ast.IfStmt:
512 block := &ast.BlockStmt{
513 Lbrace: pos,
514 List: []ast.Stmt{stmt},
515 Rbrace: stmt.End(),
516 }
517 n.Else = block
518 case *ast.BlockStmt:
519 stmt.Lbrace = pos
520 default:
521 panic("unexpected node type in if")
522 }
523 ast.Walk(f, n.Else)
524 return nil
525 case *ast.SelectStmt:
526
527 if n.Body == nil || len(n.Body.List) == 0 {
528 return nil
529 }
530 case *ast.SwitchStmt:
531
532 if n.Body == nil || len(n.Body.List) == 0 {
533 if n.Init != nil {
534 ast.Walk(f, n.Init)
535 }
536 if n.Tag != nil {
537 ast.Walk(f, n.Tag)
538 }
539 return nil
540 }
541 case *ast.TypeSwitchStmt:
542
543 if n.Body == nil || len(n.Body.List) == 0 {
544 if n.Init != nil {
545 ast.Walk(f, n.Init)
546 }
547 ast.Walk(f, n.Assign)
548 return nil
549 }
550 case *ast.FuncDecl:
551
552
553 if n.Name.Name == "_" || n.Body == nil {
554 return nil
555 }
556 fname := n.Name.Name
557
558
559
560
561
562
563
564
565
566
567
568
569 if atomicOnAtomic() && (fname == "AddUint32" || fname == "StoreUint32") {
570 return nil
571 }
572
573 if r := n.Recv; r != nil && len(r.List) == 1 {
574 t := r.List[0].Type
575 star := ""
576 if p, _ := t.(*ast.StarExpr); p != nil {
577 t = p.X
578 star = "*"
579 }
580 if p, _ := t.(*ast.Ident); p != nil {
581 fname = star + p.Name + "." + fname
582 }
583 }
584 walkBody := true
585 if *pkgcfg != "" {
586 f.preFunc(n, fname)
587 if pkgconfig.Granularity == "perfunc" {
588 walkBody = false
589 }
590 }
591 if walkBody {
592 ast.Walk(f, n.Body)
593 }
594 if *pkgcfg != "" {
595 flit := false
596 f.postFunc(n, fname, flit, n.Body)
597 }
598 return nil
599 case *ast.FuncLit:
600
601
602 if f.fn.counterVar != "" {
603 return f
604 }
605
606
607
608
609 pos := n.Pos()
610 p := f.fset.File(pos).Position(pos)
611 fname := fmt.Sprintf("func.L%d.C%d", p.Line, p.Column)
612 if *pkgcfg != "" {
613 f.preFunc(n, fname)
614 }
615 if pkgconfig.Granularity != "perfunc" {
616 ast.Walk(f, n.Body)
617 }
618 if *pkgcfg != "" {
619 flit := true
620 f.postFunc(n, fname, flit, n.Body)
621 }
622 return nil
623 }
624 return f
625 }
626
627 func mkCounterVarName(idx int) string {
628 return fmt.Sprintf("%s_%d", *varVar, idx)
629 }
630
631 func mkPackageIdVar() string {
632 return *varVar + "P"
633 }
634
635 func mkMetaVar() string {
636 return *varVar + "M"
637 }
638
639 func mkPackageIdExpression() string {
640 ppath := pkgconfig.PkgPath
641 if hcid := coverage.HardCodedPkgID(ppath); hcid != -1 {
642 return fmt.Sprintf("uint32(%d)", uint32(hcid))
643 }
644 return mkPackageIdVar()
645 }
646
647 func (f *File) preFunc(fn ast.Node, fname string) {
648 f.fn.units = f.fn.units[:0]
649
650
651 cv := mkCounterVarName(len(f.pkg.counterLengths))
652 f.fn.counterVar = cv
653 }
654
655 func (f *File) postFunc(fn ast.Node, funcname string, flit bool, body *ast.BlockStmt) {
656
657
658 singleCtr := ""
659 if pkgconfig.Granularity == "perfunc" {
660 singleCtr = "; " + f.newCounter(fn.Pos(), fn.Pos(), 1)
661 }
662
663
664 nc := len(f.fn.units) + coverage.FirstCtrOffset
665 f.pkg.counterLengths = append(f.pkg.counterLengths, nc)
666
667
668
669 fnpos := f.fset.Position(fn.Pos())
670 ppath := pkgconfig.PkgPath
671 filename := ppath + "/" + filepath.Base(fnpos.Filename)
672
673
674
675
676
677
678
679 if pkgconfig.Local {
680 filename = f.name
681 }
682
683
684 fd := coverage.FuncDesc{
685 Funcname: funcname,
686 Srcfile: filename,
687 Units: f.fn.units,
688 Lit: flit,
689 }
690 funcId := f.mdb.AddFunc(fd)
691
692 hookWrite := func(cv string, which int, val string) string {
693 return fmt.Sprintf("%s[%d] = %s", cv, which, val)
694 }
695 if *mode == "atomic" {
696 hookWrite = func(cv string, which int, val string) string {
697 return fmt.Sprintf("%sStoreUint32(&%s[%d], %s)",
698 atomicPackagePrefix(), cv, which, val)
699 }
700 }
701
702
703
704
705
706
707
708
709 cv := f.fn.counterVar
710 regHook := hookWrite(cv, 0, strconv.Itoa(len(f.fn.units))) + " ; " +
711 hookWrite(cv, 1, mkPackageIdExpression()) + " ; " +
712 hookWrite(cv, 2, strconv.Itoa(int(funcId))) + singleCtr
713
714
715
716
717
718 boff := f.offset(body.Pos())
719 ipos := f.fset.File(body.Pos()).Pos(boff)
720 ip := f.offset(ipos)
721 f.edit.Replace(ip, ip+1, string(f.content[ipos-1])+regHook+" ; ")
722
723 f.fn.counterVar = ""
724 }
725
726 func annotate(names []string) {
727 var p *Package
728 if *pkgcfg != "" {
729 pp := pkgconfig.PkgPath
730 pn := pkgconfig.PkgName
731 mp := pkgconfig.ModulePath
732 mdb, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)
733 if err != nil {
734 log.Fatalf("creating coverage meta-data builder: %v\n", err)
735 }
736 p = &Package{
737 mdb: mdb,
738 }
739 }
740
741 for k, name := range names {
742 if strings.ContainsAny(name, "\r\n") {
743
744 log.Fatalf("cover: input path contains newline character: %q", name)
745 }
746
747 fd := os.Stdout
748 isStdout := true
749 if *pkgcfg != "" {
750 var err error
751 fd, err = os.Create(outputfiles[k])
752 if err != nil {
753 log.Fatalf("cover: %s", err)
754 }
755 isStdout = false
756 } else if *output != "" {
757 var err error
758 fd, err = os.Create(*output)
759 if err != nil {
760 log.Fatalf("cover: %s", err)
761 }
762 isStdout = false
763 }
764 p.annotateFile(name, fd)
765 if !isStdout {
766 if err := fd.Close(); err != nil {
767 log.Fatalf("cover: %s", err)
768 }
769 }
770 }
771
772 if *pkgcfg != "" {
773 fd, err := os.Create(covervarsoutfile)
774 if err != nil {
775 log.Fatalf("cover: %s", err)
776 }
777 p.emitMetaData(fd)
778 if err := fd.Close(); err != nil {
779 log.Fatalf("cover: %s", err)
780 }
781 }
782 }
783
784 func (p *Package) annotateFile(name string, fd io.Writer) {
785 fset := token.NewFileSet()
786 content, err := os.ReadFile(name)
787 if err != nil {
788 log.Fatalf("cover: %s: %s", name, err)
789 }
790 parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments|parser.SkipObjectResolution)
791 if err != nil {
792 log.Fatalf("cover: %s: %s", name, err)
793 }
794
795 file := &File{
796 fset: fset,
797 name: name,
798 content: content,
799 edit: edit.NewBuffer(content),
800 astFile: parsedFile,
801 }
802 if p != nil {
803 file.mdb = p.mdb
804 file.pkg = p
805 }
806
807 if *mode == "atomic" {
808
809
810
811
812
813
814
815 if pkgconfig.PkgPath != "sync/atomic" {
816 file.edit.Insert(file.offset(file.astFile.Name.End()),
817 fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
818 }
819 }
820 if pkgconfig.PkgName == "main" {
821 file.edit.Insert(file.offset(file.astFile.Name.End()),
822 "; import _ \"runtime/coverage\"")
823 }
824
825 if counterStmt != nil {
826 ast.Walk(file, file.astFile)
827 }
828 newContent := file.edit.Bytes()
829
830 if strings.ContainsAny(name, "\r\n") {
831
832
833 panic(fmt.Sprintf("annotateFile: name contains unexpected newline character: %q", name))
834 }
835 fmt.Fprintf(fd, "//line %s:1:1\n", name)
836 fd.Write(newContent)
837
838
839
840
841 file.addVariables(fd)
842
843
844
845 if *mode == "atomic" {
846 fmt.Fprintf(fd, "\nvar _ = %sLoadUint32\n", atomicPackagePrefix())
847 }
848 }
849
850
851 func setCounterStmt(f *File, counter string) string {
852 return fmt.Sprintf("%s = 1", counter)
853 }
854
855
856 func incCounterStmt(f *File, counter string) string {
857 return fmt.Sprintf("%s++", counter)
858 }
859
860
861 func atomicCounterStmt(f *File, counter string) string {
862 return fmt.Sprintf("%sAddUint32(&%s, 1)", atomicPackagePrefix(), counter)
863 }
864
865
866 func (f *File) newCounter(start, end token.Pos, numStmt int) string {
867 var stmt string
868 if *pkgcfg != "" {
869 slot := len(f.fn.units) + coverage.FirstCtrOffset
870 if f.fn.counterVar == "" {
871 panic("internal error: counter var unset")
872 }
873 stmt = counterStmt(f, fmt.Sprintf("%s[%d]", f.fn.counterVar, slot))
874
875 stpos := f.position(start)
876 enpos := f.position(end)
877 stpos, enpos = dedup(stpos, enpos)
878 unit := coverage.CoverableUnit{
879 StLine: uint32(stpos.Line),
880 StCol: uint32(stpos.Column),
881 EnLine: uint32(enpos.Line),
882 EnCol: uint32(enpos.Column),
883 NxStmts: uint32(numStmt),
884 }
885 f.fn.units = append(f.fn.units, unit)
886 } else {
887 stmt = counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar,
888 len(f.blocks)))
889 f.blocks = append(f.blocks, Block{start, end, numStmt})
890 }
891 return stmt
892 }
893
894
895
896
897
898
899
900
901
902
903
904
905
906 func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
907
908
909 if len(list) == 0 {
910 r := f.codeRanges(insertPos, blockEnd)[0]
911 f.edit.Insert(f.offset(r.pos), f.newCounter(r.pos, r.end, 0)+";")
912 return
913 }
914
915
916 list = append([]ast.Stmt(nil), list...)
917
918
919 for {
920
921
922 var last int
923 end := blockEnd
924 for last = 0; last < len(list); last++ {
925 stmt := list[last]
926 end = f.statementBoundary(stmt)
927 if f.endsBasicSourceBlock(stmt) {
928
929
930
931
932
933
934
935
936
937
938
939 if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
940 newLabel := *label
941 newLabel.Stmt = &ast.EmptyStmt{
942 Semicolon: label.Stmt.Pos(),
943 Implicit: true,
944 }
945 end = label.Pos()
946 list[last] = &newLabel
947
948 list = append(list, nil)
949 copy(list[last+1:], list[last:])
950 list[last+1] = label.Stmt
951 }
952 last++
953 extendToClosingBrace = false
954 break
955 }
956 }
957 if extendToClosingBrace {
958 end = blockEnd
959 }
960 if pos != end {
961
962
963
964 for i, r := range mergeRangesWithinStatements(f.codeRanges(pos, end), list[:last]) {
965 insertOffset := f.offset(r.pos)
966 if i == 0 {
967 insertOffset = f.offset(insertPos)
968 }
969 f.edit.Insert(insertOffset, f.newCounter(r.pos, r.end, r.numStmt)+";")
970 }
971 }
972 list = list[last:]
973 if len(list) == 0 {
974 break
975 }
976 pos = list[0].Pos()
977 insertPos = pos
978 }
979 }
980
981
982
983
984
985
986 func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
987 if n == nil {
988 return false, 0
989 }
990 var literal funcLitFinder
991 ast.Walk(&literal, n)
992 return literal.found(), token.Pos(literal)
993 }
994
995
996
997 func (f *File) statementBoundary(s ast.Stmt) token.Pos {
998
999 switch s := s.(type) {
1000 case *ast.BlockStmt:
1001
1002 return s.Lbrace
1003 case *ast.IfStmt:
1004 found, pos := hasFuncLiteral(s.Init)
1005 if found {
1006 return pos
1007 }
1008 found, pos = hasFuncLiteral(s.Cond)
1009 if found {
1010 return pos
1011 }
1012 return s.Body.Lbrace
1013 case *ast.ForStmt:
1014 found, pos := hasFuncLiteral(s.Init)
1015 if found {
1016 return pos
1017 }
1018 found, pos = hasFuncLiteral(s.Cond)
1019 if found {
1020 return pos
1021 }
1022 found, pos = hasFuncLiteral(s.Post)
1023 if found {
1024 return pos
1025 }
1026 return s.Body.Lbrace
1027 case *ast.LabeledStmt:
1028 return f.statementBoundary(s.Stmt)
1029 case *ast.RangeStmt:
1030 found, pos := hasFuncLiteral(s.X)
1031 if found {
1032 return pos
1033 }
1034 return s.Body.Lbrace
1035 case *ast.SwitchStmt:
1036 found, pos := hasFuncLiteral(s.Init)
1037 if found {
1038 return pos
1039 }
1040 found, pos = hasFuncLiteral(s.Tag)
1041 if found {
1042 return pos
1043 }
1044 return s.Body.Lbrace
1045 case *ast.SelectStmt:
1046 return s.Body.Lbrace
1047 case *ast.TypeSwitchStmt:
1048 found, pos := hasFuncLiteral(s.Init)
1049 if found {
1050 return pos
1051 }
1052 return s.Body.Lbrace
1053 }
1054
1055
1056
1057
1058 found, pos := hasFuncLiteral(s)
1059 if found {
1060 return pos
1061 }
1062 return s.End()
1063 }
1064
1065
1066
1067
1068 func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
1069 switch s := s.(type) {
1070 case *ast.BlockStmt:
1071
1072 return true
1073 case *ast.BranchStmt:
1074 return true
1075 case *ast.ForStmt:
1076 return true
1077 case *ast.IfStmt:
1078 return true
1079 case *ast.LabeledStmt:
1080 return true
1081 case *ast.RangeStmt:
1082 return true
1083 case *ast.SwitchStmt:
1084 return true
1085 case *ast.SelectStmt:
1086 return true
1087 case *ast.TypeSwitchStmt:
1088 return true
1089 case *ast.ExprStmt:
1090
1091
1092
1093
1094 if call, ok := s.X.(*ast.CallExpr); ok {
1095 if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
1096 return true
1097 }
1098 }
1099 }
1100 found, _ := hasFuncLiteral(s)
1101 return found
1102 }
1103
1104
1105
1106 func (f *File) isControl(s ast.Stmt) bool {
1107 switch s.(type) {
1108 case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
1109 return true
1110 }
1111 return false
1112 }
1113
1114
1115
1116 type funcLitFinder token.Pos
1117
1118 func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
1119 if f.found() {
1120 return nil
1121 }
1122 switch n := node.(type) {
1123 case *ast.FuncLit:
1124 *f = funcLitFinder(n.Body.Lbrace)
1125 return nil
1126 }
1127 return f
1128 }
1129
1130 func (f *funcLitFinder) found() bool {
1131 return token.Pos(*f) != token.NoPos
1132 }
1133
1134
1135
1136 type block1 struct {
1137 Block
1138 index int
1139 }
1140
1141
1142 func (f *File) position(pos token.Pos) token.Position {
1143 return f.fset.PositionFor(pos, false)
1144 }
1145
1146
1147 func (f *File) offset(pos token.Pos) int {
1148 return f.position(pos).Offset
1149 }
1150
1151
1152 func (f *File) addVariables(w io.Writer) {
1153 if *pkgcfg != "" {
1154 return
1155 }
1156
1157 t := make([]block1, len(f.blocks))
1158 for i := range f.blocks {
1159 t[i].Block = f.blocks[i]
1160 t[i].index = i
1161 }
1162 slices.SortFunc(t, func(a, b block1) int {
1163 return cmp.Compare(a.startByte, b.startByte)
1164 })
1165 for i := 1; i < len(t); i++ {
1166 if t[i-1].endByte > t[i].startByte {
1167 fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
1168
1169 fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
1170 f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
1171 f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
1172 }
1173 }
1174
1175
1176 fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
1177 fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
1178 fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
1179 fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
1180 fmt.Fprintf(w, "} {\n")
1181
1182
1183 fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
1184
1185
1186
1187
1188
1189 for i, block := range f.blocks {
1190
1191 start := f.position(block.startByte)
1192 end := f.position(block.endByte)
1193
1194 start, end = dedup(start, end)
1195
1196 fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
1197 }
1198
1199
1200 fmt.Fprintf(w, "\t},\n")
1201
1202
1203 fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
1204
1205
1206
1207
1208 for i, block := range f.blocks {
1209 n := block.numStmt
1210 if n > 1<<16-1 {
1211 n = 1<<16 - 1
1212 }
1213 fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
1214 }
1215
1216
1217 fmt.Fprintf(w, "\t},\n")
1218
1219
1220 fmt.Fprintf(w, "}\n")
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 type pos2 struct {
1232 p1, p2 token.Position
1233 }
1234
1235
1236 var seenPos2 = make(map[pos2]bool)
1237
1238
1239
1240
1241 func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
1242 key := pos2{
1243 p1: p1,
1244 p2: p2,
1245 }
1246
1247
1248
1249 key.p1.Offset = 0
1250 key.p2.Offset = 0
1251
1252 for seenPos2[key] {
1253 key.p2.Column++
1254 }
1255 seenPos2[key] = true
1256
1257 return key.p1, key.p2
1258 }
1259
1260 func (p *Package) emitMetaData(w io.Writer) {
1261 if *pkgcfg == "" {
1262 return
1263 }
1264
1265
1266
1267
1268
1269 if pkgconfig.EmitMetaFile != "" {
1270 p.emitMetaFile(pkgconfig.EmitMetaFile)
1271 }
1272
1273
1274
1275 if counterStmt == nil && len(p.counterLengths) != 0 {
1276 panic("internal error: seen functions with regonly/testmain")
1277 }
1278
1279
1280 fmt.Fprintf(w, "\npackage %s\n\n", pkgconfig.PkgName)
1281
1282
1283 fmt.Fprintf(w, "\nvar %sP uint32\n", *varVar)
1284
1285
1286 for k := range p.counterLengths {
1287 cvn := mkCounterVarName(k)
1288 fmt.Fprintf(w, "var %s [%d]uint32\n", cvn, p.counterLengths[k])
1289 }
1290
1291
1292 var sws slicewriter.WriteSeeker
1293 digest, err := p.mdb.Emit(&sws)
1294 if err != nil {
1295 log.Fatalf("encoding meta-data: %v", err)
1296 }
1297 p.mdb = nil
1298 fmt.Fprintf(w, "var %s = [...]byte{\n", mkMetaVar())
1299 payload := sws.BytesWritten()
1300 for k, b := range payload {
1301 fmt.Fprintf(w, " 0x%x,", b)
1302 if k != 0 && k%8 == 0 {
1303 fmt.Fprintf(w, "\n")
1304 }
1305 }
1306 fmt.Fprintf(w, "}\n")
1307
1308 fixcfg := covcmd.CoverFixupConfig{
1309 Strategy: "normal",
1310 MetaVar: mkMetaVar(),
1311 MetaLen: len(payload),
1312 MetaHash: fmt.Sprintf("%x", digest),
1313 PkgIdVar: mkPackageIdVar(),
1314 CounterPrefix: *varVar,
1315 CounterGranularity: pkgconfig.Granularity,
1316 CounterMode: *mode,
1317 }
1318 fixdata, err := json.Marshal(fixcfg)
1319 if err != nil {
1320 log.Fatalf("marshal fixupcfg: %v", err)
1321 }
1322 if err := os.WriteFile(pkgconfig.OutConfig, fixdata, 0666); err != nil {
1323 log.Fatalf("error writing %s: %v", pkgconfig.OutConfig, err)
1324 }
1325 }
1326
1327
1328
1329 func atomicOnAtomic() bool {
1330 return *mode == "atomic" && pkgconfig.PkgPath == "sync/atomic"
1331 }
1332
1333
1334
1335
1336
1337 func atomicPackagePrefix() string {
1338 if atomicOnAtomic() {
1339 return ""
1340 }
1341 return atomicPackageName + "."
1342 }
1343
1344 func (p *Package) emitMetaFile(outpath string) {
1345
1346 of, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
1347 if err != nil {
1348 log.Fatalf("opening covmeta %s: %v", outpath, err)
1349 }
1350
1351 if len(p.counterLengths) == 0 {
1352
1353
1354
1355 if err = of.Close(); err != nil {
1356 log.Fatalf("closing meta-data file: %v", err)
1357 }
1358 return
1359 }
1360
1361
1362 var sws slicewriter.WriteSeeker
1363 digest, err := p.mdb.Emit(&sws)
1364 if err != nil {
1365 log.Fatalf("encoding meta-data: %v", err)
1366 }
1367 payload := sws.BytesWritten()
1368 blobs := [][]byte{payload}
1369
1370
1371 mfw := encodemeta.NewCoverageMetaFileWriter(outpath, of)
1372 err = mfw.Write(digest, blobs, cmode, cgran)
1373 if err != nil {
1374 log.Fatalf("writing meta-data file: %v", err)
1375 }
1376 if err = of.Close(); err != nil {
1377 log.Fatalf("closing meta-data file: %v", err)
1378 }
1379 }
1380
View as plain text