1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package main |
6 | |
7 | import ( |
8 | "bytes" |
9 | "flag" |
10 | "fmt" |
11 | "go/ast" |
12 | "go/parser" |
13 | "go/printer" |
14 | "go/token" |
15 | "io" |
16 | "io/ioutil" |
17 | "log" |
18 | "os" |
19 | "path/filepath" |
20 | "sort" |
21 | "strconv" |
22 | "strings" |
23 | ) |
24 | |
25 | const usageMessage = "" + |
26 | `Usage of 'go tool cover': |
27 | Given a coverage profile produced by 'go test': |
28 | go test -coverprofile=c.out |
29 | |
30 | Open a web browser displaying annotated source code: |
31 | go tool cover -html=c.out |
32 | |
33 | Write out an HTML file instead of launching a web browser: |
34 | go tool cover -html=c.out -o coverage.html |
35 | |
36 | Display coverage percentages to stdout for each function: |
37 | go tool cover -func=c.out |
38 | |
39 | Finally, to generate modified source code with coverage annotations |
40 | (what go test -cover does): |
41 | go tool cover -mode=set -var=CoverageVariableName program.go |
42 | ` |
43 | |
44 | func usage() { |
45 | fmt.Fprint(os.Stderr, usageMessage) |
46 | fmt.Fprintln(os.Stderr, "\nFlags:") |
47 | flag.PrintDefaults() |
48 | fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.") |
49 | os.Exit(2) |
50 | } |
51 | |
52 | var ( |
53 | mode = flag.String("mode", "", "coverage mode: set, count, atomic") |
54 | varVar = flag.String("var", "GoCover", "name of coverage variable to generate") |
55 | output = flag.String("o", "", "file for output; default: stdout") |
56 | htmlOut = flag.String("html", "", "generate HTML representation of coverage profile") |
57 | funcOut = flag.String("func", "", "output coverage profile information for each function") |
58 | ) |
59 | |
60 | var profile string // The profile to read; the value of -html or -func |
61 | |
62 | var counterStmt func(*File, ast.Expr) ast.Stmt |
63 | |
64 | const ( |
65 | atomicPackagePath = "sync/atomic" |
66 | atomicPackageName = "_cover_atomic_" |
67 | ) |
68 | |
69 | func main() { |
70 | flag.Usage = usage |
71 | flag.Parse() |
72 | |
73 | // Usage information when no arguments. |
74 | if flag.NFlag() == 0 && flag.NArg() == 0 { |
75 | flag.Usage() |
76 | } |
77 | |
78 | err := parseFlags() |
79 | if err != nil { |
80 | fmt.Fprintln(os.Stderr, err) |
81 | fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`) |
82 | os.Exit(2) |
83 | } |
84 | |
85 | // Generate coverage-annotated source. |
86 | if *mode != "" { |
87 | annotate(flag.Arg(0)) |
88 | return |
89 | } |
90 | |
91 | // Output HTML or function coverage information. |
92 | if *htmlOut != "" { |
93 | err = htmlOutput(profile, *output) |
94 | } else { |
95 | err = funcOutput(profile, *output) |
96 | } |
97 | |
98 | if err != nil { |
99 | fmt.Fprintf(os.Stderr, "cover: %v\n", err) |
100 | os.Exit(2) |
101 | } |
102 | } |
103 | |
104 | // parseFlags sets the profile and counterStmt globals and performs validations. |
105 | func parseFlags() error { |
106 | profile = *htmlOut |
107 | if *funcOut != "" { |
108 | if profile != "" { |
109 | return fmt.Errorf("too many options") |
110 | } |
111 | profile = *funcOut |
112 | } |
113 | |
114 | // Must either display a profile or rewrite Go source. |
115 | if (profile == "") == (*mode == "") { |
116 | return fmt.Errorf("too many options") |
117 | } |
118 | |
119 | if *mode != "" { |
120 | switch *mode { |
121 | case "set": |
122 | counterStmt = setCounterStmt |
123 | case "count": |
124 | counterStmt = incCounterStmt |
125 | case "atomic": |
126 | counterStmt = atomicCounterStmt |
127 | default: |
128 | return fmt.Errorf("unknown -mode %v", *mode) |
129 | } |
130 | |
131 | if flag.NArg() == 0 { |
132 | return fmt.Errorf("missing source file") |
133 | } else if flag.NArg() == 1 { |
134 | return nil |
135 | } |
136 | } else if flag.NArg() == 0 { |
137 | return nil |
138 | } |
139 | return fmt.Errorf("too many arguments") |
140 | } |
141 | |
142 | // Block represents the information about a basic block to be recorded in the analysis. |
143 | // Note: Our definition of basic block is based on control structures; we don't break |
144 | // apart && and ||. We could but it doesn't seem important enough to bother. |
145 | type Block struct { |
146 | startByte token.Pos |
147 | endByte token.Pos |
148 | numStmt int |
149 | } |
150 | |
151 | // File is a wrapper for the state of a file used in the parser. |
152 | // The basic parse tree walker is a method of this type. |
153 | type File struct { |
154 | fset *token.FileSet |
155 | name string // Name of file. |
156 | astFile *ast.File |
157 | blocks []Block |
158 | atomicPkg string // Package name for "sync/atomic" in this file. |
159 | } |
160 | |
161 | // Visit implements the ast.Visitor interface. |
162 | func (f *File) Visit(node ast.Node) ast.Visitor { |
163 | switch n := node.(type) { |
164 | case *ast.BlockStmt: |
165 | // If it's a switch or select, the body is a list of case clauses; don't tag the block itself. |
166 | if len(n.List) > 0 { |
167 | switch n.List[0].(type) { |
168 | case *ast.CaseClause: // switch |
169 | for _, n := range n.List { |
170 | clause := n.(*ast.CaseClause) |
171 | clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) |
172 | } |
173 | return f |
174 | case *ast.CommClause: // select |
175 | for _, n := range n.List { |
176 | clause := n.(*ast.CommClause) |
177 | clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) |
178 | } |
179 | return f |
180 | } |
181 | } |
182 | n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace. |
183 | case *ast.IfStmt: |
184 | ast.Walk(f, n.Body) |
185 | if n.Else == nil { |
186 | return nil |
187 | } |
188 | // The elses are special, because if we have |
189 | // if x { |
190 | // } else if y { |
191 | // } |
192 | // we want to cover the "if y". To do this, we need a place to drop the counter, |
193 | // so we add a hidden block: |
194 | // if x { |
195 | // } else { |
196 | // if y { |
197 | // } |
198 | // } |
199 | switch stmt := n.Else.(type) { |
200 | case *ast.IfStmt: |
201 | block := &ast.BlockStmt{ |
202 | Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else". |
203 | List: []ast.Stmt{stmt}, |
204 | Rbrace: stmt.End(), |
205 | } |
206 | n.Else = block |
207 | case *ast.BlockStmt: |
208 | stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else". |
209 | default: |
210 | panic("unexpected node type in if") |
211 | } |
212 | ast.Walk(f, n.Else) |
213 | return nil |
214 | case *ast.SelectStmt: |
215 | // Don't annotate an empty select - creates a syntax error. |
216 | if n.Body == nil || len(n.Body.List) == 0 { |
217 | return nil |
218 | } |
219 | case *ast.SwitchStmt: |
220 | // Don't annotate an empty switch - creates a syntax error. |
221 | if n.Body == nil || len(n.Body.List) == 0 { |
222 | return nil |
223 | } |
224 | case *ast.TypeSwitchStmt: |
225 | // Don't annotate an empty type switch - creates a syntax error. |
226 | if n.Body == nil || len(n.Body.List) == 0 { |
227 | return nil |
228 | } |
229 | } |
230 | return f |
231 | } |
232 | |
233 | // unquote returns the unquoted string. |
234 | func unquote(s string) string { |
235 | t, err := strconv.Unquote(s) |
236 | if err != nil { |
237 | log.Fatalf("cover: improperly quoted string %q\n", s) |
238 | } |
239 | return t |
240 | } |
241 | |
242 | // addImport adds an import for the specified path, if one does not already exist, and returns |
243 | // the local package name. |
244 | func (f *File) addImport(path string) string { |
245 | // Does the package already import it? |
246 | for _, s := range f.astFile.Imports { |
247 | if unquote(s.Path.Value) == path { |
248 | if s.Name != nil { |
249 | return s.Name.Name |
250 | } |
251 | return filepath.Base(path) |
252 | } |
253 | } |
254 | newImport := &ast.ImportSpec{ |
255 | Name: ast.NewIdent(atomicPackageName), |
256 | Path: &ast.BasicLit{ |
257 | Kind: token.STRING, |
258 | Value: fmt.Sprintf("%q", path), |
259 | }, |
260 | } |
261 | impDecl := &ast.GenDecl{ |
262 | Tok: token.IMPORT, |
263 | Specs: []ast.Spec{ |
264 | newImport, |
265 | }, |
266 | } |
267 | // Make the new import the first Decl in the file. |
268 | astFile := f.astFile |
269 | astFile.Decls = append(astFile.Decls, nil) |
270 | copy(astFile.Decls[1:], astFile.Decls[0:]) |
271 | astFile.Decls[0] = impDecl |
272 | astFile.Imports = append(astFile.Imports, newImport) |
273 | |
274 | // Now refer to the package, just in case it ends up unused. |
275 | // That is, append to the end of the file the declaration |
276 | // var _ = _cover_atomic_.AddUint32 |
277 | reference := &ast.GenDecl{ |
278 | Tok: token.VAR, |
279 | Specs: []ast.Spec{ |
280 | &ast.ValueSpec{ |
281 | Names: []*ast.Ident{ |
282 | ast.NewIdent("_"), |
283 | }, |
284 | Values: []ast.Expr{ |
285 | &ast.SelectorExpr{ |
286 | X: ast.NewIdent(atomicPackageName), |
287 | Sel: ast.NewIdent("AddUint32"), |
288 | }, |
289 | }, |
290 | }, |
291 | }, |
292 | } |
293 | astFile.Decls = append(astFile.Decls, reference) |
294 | return atomicPackageName |
295 | } |
296 | |
297 | var slashslash = []byte("//") |
298 | |
299 | // initialComments returns the prefix of content containing only |
300 | // whitespace and line comments. Any +build directives must appear |
301 | // within this region. This approach is more reliable than using |
302 | // go/printer to print a modified AST containing comments. |
303 | func initialComments(content []byte) []byte { |
304 | // Derived from go/build.Context.shouldBuild. |
305 | end := 0 |
306 | p := content |
307 | for len(p) > 0 { |
308 | line := p |
309 | if i := bytes.IndexByte(line, '\n'); i >= 0 { |
310 | line, p = line[:i], p[i+1:] |
311 | } else { |
312 | p = p[len(p):] |
313 | } |
314 | line = bytes.TrimSpace(line) |
315 | if len(line) == 0 { // Blank line. |
316 | end = len(content) - len(p) |
317 | continue |
318 | } |
319 | if !bytes.HasPrefix(line, slashslash) { // Not comment line. |
320 | break |
321 | } |
322 | } |
323 | return content[:end] |
324 | } |
325 | |
326 | func annotate(name string) { |
327 | fset := token.NewFileSet() |
328 | content, err := ioutil.ReadFile(name) |
329 | if err != nil { |
330 | log.Fatalf("cover: %s: %s", name, err) |
331 | } |
332 | parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments) |
333 | if err != nil { |
334 | log.Fatalf("cover: %s: %s", name, err) |
335 | } |
336 | parsedFile.Comments = trimComments(parsedFile, fset) |
337 | |
338 | file := &File{ |
339 | fset: fset, |
340 | name: name, |
341 | astFile: parsedFile, |
342 | } |
343 | if *mode == "atomic" { |
344 | file.atomicPkg = file.addImport(atomicPackagePath) |
345 | } |
346 | ast.Walk(file, file.astFile) |
347 | fd := os.Stdout |
348 | if *output != "" { |
349 | var err error |
350 | fd, err = os.Create(*output) |
351 | if err != nil { |
352 | log.Fatalf("cover: %s", err) |
353 | } |
354 | } |
355 | fd.Write(initialComments(content)) // Retain '// +build' directives. |
356 | file.print(fd) |
357 | // After printing the source tree, add some declarations for the counters etc. |
358 | // We could do this by adding to the tree, but it's easier just to print the text. |
359 | file.addVariables(fd) |
360 | } |
361 | |
362 | // trimComments drops all but the //go: comments, some of which are semantically important. |
363 | // We drop all others because they can appear in places that cause our counters |
364 | // to appear in syntactically incorrect places. //go: appears at the beginning of |
365 | // the line and is syntactically safe. |
366 | func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup { |
367 | var comments []*ast.CommentGroup |
368 | for _, group := range file.Comments { |
369 | var list []*ast.Comment |
370 | for _, comment := range group.List { |
371 | if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 { |
372 | list = append(list, comment) |
373 | } |
374 | } |
375 | if list != nil { |
376 | comments = append(comments, &ast.CommentGroup{List: list}) |
377 | } |
378 | } |
379 | return comments |
380 | } |
381 | |
382 | func (f *File) print(w io.Writer) { |
383 | printer.Fprint(w, f.fset, f.astFile) |
384 | } |
385 | |
386 | // intLiteral returns an ast.BasicLit representing the integer value. |
387 | func (f *File) intLiteral(i int) *ast.BasicLit { |
388 | node := &ast.BasicLit{ |
389 | Kind: token.INT, |
390 | Value: fmt.Sprint(i), |
391 | } |
392 | return node |
393 | } |
394 | |
395 | // index returns an ast.BasicLit representing the number of counters present. |
396 | func (f *File) index() *ast.BasicLit { |
397 | return f.intLiteral(len(f.blocks)) |
398 | } |
399 | |
400 | // setCounterStmt returns the expression: __count[23] = 1. |
401 | func setCounterStmt(f *File, counter ast.Expr) ast.Stmt { |
402 | return &ast.AssignStmt{ |
403 | Lhs: []ast.Expr{counter}, |
404 | Tok: token.ASSIGN, |
405 | Rhs: []ast.Expr{f.intLiteral(1)}, |
406 | } |
407 | } |
408 | |
409 | // incCounterStmt returns the expression: __count[23]++. |
410 | func incCounterStmt(f *File, counter ast.Expr) ast.Stmt { |
411 | return &ast.IncDecStmt{ |
412 | X: counter, |
413 | Tok: token.INC, |
414 | } |
415 | } |
416 | |
417 | // atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1) |
418 | func atomicCounterStmt(f *File, counter ast.Expr) ast.Stmt { |
419 | return &ast.ExprStmt{ |
420 | X: &ast.CallExpr{ |
421 | Fun: &ast.SelectorExpr{ |
422 | X: ast.NewIdent(f.atomicPkg), |
423 | Sel: ast.NewIdent("AddUint32"), |
424 | }, |
425 | Args: []ast.Expr{&ast.UnaryExpr{ |
426 | Op: token.AND, |
427 | X: counter, |
428 | }, |
429 | f.intLiteral(1), |
430 | }, |
431 | }, |
432 | } |
433 | } |
434 | |
435 | // newCounter creates a new counter expression of the appropriate form. |
436 | func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt { |
437 | counter := &ast.IndexExpr{ |
438 | X: &ast.SelectorExpr{ |
439 | X: ast.NewIdent(*varVar), |
440 | Sel: ast.NewIdent("Count"), |
441 | }, |
442 | Index: f.index(), |
443 | } |
444 | stmt := counterStmt(f, counter) |
445 | f.blocks = append(f.blocks, Block{start, end, numStmt}) |
446 | return stmt |
447 | } |
448 | |
449 | // addCounters takes a list of statements and adds counters to the beginning of |
450 | // each basic block at the top level of that list. For instance, given |
451 | // |
452 | // S1 |
453 | // if cond { |
454 | // S2 |
455 | // } |
456 | // S3 |
457 | // |
458 | // counters will be added before S1 and before S3. The block containing S2 |
459 | // will be visited in a separate call. |
460 | // TODO: Nested simple blocks get unnecessary (but correct) counters |
461 | func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt { |
462 | // Special case: make sure we add a counter to an empty block. Can't do this below |
463 | // or we will add a counter to an empty statement list after, say, a return statement. |
464 | if len(list) == 0 { |
465 | return []ast.Stmt{f.newCounter(pos, blockEnd, 0)} |
466 | } |
467 | // We have a block (statement list), but it may have several basic blocks due to the |
468 | // appearance of statements that affect the flow of control. |
469 | var newList []ast.Stmt |
470 | for { |
471 | // Find first statement that affects flow of control (break, continue, if, etc.). |
472 | // It will be the last statement of this basic block. |
473 | var last int |
474 | end := blockEnd |
475 | for last = 0; last < len(list); last++ { |
476 | end = f.statementBoundary(list[last]) |
477 | if f.endsBasicSourceBlock(list[last]) { |
478 | extendToClosingBrace = false // Block is broken up now. |
479 | last++ |
480 | break |
481 | } |
482 | } |
483 | if extendToClosingBrace { |
484 | end = blockEnd |
485 | } |
486 | if pos != end { // Can have no source to cover if e.g. blocks abut. |
487 | newList = append(newList, f.newCounter(pos, end, last)) |
488 | } |
489 | newList = append(newList, list[0:last]...) |
490 | list = list[last:] |
491 | if len(list) == 0 { |
492 | break |
493 | } |
494 | pos = list[0].Pos() |
495 | } |
496 | return newList |
497 | } |
498 | |
499 | // hasFuncLiteral reports the existence and position of the first func literal |
500 | // in the node, if any. If a func literal appears, it usually marks the termination |
501 | // of a basic block because the function body is itself a block. |
502 | // Therefore we draw a line at the start of the body of the first function literal we find. |
503 | // TODO: what if there's more than one? Probably doesn't matter much. |
504 | func hasFuncLiteral(n ast.Node) (bool, token.Pos) { |
505 | if n == nil { |
506 | return false, 0 |
507 | } |
508 | var literal funcLitFinder |
509 | ast.Walk(&literal, n) |
510 | return literal.found(), token.Pos(literal) |
511 | } |
512 | |
513 | // statementBoundary finds the location in s that terminates the current basic |
514 | // block in the source. |
515 | func (f *File) statementBoundary(s ast.Stmt) token.Pos { |
516 | // Control flow statements are easy. |
517 | switch s := s.(type) { |
518 | case *ast.BlockStmt: |
519 | // Treat blocks like basic blocks to avoid overlapping counters. |
520 | return s.Lbrace |
521 | case *ast.IfStmt: |
522 | found, pos := hasFuncLiteral(s.Init) |
523 | if found { |
524 | return pos |
525 | } |
526 | found, pos = hasFuncLiteral(s.Cond) |
527 | if found { |
528 | return pos |
529 | } |
530 | return s.Body.Lbrace |
531 | case *ast.ForStmt: |
532 | found, pos := hasFuncLiteral(s.Init) |
533 | if found { |
534 | return pos |
535 | } |
536 | found, pos = hasFuncLiteral(s.Cond) |
537 | if found { |
538 | return pos |
539 | } |
540 | found, pos = hasFuncLiteral(s.Post) |
541 | if found { |
542 | return pos |
543 | } |
544 | return s.Body.Lbrace |
545 | case *ast.LabeledStmt: |
546 | return f.statementBoundary(s.Stmt) |
547 | case *ast.RangeStmt: |
548 | found, pos := hasFuncLiteral(s.X) |
549 | if found { |
550 | return pos |
551 | } |
552 | return s.Body.Lbrace |
553 | case *ast.SwitchStmt: |
554 | found, pos := hasFuncLiteral(s.Init) |
555 | if found { |
556 | return pos |
557 | } |
558 | found, pos = hasFuncLiteral(s.Tag) |
559 | if found { |
560 | return pos |
561 | } |
562 | return s.Body.Lbrace |
563 | case *ast.SelectStmt: |
564 | return s.Body.Lbrace |
565 | case *ast.TypeSwitchStmt: |
566 | found, pos := hasFuncLiteral(s.Init) |
567 | if found { |
568 | return pos |
569 | } |
570 | return s.Body.Lbrace |
571 | } |
572 | // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal. |
573 | // If it does, that's tricky because we want to exclude the body of the function from this block. |
574 | // Draw a line at the start of the body of the first function literal we find. |
575 | // TODO: what if there's more than one? Probably doesn't matter much. |
576 | found, pos := hasFuncLiteral(s) |
577 | if found { |
578 | return pos |
579 | } |
580 | return s.End() |
581 | } |
582 | |
583 | // endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc., |
584 | // or if it's just problematic, for instance contains a function literal, which will complicate |
585 | // accounting due to the block-within-an expression. |
586 | func (f *File) endsBasicSourceBlock(s ast.Stmt) bool { |
587 | switch s := s.(type) { |
588 | case *ast.BlockStmt: |
589 | // Treat blocks like basic blocks to avoid overlapping counters. |
590 | return true |
591 | case *ast.BranchStmt: |
592 | return true |
593 | case *ast.ForStmt: |
594 | return true |
595 | case *ast.IfStmt: |
596 | return true |
597 | case *ast.LabeledStmt: |
598 | return f.endsBasicSourceBlock(s.Stmt) |
599 | case *ast.RangeStmt: |
600 | return true |
601 | case *ast.SwitchStmt: |
602 | return true |
603 | case *ast.SelectStmt: |
604 | return true |
605 | case *ast.TypeSwitchStmt: |
606 | return true |
607 | case *ast.ExprStmt: |
608 | // Calls to panic change the flow. |
609 | // We really should verify that "panic" is the predefined function, |
610 | // but without type checking we can't and the likelihood of it being |
611 | // an actual problem is vanishingly small. |
612 | if call, ok := s.X.(*ast.CallExpr); ok { |
613 | if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 { |
614 | return true |
615 | } |
616 | } |
617 | } |
618 | found, _ := hasFuncLiteral(s) |
619 | return found |
620 | } |
621 | |
622 | // funcLitFinder implements the ast.Visitor pattern to find the location of any |
623 | // function literal in a subtree. |
624 | type funcLitFinder token.Pos |
625 | |
626 | func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) { |
627 | if f.found() { |
628 | return nil // Prune search. |
629 | } |
630 | switch n := node.(type) { |
631 | case *ast.FuncLit: |
632 | *f = funcLitFinder(n.Body.Lbrace) |
633 | return nil // Prune search. |
634 | } |
635 | return f |
636 | } |
637 | |
638 | func (f *funcLitFinder) found() bool { |
639 | return token.Pos(*f) != token.NoPos |
640 | } |
641 | |
642 | // Sort interface for []block1; used for self-check in addVariables. |
643 | |
644 | type block1 struct { |
645 | Block |
646 | index int |
647 | } |
648 | |
649 | type blockSlice []block1 |
650 | |
651 | func (b blockSlice) Len() int { return len(b) } |
652 | func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte } |
653 | func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
654 | |
655 | // offset translates a token position into a 0-indexed byte offset. |
656 | func (f *File) offset(pos token.Pos) int { |
657 | return f.fset.Position(pos).Offset |
658 | } |
659 | |
660 | // addVariables adds to the end of the file the declarations to set up the counter and position variables. |
661 | func (f *File) addVariables(w io.Writer) { |
662 | // Self-check: Verify that the instrumented basic blocks are disjoint. |
663 | t := make([]block1, len(f.blocks)) |
664 | for i := range f.blocks { |
665 | t[i].Block = f.blocks[i] |
666 | t[i].index = i |
667 | } |
668 | sort.Sort(blockSlice(t)) |
669 | for i := 1; i < len(t); i++ { |
670 | if t[i-1].endByte > t[i].startByte { |
671 | fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index) |
672 | // Note: error message is in byte positions, not token positions. |
673 | fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n", |
674 | f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte), |
675 | f.name, f.offset(t[i].startByte), f.offset(t[i].endByte)) |
676 | } |
677 | } |
678 | |
679 | // Declare the coverage struct as a package-level variable. |
680 | fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar) |
681 | fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks)) |
682 | fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks)) |
683 | fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks)) |
684 | fmt.Fprintf(w, "} {\n") |
685 | |
686 | // Initialize the position array field. |
687 | fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks)) |
688 | |
689 | // A nice long list of positions. Each position is encoded as follows to reduce size: |
690 | // - 32-bit starting line number |
691 | // - 32-bit ending line number |
692 | // - (16 bit ending column number << 16) | (16-bit starting column number). |
693 | for i, block := range f.blocks { |
694 | start := f.fset.Position(block.startByte) |
695 | end := f.fset.Position(block.endByte) |
696 | fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i) |
697 | } |
698 | |
699 | // Close the position array. |
700 | fmt.Fprintf(w, "\t},\n") |
701 | |
702 | // Initialize the position array field. |
703 | fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks)) |
704 | |
705 | // A nice long list of statements-per-block, so we can give a conventional |
706 | // valuation of "percent covered". To save space, it's a 16-bit number, so we |
707 | // clamp it if it overflows - won't matter in practice. |
708 | for i, block := range f.blocks { |
709 | n := block.numStmt |
710 | if n > 1<<16-1 { |
711 | n = 1<<16 - 1 |
712 | } |
713 | fmt.Fprintf(w, "\t\t%d, // %d\n", n, i) |
714 | } |
715 | |
716 | // Close the statements-per-block array. |
717 | fmt.Fprintf(w, "\t},\n") |
718 | |
719 | // Close the struct initialization. |
720 | fmt.Fprintf(w, "}\n") |
721 | } |
722 |
Members