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