You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

722 lines
20 KiB

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