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