Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/cover/cover.go

Documentation: cmd/cover

     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/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.Fprintln(os.Stderr, usageMessage)
    44  	fmt.Fprintln(os.Stderr, "Flags:")
    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 // The profile to read; the value of -html or -func
    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  	// Usage information when no arguments.
    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  	// Generate coverage-annotated source.
    85  	if *mode != "" {
    86  		annotate(flag.Arg(0))
    87  		return
    88  	}
    89  
    90  	// Output HTML or function coverage information.
    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  // parseFlags sets the profile and counterStmt globals and performs validations.
   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  	// Must either display a profile or rewrite Go source.
   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  // Block represents the information about a basic block to be recorded in the analysis.
   146  // Note: Our definition of basic block is based on control structures; we don't break
   147  // apart && and ||. We could but it doesn't seem important enough to bother.
   148  type Block struct {
   149  	startByte token.Pos
   150  	endByte   token.Pos
   151  	numStmt   int
   152  }
   153  
   154  // File is a wrapper for the state of a file used in the parser.
   155  // The basic parse tree walker is a method of this type.
   156  type File struct {
   157  	fset    *token.FileSet
   158  	name    string // Name of file.
   159  	astFile *ast.File
   160  	blocks  []Block
   161  	content []byte
   162  	edit    *edit.Buffer
   163  }
   164  
   165  // findText finds text in the original source, starting at pos.
   166  // It correctly skips over comments and assumes it need not
   167  // handle quoted strings.
   168  // It returns a byte offset within f.src.
   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  // Visit implements the ast.Visitor interface.
   202  func (f *File) Visit(node ast.Node) ast.Visitor {
   203  	switch n := node.(type) {
   204  	case *ast.BlockStmt:
   205  		// If it's a switch or select, the body is a list of case clauses; don't tag the block itself.
   206  		if len(n.List) > 0 {
   207  			switch n.List[0].(type) {
   208  			case *ast.CaseClause: // switch
   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: // select
   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) // +1 to step past closing brace.
   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  		// The elses are special, because if we have
   233  		//	if x {
   234  		//	} else if y {
   235  		//	}
   236  		// we want to cover the "if y". To do this, we need a place to drop the counter,
   237  		// so we add a hidden block:
   238  		//	if x {
   239  		//	} else {
   240  		//		if y {
   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  		// We just created a block, now walk it.
   251  		// Adjust the position of the new block to start after
   252  		// the "else". That will cause it to follow the "{"
   253  		// we inserted above.
   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  		// Don't annotate an empty select - creates a syntax error.
   272  		if n.Body == nil || len(n.Body.List) == 0 {
   273  			return nil
   274  		}
   275  	case *ast.SwitchStmt:
   276  		// Don't annotate an empty switch - creates a syntax error.
   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  		// Don't annotate an empty type switch - creates a syntax error.
   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  		// Don't annotate functions with blank names - they cannot be executed.
   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  		// Add import of sync/atomic immediately after package clause.
   324  		// We do this even if there is an existing import, because the
   325  		// existing import may be shadowed at any given place we want
   326  		// to refer to it, and our name (_cover_atomic_) is less likely to
   327  		// be shadowed.
   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  	// After printing the source tree, add some declarations for the counters etc.
   348  	// We could do this by adding to the tree, but it's easier just to print the text.
   349  	file.addVariables(fd)
   350  }
   351  
   352  // setCounterStmt returns the expression: __count[23] = 1.
   353  func setCounterStmt(f *File, counter string) string {
   354  	return fmt.Sprintf("%s = 1", counter)
   355  }
   356  
   357  // incCounterStmt returns the expression: __count[23]++.
   358  func incCounterStmt(f *File, counter string) string {
   359  	return fmt.Sprintf("%s++", counter)
   360  }
   361  
   362  // atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1)
   363  func atomicCounterStmt(f *File, counter string) string {
   364  	return fmt.Sprintf("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
   365  }
   366  
   367  // newCounter creates a new counter expression of the appropriate form.
   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  // addCounters takes a list of statements and adds counters to the beginning of
   375  // each basic block at the top level of that list. For instance, given
   376  //
   377  //	S1
   378  //	if cond {
   379  //		S2
   380  // 	}
   381  //	S3
   382  //
   383  // counters will be added before S1 and before S3. The block containing S2
   384  // will be visited in a separate call.
   385  // TODO: Nested simple blocks get unnecessary (but correct) counters
   386  func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
   387  	// Special case: make sure we add a counter to an empty block. Can't do this below
   388  	// or we will add a counter to an empty statement list after, say, a return statement.
   389  	if len(list) == 0 {
   390  		f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
   391  		return
   392  	}
   393  	// Make a copy of the list, as we may mutate it and should leave the
   394  	// existing list intact.
   395  	list = append([]ast.Stmt(nil), list...)
   396  	// We have a block (statement list), but it may have several basic blocks due to the
   397  	// appearance of statements that affect the flow of control.
   398  	for {
   399  		// Find first statement that affects flow of control (break, continue, if, etc.).
   400  		// It will be the last statement of this basic block.
   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  				// If it is a labeled statement, we need to place a counter between
   408  				// the label and its statement because it may be the target of a goto
   409  				// and thus start a basic block. That is, given
   410  				//	foo: stmt
   411  				// we need to create
   412  				//	foo: ; stmt
   413  				// and mark the label as a block-terminating statement.
   414  				// The result will then be
   415  				//	foo: COUNTER[n]++; stmt
   416  				// However, we can't do this if the labeled statement is already
   417  				// a control statement, such as a labeled for.
   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() // Previous block ends before the label.
   425  					list[last] = &newLabel
   426  					// Open a gap and drop in the old statement, now without a label.
   427  					list = append(list, nil)
   428  					copy(list[last+1:], list[last:])
   429  					list[last+1] = label.Stmt
   430  				}
   431  				last++
   432  				extendToClosingBrace = false // Block is broken up now.
   433  				break
   434  			}
   435  		}
   436  		if extendToClosingBrace {
   437  			end = blockEnd
   438  		}
   439  		if pos != end { // Can have no source to cover if e.g. blocks abut.
   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  // hasFuncLiteral reports the existence and position of the first func literal
   452  // in the node, if any. If a func literal appears, it usually marks the termination
   453  // of a basic block because the function body is itself a block.
   454  // Therefore we draw a line at the start of the body of the first function literal we find.
   455  // TODO: what if there's more than one? Probably doesn't matter much.
   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  // statementBoundary finds the location in s that terminates the current basic
   466  // block in the source.
   467  func (f *File) statementBoundary(s ast.Stmt) token.Pos {
   468  	// Control flow statements are easy.
   469  	switch s := s.(type) {
   470  	case *ast.BlockStmt:
   471  		// Treat blocks like basic blocks to avoid overlapping counters.
   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  	// If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.
   525  	// If it does, that's tricky because we want to exclude the body of the function from this block.
   526  	// Draw a line at the start of the body of the first function literal we find.
   527  	// TODO: what if there's more than one? Probably doesn't matter much.
   528  	found, pos := hasFuncLiteral(s)
   529  	if found {
   530  		return pos
   531  	}
   532  	return s.End()
   533  }
   534  
   535  // endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc.,
   536  // or if it's just problematic, for instance contains a function literal, which will complicate
   537  // accounting due to the block-within-an expression.
   538  func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
   539  	switch s := s.(type) {
   540  	case *ast.BlockStmt:
   541  		// Treat blocks like basic blocks to avoid overlapping counters.
   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 // A goto may branch here, starting a new basic block.
   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  		// Calls to panic change the flow.
   561  		// We really should verify that "panic" is the predefined function,
   562  		// but without type checking we can't and the likelihood of it being
   563  		// an actual problem is vanishingly small.
   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  // isControl reports whether s is a control statement that, if labeled, cannot be
   575  // separated from its label.
   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  // funcLitFinder implements the ast.Visitor pattern to find the location of any
   585  // function literal in a subtree.
   586  type funcLitFinder token.Pos
   587  
   588  func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
   589  	if f.found() {
   590  		return nil // Prune search.
   591  	}
   592  	switch n := node.(type) {
   593  	case *ast.FuncLit:
   594  		*f = funcLitFinder(n.Body.Lbrace)
   595  		return nil // Prune search.
   596  	}
   597  	return f
   598  }
   599  
   600  func (f *funcLitFinder) found() bool {
   601  	return token.Pos(*f) != token.NoPos
   602  }
   603  
   604  // Sort interface for []block1; used for self-check in addVariables.
   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  // offset translates a token position into a 0-indexed byte offset.
   618  func (f *File) offset(pos token.Pos) int {
   619  	return f.fset.Position(pos).Offset
   620  }
   621  
   622  // addVariables adds to the end of the file the declarations to set up the counter and position variables.
   623  func (f *File) addVariables(w io.Writer) {
   624  	// Self-check: Verify that the instrumented basic blocks are disjoint.
   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  			// Note: error message is in byte positions, not token positions.
   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  	// Declare the coverage struct as a package-level variable.
   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  	// Initialize the position array field.
   649  	fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
   650  
   651  	// A nice long list of positions. Each position is encoded as follows to reduce size:
   652  	// - 32-bit starting line number
   653  	// - 32-bit ending line number
   654  	// - (16 bit ending column number << 16) | (16-bit starting column number).
   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  	// Close the position array.
   665  	fmt.Fprintf(w, "\t},\n")
   666  
   667  	// Initialize the position array field.
   668  	fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
   669  
   670  	// A nice long list of statements-per-block, so we can give a conventional
   671  	// valuation of "percent covered". To save space, it's a 16-bit number, so we
   672  	// clamp it if it overflows - won't matter in practice.
   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  	// Close the statements-per-block array.
   682  	fmt.Fprintf(w, "\t},\n")
   683  
   684  	// Close the struct initialization.
   685  	fmt.Fprintf(w, "}\n")
   686  
   687  	// Emit a reference to the atomic package to avoid
   688  	// import and not used error when there's no code in a file.
   689  	if *mode == "atomic" {
   690  		fmt.Fprintf(w, "var _ = %s.LoadUint32\n", atomicPackageName)
   691  	}
   692  }
   693  
   694  // It is possible for positions to repeat when there is a line
   695  // directive that does not specify column information and the input
   696  // has not been passed through gofmt.
   697  // See issues #27530 and #30746.
   698  // Tests are TestHtmlUnformatted and TestLineDup.
   699  // We use a map to avoid duplicates.
   700  
   701  // pos2 is a pair of token.Position values, used as a map key type.
   702  type pos2 struct {
   703  	p1, p2 token.Position
   704  }
   705  
   706  // seenPos2 tracks whether we have seen a token.Position pair.
   707  var seenPos2 = make(map[pos2]bool)
   708  
   709  // dedup takes a token.Position pair and returns a pair that does not
   710  // duplicate any existing pair. The returned pair will have the Offset
   711  // fields cleared.
   712  func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
   713  	key := pos2{
   714  		p1: p1,
   715  		p2: p2,
   716  	}
   717  
   718  	// We want to ignore the Offset fields in the map,
   719  	// since cover uses only file/line/column.
   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