...

Source file src/cmd/cover/cover.go

     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		"io/ioutil"
    16		"log"
    17		"os"
    18		"sort"
    19	
    20		"cmd/internal/edit"
    21		"cmd/internal/objabi"
    22	)
    23	
    24	const usageMessage = "" +
    25		`Usage of 'go tool cover':
    26	Given a coverage profile produced by 'go test':
    27		go test -coverprofile=c.out
    28	
    29	Open a web browser displaying annotated source code:
    30		go tool cover -html=c.out
    31	
    32	Write out an HTML file instead of launching a web browser:
    33		go tool cover -html=c.out -o coverage.html
    34	
    35	Display coverage percentages to stdout for each function:
    36		go tool cover -func=c.out
    37	
    38	Finally, to generate modified source code with coverage annotations
    39	(what go test -cover does):
    40		go tool cover -mode=set -var=CoverageVariableName program.go
    41	`
    42	
    43	func usage() {
    44		fmt.Fprintln(os.Stderr, usageMessage)
    45		fmt.Fprintln(os.Stderr, "Flags:")
    46		flag.PrintDefaults()
    47		fmt.Fprintln(os.Stderr, "\n  Only one of -html, -func, or -mode may be set.")
    48		os.Exit(2)
    49	}
    50	
    51	var (
    52		mode    = flag.String("mode", "", "coverage mode: set, count, atomic")
    53		varVar  = flag.String("var", "GoCover", "name of coverage variable to generate")
    54		output  = flag.String("o", "", "file for output; default: stdout")
    55		htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
    56		funcOut = flag.String("func", "", "output coverage profile information for each function")
    57	)
    58	
    59	var profile string // The profile to read; the value of -html or -func
    60	
    61	var counterStmt func(*File, string) string
    62	
    63	const (
    64		atomicPackagePath = "sync/atomic"
    65		atomicPackageName = "_cover_atomic_"
    66	)
    67	
    68	func main() {
    69		objabi.AddVersionFlag()
    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 *varVar != "" && !token.IsIdentifier(*varVar) {
   120			return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
   121		}
   122	
   123		if *mode != "" {
   124			switch *mode {
   125			case "set":
   126				counterStmt = setCounterStmt
   127			case "count":
   128				counterStmt = incCounterStmt
   129			case "atomic":
   130				counterStmt = atomicCounterStmt
   131			default:
   132				return fmt.Errorf("unknown -mode %v", *mode)
   133			}
   134	
   135			if flag.NArg() == 0 {
   136				return fmt.Errorf("missing source file")
   137			} else if flag.NArg() == 1 {
   138				return nil
   139			}
   140		} else if flag.NArg() == 0 {
   141			return nil
   142		}
   143		return fmt.Errorf("too many arguments")
   144	}
   145	
   146	// Block represents the information about a basic block to be recorded in the analysis.
   147	// Note: Our definition of basic block is based on control structures; we don't break
   148	// apart && and ||. We could but it doesn't seem important enough to bother.
   149	type Block struct {
   150		startByte token.Pos
   151		endByte   token.Pos
   152		numStmt   int
   153	}
   154	
   155	// File is a wrapper for the state of a file used in the parser.
   156	// The basic parse tree walker is a method of this type.
   157	type File struct {
   158		fset    *token.FileSet
   159		name    string // Name of file.
   160		astFile *ast.File
   161		blocks  []Block
   162		content []byte
   163		edit    *edit.Buffer
   164	}
   165	
   166	// findText finds text in the original source, starting at pos.
   167	// It correctly skips over comments and assumes it need not
   168	// handle quoted strings.
   169	// It returns a byte offset within f.src.
   170	func (f *File) findText(pos token.Pos, text string) int {
   171		b := []byte(text)
   172		start := f.offset(pos)
   173		i := start
   174		s := f.content
   175		for i < len(s) {
   176			if bytes.HasPrefix(s[i:], b) {
   177				return i
   178			}
   179			if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
   180				for i < len(s) && s[i] != '\n' {
   181					i++
   182				}
   183				continue
   184			}
   185			if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
   186				for i += 2; ; i++ {
   187					if i+2 > len(s) {
   188						return 0
   189					}
   190					if s[i] == '*' && s[i+1] == '/' {
   191						i += 2
   192						break
   193					}
   194				}
   195				continue
   196			}
   197			i++
   198		}
   199		return -1
   200	}
   201	
   202	// Visit implements the ast.Visitor interface.
   203	func (f *File) Visit(node ast.Node) ast.Visitor {
   204		switch n := node.(type) {
   205		case *ast.BlockStmt:
   206			// If it's a switch or select, the body is a list of case clauses; don't tag the block itself.
   207			if len(n.List) > 0 {
   208				switch n.List[0].(type) {
   209				case *ast.CaseClause: // switch
   210					for _, n := range n.List {
   211						clause := n.(*ast.CaseClause)
   212						f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
   213					}
   214					return f
   215				case *ast.CommClause: // select
   216					for _, n := range n.List {
   217						clause := n.(*ast.CommClause)
   218						f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
   219					}
   220					return f
   221				}
   222			}
   223			f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true) // +1 to step past closing brace.
   224		case *ast.IfStmt:
   225			if n.Init != nil {
   226				ast.Walk(f, n.Init)
   227			}
   228			ast.Walk(f, n.Cond)
   229			ast.Walk(f, n.Body)
   230			if n.Else == nil {
   231				return nil
   232			}
   233			// The elses are special, because if we have
   234			//	if x {
   235			//	} else if y {
   236			//	}
   237			// we want to cover the "if y". To do this, we need a place to drop the counter,
   238			// so we add a hidden block:
   239			//	if x {
   240			//	} else {
   241			//		if y {
   242			//		}
   243			//	}
   244			elseOffset := f.findText(n.Body.End(), "else")
   245			if elseOffset < 0 {
   246				panic("lost else")
   247			}
   248			f.edit.Insert(elseOffset+4, "{")
   249			f.edit.Insert(f.offset(n.Else.End()), "}")
   250	
   251			// We just created a block, now walk it.
   252			// Adjust the position of the new block to start after
   253			// the "else". That will cause it to follow the "{"
   254			// we inserted above.
   255			pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
   256			switch stmt := n.Else.(type) {
   257			case *ast.IfStmt:
   258				block := &ast.BlockStmt{
   259					Lbrace: pos,
   260					List:   []ast.Stmt{stmt},
   261					Rbrace: stmt.End(),
   262				}
   263				n.Else = block
   264			case *ast.BlockStmt:
   265				stmt.Lbrace = pos
   266			default:
   267				panic("unexpected node type in if")
   268			}
   269			ast.Walk(f, n.Else)
   270			return nil
   271		case *ast.SelectStmt:
   272			// Don't annotate an empty select - creates a syntax error.
   273			if n.Body == nil || len(n.Body.List) == 0 {
   274				return nil
   275			}
   276		case *ast.SwitchStmt:
   277			// Don't annotate an empty switch - creates a syntax error.
   278			if n.Body == nil || len(n.Body.List) == 0 {
   279				if n.Init != nil {
   280					ast.Walk(f, n.Init)
   281				}
   282				if n.Tag != nil {
   283					ast.Walk(f, n.Tag)
   284				}
   285				return nil
   286			}
   287		case *ast.TypeSwitchStmt:
   288			// Don't annotate an empty type switch - creates a syntax error.
   289			if n.Body == nil || len(n.Body.List) == 0 {
   290				if n.Init != nil {
   291					ast.Walk(f, n.Init)
   292				}
   293				ast.Walk(f, n.Assign)
   294				return nil
   295			}
   296		}
   297		return f
   298	}
   299	
   300	func annotate(name string) {
   301		fset := token.NewFileSet()
   302		content, err := ioutil.ReadFile(name)
   303		if err != nil {
   304			log.Fatalf("cover: %s: %s", name, err)
   305		}
   306		parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
   307		if err != nil {
   308			log.Fatalf("cover: %s: %s", name, err)
   309		}
   310	
   311		file := &File{
   312			fset:    fset,
   313			name:    name,
   314			content: content,
   315			edit:    edit.NewBuffer(content),
   316			astFile: parsedFile,
   317		}
   318		if *mode == "atomic" {
   319			// Add import of sync/atomic immediately after package clause.
   320			// We do this even if there is an existing import, because the
   321			// existing import may be shadowed at any given place we want
   322			// to refer to it, and our name (_cover_atomic_) is less likely to
   323			// be shadowed.
   324			file.edit.Insert(file.offset(file.astFile.Name.End()),
   325				fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
   326		}
   327	
   328		ast.Walk(file, file.astFile)
   329		newContent := file.edit.Bytes()
   330	
   331		fd := os.Stdout
   332		if *output != "" {
   333			var err error
   334			fd, err = os.Create(*output)
   335			if err != nil {
   336				log.Fatalf("cover: %s", err)
   337			}
   338		}
   339	
   340		fmt.Fprintf(fd, "//line %s:1\n", name)
   341		fd.Write(newContent)
   342	
   343		// After printing the source tree, add some declarations for the counters etc.
   344		// We could do this by adding to the tree, but it's easier just to print the text.
   345		file.addVariables(fd)
   346	}
   347	
   348	// setCounterStmt returns the expression: __count[23] = 1.
   349	func setCounterStmt(f *File, counter string) string {
   350		return fmt.Sprintf("%s = 1", counter)
   351	}
   352	
   353	// incCounterStmt returns the expression: __count[23]++.
   354	func incCounterStmt(f *File, counter string) string {
   355		return fmt.Sprintf("%s++", counter)
   356	}
   357	
   358	// atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1)
   359	func atomicCounterStmt(f *File, counter string) string {
   360		return fmt.Sprintf("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
   361	}
   362	
   363	// newCounter creates a new counter expression of the appropriate form.
   364	func (f *File) newCounter(start, end token.Pos, numStmt int) string {
   365		stmt := counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar, len(f.blocks)))
   366		f.blocks = append(f.blocks, Block{start, end, numStmt})
   367		return stmt
   368	}
   369	
   370	// addCounters takes a list of statements and adds counters to the beginning of
   371	// each basic block at the top level of that list. For instance, given
   372	//
   373	//	S1
   374	//	if cond {
   375	//		S2
   376	// 	}
   377	//	S3
   378	//
   379	// counters will be added before S1 and before S3. The block containing S2
   380	// will be visited in a separate call.
   381	// TODO: Nested simple blocks get unnecessary (but correct) counters
   382	func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
   383		// Special case: make sure we add a counter to an empty block. Can't do this below
   384		// or we will add a counter to an empty statement list after, say, a return statement.
   385		if len(list) == 0 {
   386			f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
   387			return
   388		}
   389		// Make a copy of the list, as we may mutate it and should leave the
   390		// existing list intact.
   391		list = append([]ast.Stmt(nil), list...)
   392		// We have a block (statement list), but it may have several basic blocks due to the
   393		// appearance of statements that affect the flow of control.
   394		for {
   395			// Find first statement that affects flow of control (break, continue, if, etc.).
   396			// It will be the last statement of this basic block.
   397			var last int
   398			end := blockEnd
   399			for last = 0; last < len(list); last++ {
   400				stmt := list[last]
   401				end = f.statementBoundary(stmt)
   402				if f.endsBasicSourceBlock(stmt) {
   403					// If it is a labeled statement, we need to place a counter between
   404					// the label and its statement because it may be the target of a goto
   405					// and thus start a basic block. That is, given
   406					//	foo: stmt
   407					// we need to create
   408					//	foo: ; stmt
   409					// and mark the label as a block-terminating statement.
   410					// The result will then be
   411					//	foo: COUNTER[n]++; stmt
   412					// However, we can't do this if the labeled statement is already
   413					// a control statement, such as a labeled for.
   414					if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
   415						newLabel := *label
   416						newLabel.Stmt = &ast.EmptyStmt{
   417							Semicolon: label.Stmt.Pos(),
   418							Implicit:  true,
   419						}
   420						end = label.Pos() // Previous block ends before the label.
   421						list[last] = &newLabel
   422						// Open a gap and drop in the old statement, now without a label.
   423						list = append(list, nil)
   424						copy(list[last+1:], list[last:])
   425						list[last+1] = label.Stmt
   426					}
   427					last++
   428					extendToClosingBrace = false // Block is broken up now.
   429					break
   430				}
   431			}
   432			if extendToClosingBrace {
   433				end = blockEnd
   434			}
   435			if pos != end { // Can have no source to cover if e.g. blocks abut.
   436				f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
   437			}
   438			list = list[last:]
   439			if len(list) == 0 {
   440				break
   441			}
   442			pos = list[0].Pos()
   443			insertPos = pos
   444		}
   445	}
   446	
   447	// hasFuncLiteral reports the existence and position of the first func literal
   448	// in the node, if any. If a func literal appears, it usually marks the termination
   449	// of a basic block because the function body is itself a block.
   450	// Therefore we draw a line at the start of the body of the first function literal we find.
   451	// TODO: what if there's more than one? Probably doesn't matter much.
   452	func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
   453		if n == nil {
   454			return false, 0
   455		}
   456		var literal funcLitFinder
   457		ast.Walk(&literal, n)
   458		return literal.found(), token.Pos(literal)
   459	}
   460	
   461	// statementBoundary finds the location in s that terminates the current basic
   462	// block in the source.
   463	func (f *File) statementBoundary(s ast.Stmt) token.Pos {
   464		// Control flow statements are easy.
   465		switch s := s.(type) {
   466		case *ast.BlockStmt:
   467			// Treat blocks like basic blocks to avoid overlapping counters.
   468			return s.Lbrace
   469		case *ast.IfStmt:
   470			found, pos := hasFuncLiteral(s.Init)
   471			if found {
   472				return pos
   473			}
   474			found, pos = hasFuncLiteral(s.Cond)
   475			if found {
   476				return pos
   477			}
   478			return s.Body.Lbrace
   479		case *ast.ForStmt:
   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			found, pos = hasFuncLiteral(s.Post)
   489			if found {
   490				return pos
   491			}
   492			return s.Body.Lbrace
   493		case *ast.LabeledStmt:
   494			return f.statementBoundary(s.Stmt)
   495		case *ast.RangeStmt:
   496			found, pos := hasFuncLiteral(s.X)
   497			if found {
   498				return pos
   499			}
   500			return s.Body.Lbrace
   501		case *ast.SwitchStmt:
   502			found, pos := hasFuncLiteral(s.Init)
   503			if found {
   504				return pos
   505			}
   506			found, pos = hasFuncLiteral(s.Tag)
   507			if found {
   508				return pos
   509			}
   510			return s.Body.Lbrace
   511		case *ast.SelectStmt:
   512			return s.Body.Lbrace
   513		case *ast.TypeSwitchStmt:
   514			found, pos := hasFuncLiteral(s.Init)
   515			if found {
   516				return pos
   517			}
   518			return s.Body.Lbrace
   519		}
   520		// If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.
   521		// If it does, that's tricky because we want to exclude the body of the function from this block.
   522		// Draw a line at the start of the body of the first function literal we find.
   523		// TODO: what if there's more than one? Probably doesn't matter much.
   524		found, pos := hasFuncLiteral(s)
   525		if found {
   526			return pos
   527		}
   528		return s.End()
   529	}
   530	
   531	// endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc.,
   532	// or if it's just problematic, for instance contains a function literal, which will complicate
   533	// accounting due to the block-within-an expression.
   534	func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
   535		switch s := s.(type) {
   536		case *ast.BlockStmt:
   537			// Treat blocks like basic blocks to avoid overlapping counters.
   538			return true
   539		case *ast.BranchStmt:
   540			return true
   541		case *ast.ForStmt:
   542			return true
   543		case *ast.IfStmt:
   544			return true
   545		case *ast.LabeledStmt:
   546			return true // A goto may branch here, starting a new basic block.
   547		case *ast.RangeStmt:
   548			return true
   549		case *ast.SwitchStmt:
   550			return true
   551		case *ast.SelectStmt:
   552			return true
   553		case *ast.TypeSwitchStmt:
   554			return true
   555		case *ast.ExprStmt:
   556			// Calls to panic change the flow.
   557			// We really should verify that "panic" is the predefined function,
   558			// but without type checking we can't and the likelihood of it being
   559			// an actual problem is vanishingly small.
   560			if call, ok := s.X.(*ast.CallExpr); ok {
   561				if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
   562					return true
   563				}
   564			}
   565		}
   566		found, _ := hasFuncLiteral(s)
   567		return found
   568	}
   569	
   570	// isControl reports whether s is a control statement that, if labeled, cannot be
   571	// separated from its label.
   572	func (f *File) isControl(s ast.Stmt) bool {
   573		switch s.(type) {
   574		case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
   575			return true
   576		}
   577		return false
   578	}
   579	
   580	// funcLitFinder implements the ast.Visitor pattern to find the location of any
   581	// function literal in a subtree.
   582	type funcLitFinder token.Pos
   583	
   584	func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
   585		if f.found() {
   586			return nil // Prune search.
   587		}
   588		switch n := node.(type) {
   589		case *ast.FuncLit:
   590			*f = funcLitFinder(n.Body.Lbrace)
   591			return nil // Prune search.
   592		}
   593		return f
   594	}
   595	
   596	func (f *funcLitFinder) found() bool {
   597		return token.Pos(*f) != token.NoPos
   598	}
   599	
   600	// Sort interface for []block1; used for self-check in addVariables.
   601	
   602	type block1 struct {
   603		Block
   604		index int
   605	}
   606	
   607	type blockSlice []block1
   608	
   609	func (b blockSlice) Len() int           { return len(b) }
   610	func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
   611	func (b blockSlice) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   612	
   613	// offset translates a token position into a 0-indexed byte offset.
   614	func (f *File) offset(pos token.Pos) int {
   615		return f.fset.Position(pos).Offset
   616	}
   617	
   618	// addVariables adds to the end of the file the declarations to set up the counter and position variables.
   619	func (f *File) addVariables(w io.Writer) {
   620		// Self-check: Verify that the instrumented basic blocks are disjoint.
   621		t := make([]block1, len(f.blocks))
   622		for i := range f.blocks {
   623			t[i].Block = f.blocks[i]
   624			t[i].index = i
   625		}
   626		sort.Sort(blockSlice(t))
   627		for i := 1; i < len(t); i++ {
   628			if t[i-1].endByte > t[i].startByte {
   629				fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
   630				// Note: error message is in byte positions, not token positions.
   631				fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
   632					f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
   633					f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
   634			}
   635		}
   636	
   637		// Declare the coverage struct as a package-level variable.
   638		fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
   639		fmt.Fprintf(w, "\tCount     [%d]uint32\n", len(f.blocks))
   640		fmt.Fprintf(w, "\tPos       [3 * %d]uint32\n", len(f.blocks))
   641		fmt.Fprintf(w, "\tNumStmt   [%d]uint16\n", len(f.blocks))
   642		fmt.Fprintf(w, "} {\n")
   643	
   644		// Initialize the position array field.
   645		fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
   646	
   647		// A nice long list of positions. Each position is encoded as follows to reduce size:
   648		// - 32-bit starting line number
   649		// - 32-bit ending line number
   650		// - (16 bit ending column number << 16) | (16-bit starting column number).
   651		for i, block := range f.blocks {
   652			start := f.fset.Position(block.startByte)
   653			end := f.fset.Position(block.endByte)
   654	
   655			start, end = dedup(start, end)
   656	
   657			fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
   658		}
   659	
   660		// Close the position array.
   661		fmt.Fprintf(w, "\t},\n")
   662	
   663		// Initialize the position array field.
   664		fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
   665	
   666		// A nice long list of statements-per-block, so we can give a conventional
   667		// valuation of "percent covered". To save space, it's a 16-bit number, so we
   668		// clamp it if it overflows - won't matter in practice.
   669		for i, block := range f.blocks {
   670			n := block.numStmt
   671			if n > 1<<16-1 {
   672				n = 1<<16 - 1
   673			}
   674			fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
   675		}
   676	
   677		// Close the statements-per-block array.
   678		fmt.Fprintf(w, "\t},\n")
   679	
   680		// Close the struct initialization.
   681		fmt.Fprintf(w, "}\n")
   682	
   683		// Emit a reference to the atomic package to avoid
   684		// import and not used error when there's no code in a file.
   685		if *mode == "atomic" {
   686			fmt.Fprintf(w, "var _ = %s.LoadUint32\n", atomicPackageName)
   687		}
   688	}
   689	
   690	// It is possible for positions to repeat when there is a line
   691	// directive that does not specify column information and the input
   692	// has not been passed through gofmt.
   693	// See issues #27530 and #30746.
   694	// Tests are TestHtmlUnformatted and TestLineDup.
   695	// We use a map to avoid duplicates.
   696	
   697	// pos2 is a pair of token.Position values, used as a map key type.
   698	type pos2 struct {
   699		p1, p2 token.Position
   700	}
   701	
   702	// seenPos2 tracks whether we have seen a token.Position pair.
   703	var seenPos2 = make(map[pos2]bool)
   704	
   705	// dedup takes a token.Position pair and returns a pair that does not
   706	// duplicate any existing pair. The returned pair will have the Offset
   707	// fields cleared.
   708	func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
   709		key := pos2{
   710			p1: p1,
   711			p2: p2,
   712		}
   713	
   714		// We want to ignore the Offset fields in the map,
   715		// since cover uses only file/line/column.
   716		key.p1.Offset = 0
   717		key.p2.Offset = 0
   718	
   719		for seenPos2[key] {
   720			key.p2.Column++
   721		}
   722		seenPos2[key] = true
   723	
   724		return key.p1, key.p2
   725	}
   726	

View as plain text