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