Source file
src/cmd/cover/cover.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "go/ast"
12 "go/parser"
13 "go/token"
14 "io"
15 "log"
16 "os"
17 "sort"
18
19 "cmd/internal/edit"
20 "cmd/internal/objabi"
21 )
22
23 const usageMessage = "" +
24 `Usage of 'go tool cover':
25 Given a coverage profile produced by 'go test':
26 go test -coverprofile=c.out
27
28 Open a web browser displaying annotated source code:
29 go tool cover -html=c.out
30
31 Write out an HTML file instead of launching a web browser:
32 go tool cover -html=c.out -o coverage.html
33
34 Display coverage percentages to stdout for each function:
35 go tool cover -func=c.out
36
37 Finally, to generate modified source code with coverage annotations
38 (what go test -cover does):
39 go tool cover -mode=set -var=CoverageVariableName program.go
40 `
41
42 func usage() {
43 fmt.Fprint(os.Stderr, usageMessage)
44 fmt.Fprintln(os.Stderr, "\nFlags:")
45 flag.PrintDefaults()
46 fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.")
47 os.Exit(2)
48 }
49
50 var (
51 mode = flag.String("mode", "", "coverage mode: set, count, atomic")
52 varVar = flag.String("var", "GoCover", "name of coverage variable to generate")
53 output = flag.String("o", "", "file for output; default: stdout")
54 htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
55 funcOut = flag.String("func", "", "output coverage profile information for each function")
56 )
57
58 var profile string
59
60 var counterStmt func(*File, string) string
61
62 const (
63 atomicPackagePath = "sync/atomic"
64 atomicPackageName = "_cover_atomic_"
65 )
66
67 func main() {
68 objabi.AddVersionFlag()
69 flag.Usage = usage
70 flag.Parse()
71
72
73 if flag.NFlag() == 0 && flag.NArg() == 0 {
74 flag.Usage()
75 }
76
77 err := parseFlags()
78 if err != nil {
79 fmt.Fprintln(os.Stderr, err)
80 fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
81 os.Exit(2)
82 }
83
84
85 if *mode != "" {
86 annotate(flag.Arg(0))
87 return
88 }
89
90
91 if *htmlOut != "" {
92 err = htmlOutput(profile, *output)
93 } else {
94 err = funcOutput(profile, *output)
95 }
96
97 if err != nil {
98 fmt.Fprintf(os.Stderr, "cover: %v\n", err)
99 os.Exit(2)
100 }
101 }
102
103
104 func parseFlags() error {
105 profile = *htmlOut
106 if *funcOut != "" {
107 if profile != "" {
108 return fmt.Errorf("too many options")
109 }
110 profile = *funcOut
111 }
112
113
114 if (profile == "") == (*mode == "") {
115 return fmt.Errorf("too many options")
116 }
117
118 if *varVar != "" && !token.IsIdentifier(*varVar) {
119 return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
120 }
121
122 if *mode != "" {
123 switch *mode {
124 case "set":
125 counterStmt = setCounterStmt
126 case "count":
127 counterStmt = incCounterStmt
128 case "atomic":
129 counterStmt = atomicCounterStmt
130 default:
131 return fmt.Errorf("unknown -mode %v", *mode)
132 }
133
134 if flag.NArg() == 0 {
135 return fmt.Errorf("missing source file")
136 } else if flag.NArg() == 1 {
137 return nil
138 }
139 } else if flag.NArg() == 0 {
140 return nil
141 }
142 return fmt.Errorf("too many arguments")
143 }
144
145
146
147
148 type Block struct {
149 startByte token.Pos
150 endByte token.Pos
151 numStmt int
152 }
153
154
155
156 type File struct {
157 fset *token.FileSet
158 name string
159 astFile *ast.File
160 blocks []Block
161 content []byte
162 edit *edit.Buffer
163 }
164
165
166
167
168
169 func (f *File) findText(pos token.Pos, text string) int {
170 b := []byte(text)
171 start := f.offset(pos)
172 i := start
173 s := f.content
174 for i < len(s) {
175 if bytes.HasPrefix(s[i:], b) {
176 return i
177 }
178 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
179 for i < len(s) && s[i] != '\n' {
180 i++
181 }
182 continue
183 }
184 if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
185 for i += 2; ; i++ {
186 if i+2 > len(s) {
187 return 0
188 }
189 if s[i] == '*' && s[i+1] == '/' {
190 i += 2
191 break
192 }
193 }
194 continue
195 }
196 i++
197 }
198 return -1
199 }
200
201
202 func (f *File) Visit(node ast.Node) ast.Visitor {
203 switch n := node.(type) {
204 case *ast.BlockStmt:
205
206 if len(n.List) > 0 {
207 switch n.List[0].(type) {
208 case *ast.CaseClause:
209 for _, n := range n.List {
210 clause := n.(*ast.CaseClause)
211 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
212 }
213 return f
214 case *ast.CommClause:
215 for _, n := range n.List {
216 clause := n.(*ast.CommClause)
217 f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
218 }
219 return f
220 }
221 }
222 f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true)
223 case *ast.IfStmt:
224 if n.Init != nil {
225 ast.Walk(f, n.Init)
226 }
227 ast.Walk(f, n.Cond)
228 ast.Walk(f, n.Body)
229 if n.Else == nil {
230 return nil
231 }
232
233
234
235
236
237
238
239
240
241
242
243 elseOffset := f.findText(n.Body.End(), "else")
244 if elseOffset < 0 {
245 panic("lost else")
246 }
247 f.edit.Insert(elseOffset+4, "{")
248 f.edit.Insert(f.offset(n.Else.End()), "}")
249
250
251
252
253
254 pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
255 switch stmt := n.Else.(type) {
256 case *ast.IfStmt:
257 block := &ast.BlockStmt{
258 Lbrace: pos,
259 List: []ast.Stmt{stmt},
260 Rbrace: stmt.End(),
261 }
262 n.Else = block
263 case *ast.BlockStmt:
264 stmt.Lbrace = pos
265 default:
266 panic("unexpected node type in if")
267 }
268 ast.Walk(f, n.Else)
269 return nil
270 case *ast.SelectStmt:
271
272 if n.Body == nil || len(n.Body.List) == 0 {
273 return nil
274 }
275 case *ast.SwitchStmt:
276
277 if n.Body == nil || len(n.Body.List) == 0 {
278 if n.Init != nil {
279 ast.Walk(f, n.Init)
280 }
281 if n.Tag != nil {
282 ast.Walk(f, n.Tag)
283 }
284 return nil
285 }
286 case *ast.TypeSwitchStmt:
287
288 if n.Body == nil || len(n.Body.List) == 0 {
289 if n.Init != nil {
290 ast.Walk(f, n.Init)
291 }
292 ast.Walk(f, n.Assign)
293 return nil
294 }
295 case *ast.FuncDecl:
296
297 if n.Name.Name == "_" {
298 return nil
299 }
300 }
301 return f
302 }
303
304 func annotate(name string) {
305 fset := token.NewFileSet()
306 content, err := os.ReadFile(name)
307 if err != nil {
308 log.Fatalf("cover: %s: %s", name, err)
309 }
310 parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
311 if err != nil {
312 log.Fatalf("cover: %s: %s", name, err)
313 }
314
315 file := &File{
316 fset: fset,
317 name: name,
318 content: content,
319 edit: edit.NewBuffer(content),
320 astFile: parsedFile,
321 }
322 if *mode == "atomic" {
323
324
325
326
327
328 file.edit.Insert(file.offset(file.astFile.Name.End()),
329 fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
330 }
331
332 ast.Walk(file, file.astFile)
333 newContent := file.edit.Bytes()
334
335 fd := os.Stdout
336 if *output != "" {
337 var err error
338 fd, err = os.Create(*output)
339 if err != nil {
340 log.Fatalf("cover: %s", err)
341 }
342 }
343
344 fmt.Fprintf(fd, "//line %s:1\n", name)
345 fd.Write(newContent)
346
347
348
349 file.addVariables(fd)
350 }
351
352
353 func setCounterStmt(f *File, counter string) string {
354 return fmt.Sprintf("%s = 1", counter)
355 }
356
357
358 func incCounterStmt(f *File, counter string) string {
359 return fmt.Sprintf("%s++", counter)
360 }
361
362
363 func atomicCounterStmt(f *File, counter string) string {
364 return fmt.Sprintf("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
365 }
366
367
368 func (f *File) newCounter(start, end token.Pos, numStmt int) string {
369 stmt := counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar, len(f.blocks)))
370 f.blocks = append(f.blocks, Block{start, end, numStmt})
371 return stmt
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386 func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
387
388
389 if len(list) == 0 {
390 f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
391 return
392 }
393
394
395 list = append([]ast.Stmt(nil), list...)
396
397
398 for {
399
400
401 var last int
402 end := blockEnd
403 for last = 0; last < len(list); last++ {
404 stmt := list[last]
405 end = f.statementBoundary(stmt)
406 if f.endsBasicSourceBlock(stmt) {
407
408
409
410
411
412
413
414
415
416
417
418 if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
419 newLabel := *label
420 newLabel.Stmt = &ast.EmptyStmt{
421 Semicolon: label.Stmt.Pos(),
422 Implicit: true,
423 }
424 end = label.Pos()
425 list[last] = &newLabel
426
427 list = append(list, nil)
428 copy(list[last+1:], list[last:])
429 list[last+1] = label.Stmt
430 }
431 last++
432 extendToClosingBrace = false
433 break
434 }
435 }
436 if extendToClosingBrace {
437 end = blockEnd
438 }
439 if pos != end {
440 f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
441 }
442 list = list[last:]
443 if len(list) == 0 {
444 break
445 }
446 pos = list[0].Pos()
447 insertPos = pos
448 }
449 }
450
451
452
453
454
455
456 func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
457 if n == nil {
458 return false, 0
459 }
460 var literal funcLitFinder
461 ast.Walk(&literal, n)
462 return literal.found(), token.Pos(literal)
463 }
464
465
466
467 func (f *File) statementBoundary(s ast.Stmt) token.Pos {
468
469 switch s := s.(type) {
470 case *ast.BlockStmt:
471
472 return s.Lbrace
473 case *ast.IfStmt:
474 found, pos := hasFuncLiteral(s.Init)
475 if found {
476 return pos
477 }
478 found, pos = hasFuncLiteral(s.Cond)
479 if found {
480 return pos
481 }
482 return s.Body.Lbrace
483 case *ast.ForStmt:
484 found, pos := hasFuncLiteral(s.Init)
485 if found {
486 return pos
487 }
488 found, pos = hasFuncLiteral(s.Cond)
489 if found {
490 return pos
491 }
492 found, pos = hasFuncLiteral(s.Post)
493 if found {
494 return pos
495 }
496 return s.Body.Lbrace
497 case *ast.LabeledStmt:
498 return f.statementBoundary(s.Stmt)
499 case *ast.RangeStmt:
500 found, pos := hasFuncLiteral(s.X)
501 if found {
502 return pos
503 }
504 return s.Body.Lbrace
505 case *ast.SwitchStmt:
506 found, pos := hasFuncLiteral(s.Init)
507 if found {
508 return pos
509 }
510 found, pos = hasFuncLiteral(s.Tag)
511 if found {
512 return pos
513 }
514 return s.Body.Lbrace
515 case *ast.SelectStmt:
516 return s.Body.Lbrace
517 case *ast.TypeSwitchStmt:
518 found, pos := hasFuncLiteral(s.Init)
519 if found {
520 return pos
521 }
522 return s.Body.Lbrace
523 }
524
525
526
527
528 found, pos := hasFuncLiteral(s)
529 if found {
530 return pos
531 }
532 return s.End()
533 }
534
535
536
537
538 func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
539 switch s := s.(type) {
540 case *ast.BlockStmt:
541
542 return true
543 case *ast.BranchStmt:
544 return true
545 case *ast.ForStmt:
546 return true
547 case *ast.IfStmt:
548 return true
549 case *ast.LabeledStmt:
550 return true
551 case *ast.RangeStmt:
552 return true
553 case *ast.SwitchStmt:
554 return true
555 case *ast.SelectStmt:
556 return true
557 case *ast.TypeSwitchStmt:
558 return true
559 case *ast.ExprStmt:
560
561
562
563
564 if call, ok := s.X.(*ast.CallExpr); ok {
565 if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
566 return true
567 }
568 }
569 }
570 found, _ := hasFuncLiteral(s)
571 return found
572 }
573
574
575
576 func (f *File) isControl(s ast.Stmt) bool {
577 switch s.(type) {
578 case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
579 return true
580 }
581 return false
582 }
583
584
585
586 type funcLitFinder token.Pos
587
588 func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
589 if f.found() {
590 return nil
591 }
592 switch n := node.(type) {
593 case *ast.FuncLit:
594 *f = funcLitFinder(n.Body.Lbrace)
595 return nil
596 }
597 return f
598 }
599
600 func (f *funcLitFinder) found() bool {
601 return token.Pos(*f) != token.NoPos
602 }
603
604
605
606 type block1 struct {
607 Block
608 index int
609 }
610
611 type blockSlice []block1
612
613 func (b blockSlice) Len() int { return len(b) }
614 func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
615 func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
616
617
618 func (f *File) offset(pos token.Pos) int {
619 return f.fset.Position(pos).Offset
620 }
621
622
623 func (f *File) addVariables(w io.Writer) {
624
625 t := make([]block1, len(f.blocks))
626 for i := range f.blocks {
627 t[i].Block = f.blocks[i]
628 t[i].index = i
629 }
630 sort.Sort(blockSlice(t))
631 for i := 1; i < len(t); i++ {
632 if t[i-1].endByte > t[i].startByte {
633 fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
634
635 fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
636 f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
637 f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
638 }
639 }
640
641
642 fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
643 fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
644 fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
645 fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
646 fmt.Fprintf(w, "} {\n")
647
648
649 fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
650
651
652
653
654
655 for i, block := range f.blocks {
656 start := f.fset.Position(block.startByte)
657 end := f.fset.Position(block.endByte)
658
659 start, end = dedup(start, end)
660
661 fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
662 }
663
664
665 fmt.Fprintf(w, "\t},\n")
666
667
668 fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
669
670
671
672
673 for i, block := range f.blocks {
674 n := block.numStmt
675 if n > 1<<16-1 {
676 n = 1<<16 - 1
677 }
678 fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
679 }
680
681
682 fmt.Fprintf(w, "\t},\n")
683
684
685 fmt.Fprintf(w, "}\n")
686
687
688
689 if *mode == "atomic" {
690 fmt.Fprintf(w, "var _ = %s.LoadUint32\n", atomicPackageName)
691 }
692 }
693
694
695
696
697
698
699
700
701
702 type pos2 struct {
703 p1, p2 token.Position
704 }
705
706
707 var seenPos2 = make(map[pos2]bool)
708
709
710
711
712 func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
713 key := pos2{
714 p1: p1,
715 p2: p2,
716 }
717
718
719
720 key.p1.Offset = 0
721 key.p2.Offset = 0
722
723 for seenPos2[key] {
724 key.p2.Column++
725 }
726 seenPos2[key] = true
727
728 return key.p1, key.p2
729 }
730
View as plain text