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