src/cmd/cover/cover.go GO 1,365 lines View on github.com → Search inside
1// Copyright 2013 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package main67import (8	"bytes"9	"cmd/internal/cov/covcmd"10	"cmp"11	"encoding/json"12	"flag"13	"fmt"14	"go/ast"15	"go/parser"16	"go/scanner"17	"go/token"18	"internal/coverage"19	"internal/coverage/encodemeta"20	"internal/coverage/slicewriter"21	"io"22	"log"23	"os"24	"path/filepath"25	"slices"26	"strconv"27	"strings"2829	"cmd/internal/edit"30	"cmd/internal/objabi"31	"cmd/internal/telemetry/counter"32)3334const usageMessage = "" +35	`Usage of 'go tool cover':36Given a coverage profile produced by 'go test':37	go test -coverprofile=c.out3839Open a web browser displaying annotated source code:40	go tool cover -html=c.out4142Write out an HTML file instead of launching a web browser:43	go tool cover -html=c.out -o coverage.html4445Display coverage percentages to stdout for each function:46	go tool cover -func=c.out4748Finally, to generate modified source code with coverage annotations49for a package (what go test -cover does):50	go tool cover -mode=set -var=CoverageVariableName \51		-pkgcfg=<config> -outfilelist=<file> file1.go ... fileN.go5253where -pkgcfg points to a file containing the package path,54package name, module path, and related info from "go build",55and -outfilelist points to a file containing the filenames56of the instrumented output files (one per input file).57See https://pkg.go.dev/cmd/internal/cov/covcmd#CoverPkgConfig for58more on the package config.59`6061func usage() {62	fmt.Fprint(os.Stderr, usageMessage)63	fmt.Fprintln(os.Stderr, "\nFlags:")64	flag.PrintDefaults()65	fmt.Fprintln(os.Stderr, "\n  Only one of -html, -func, or -mode may be set.")66	os.Exit(2)67}6869var (70	mode             = flag.String("mode", "", "coverage mode: set, count, atomic")71	varVar           = flag.String("var", "GoCover", "name of coverage variable to generate")72	output           = flag.String("o", "", "file for output")73	outfilelist      = flag.String("outfilelist", "", "file containing list of output files (one per line) if -pkgcfg is in use")74	htmlOut          = flag.String("html", "", "generate HTML representation of coverage profile")75	funcOut          = flag.String("func", "", "output coverage profile information for each function")76	pkgcfg           = flag.String("pkgcfg", "", "enable full-package instrumentation mode using params from specified config file")77	pkgconfig        covcmd.CoverPkgConfig78	outputfiles      []string // list of *.cover.go instrumented outputs to write, one per input (set when -pkgcfg is in use)79	profile          string   // The profile to read; the value of -html or -func80	counterStmt      func(*File, string) string81	covervarsoutfile string // an additional Go source file into which we'll write definitions of coverage counter variables + meta data variables (set when -pkgcfg is in use).82	cmode            coverage.CounterMode83	cgran            coverage.CounterGranularity84)8586const (87	atomicPackagePath = "sync/atomic"88	atomicPackageName = "_cover_atomic_"89)9091func main() {92	counter.Open()9394	objabi.AddVersionFlag()95	flag.Usage = usage96	objabi.Flagparse(usage)97	counter.Inc("cover/invocations")98	counter.CountFlags("cover/flag:", *flag.CommandLine)99100	// Usage information when no arguments.101	if flag.NFlag() == 0 && flag.NArg() == 0 {102		flag.Usage()103	}104105	err := parseFlags()106	if err != nil {107		fmt.Fprintln(os.Stderr, err)108		fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)109		os.Exit(2)110	}111112	// Generate coverage-annotated source.113	if *mode != "" {114		annotate(flag.Args())115		return116	}117118	// Output HTML or function coverage information.119	if *htmlOut != "" {120		err = htmlOutput(profile, *output)121	} else {122		err = funcOutput(profile, *output)123	}124125	if err != nil {126		fmt.Fprintf(os.Stderr, "cover: %v\n", err)127		os.Exit(2)128	}129}130131// parseFlags sets the profile and counterStmt globals and performs validations.132func parseFlags() error {133	profile = *htmlOut134	if *funcOut != "" {135		if profile != "" {136			return fmt.Errorf("too many options")137		}138		profile = *funcOut139	}140141	// Must either display a profile or rewrite Go source.142	if (profile == "") == (*mode == "") {143		return fmt.Errorf("too many options")144	}145146	if *varVar != "" && !token.IsIdentifier(*varVar) {147		return fmt.Errorf("-var: %q is not a valid identifier", *varVar)148	}149150	if *mode != "" {151		switch *mode {152		case "set":153			counterStmt = setCounterStmt154			cmode = coverage.CtrModeSet155		case "count":156			counterStmt = incCounterStmt157			cmode = coverage.CtrModeCount158		case "atomic":159			counterStmt = atomicCounterStmt160			cmode = coverage.CtrModeAtomic161		case "regonly":162			counterStmt = nil163			cmode = coverage.CtrModeRegOnly164		case "testmain":165			counterStmt = nil166			cmode = coverage.CtrModeTestMain167		default:168			return fmt.Errorf("unknown -mode %v", *mode)169		}170171		if flag.NArg() == 0 {172			return fmt.Errorf("missing source file(s)")173		} else {174			if *pkgcfg != "" {175				if *output != "" {176					return fmt.Errorf("please use '-outfilelist' flag instead of '-o'")177				}178				var err error179				if outputfiles, err = readOutFileList(*outfilelist); err != nil {180					return err181				}182				covervarsoutfile = outputfiles[0]183				outputfiles = outputfiles[1:]184				numInputs := len(flag.Args())185				numOutputs := len(outputfiles)186				if numOutputs != numInputs {187					return fmt.Errorf("number of output files (%d) not equal to number of input files (%d)", numOutputs, numInputs)188				}189				if err := readPackageConfig(*pkgcfg); err != nil {190					return err191				}192				return nil193			} else {194				if *outfilelist != "" {195					return fmt.Errorf("'-outfilelist' flag applicable only when -pkgcfg used")196				}197			}198			if flag.NArg() == 1 {199				return nil200			}201		}202	} else if flag.NArg() == 0 {203		return nil204	}205	return fmt.Errorf("too many arguments")206}207208func readOutFileList(path string) ([]string, error) {209	data, err := os.ReadFile(path)210	if err != nil {211		return nil, fmt.Errorf("error reading -outfilelist file %q: %v", path, err)212	}213	return strings.Split(strings.TrimSpace(string(data)), "\n"), nil214}215216func readPackageConfig(path string) error {217	data, err := os.ReadFile(path)218	if err != nil {219		return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)220	}221	if err := json.Unmarshal(data, &pkgconfig); err != nil {222		return fmt.Errorf("error reading pkgconfig file %q: %v", path, err)223	}224	switch pkgconfig.Granularity {225	case "perblock":226		cgran = coverage.CtrGranularityPerBlock227	case "perfunc":228		cgran = coverage.CtrGranularityPerFunc229	default:230		return fmt.Errorf(`%s: pkgconfig requires perblock/perfunc value`, path)231	}232	return nil233}234235// Block represents the information about a basic block to be recorded in the analysis.236// Note: Our definition of basic block is based on control structures; we don't break237// apart && and ||. We could but it doesn't seem important enough to bother.238type Block struct {239	startByte token.Pos240	endByte   token.Pos241	numStmt   int242}243244// Package holds package-specific state.245type Package struct {246	mdb            *encodemeta.CoverageMetaDataBuilder247	counterLengths []int248}249250// Function holds func-specific state.251type Func struct {252	units      []coverage.CoverableUnit253	counterVar string254}255256// File is a wrapper for the state of a file used in the parser.257// The basic parse tree walker is a method of this type.258type File struct {259	fset    *token.FileSet260	name    string // Name of file.261	astFile *ast.File262	blocks  []Block263	content []byte264	edit    *edit.Buffer265	mdb     *encodemeta.CoverageMetaDataBuilder266	fn      Func267	pkg     *Package268}269270// Range represents a contiguous range of executable code within a basic block.271type Range struct {272	pos token.Pos273	end token.Pos274}275276// codeRanges analyzes a block range and returns the sub-ranges that contain277// executable code, excluding comment-only and blank lines.278// If no executable code is found, it returns a single zero-width range at279// start, so that callers always get at least one range (required by pkgcfg280// mode, which needs a counter unit for every function body).281func (f *File) codeRanges(start, end token.Pos) []Range {282	var (283		startOffset = f.offset(start)284		endOffset   = f.offset(end)285		src         = f.content[startOffset:endOffset]286		origFile    = f.fset.File(start)287	)288289	// Create a temporary File for scanning this block.290	// We use a separate file because we're scanning a slice of the291	// original source, so positions in scanFile are relative to the292	// block start, not the original file.293	scanFile := token.NewFileSet().AddFile("", -1, len(src))294295	var s scanner.Scanner296	s.Init(scanFile, src, nil, 0)297298	// Build ranges in a single pass through the token stream.299	// We track the last line known to contain code (prevEndLine).300	// When the next token appears on a line beyond prevEndLine+1,301	// a gap (comment or blank lines) has been detected: close the302	// current range and start a new one. Using the token's position303	// directly (rather than the line start) ensures counter insertion304	// lands after any closing "*/" on that line.305	var ranges []Range306	var codeStart token.Pos // start of current code range (in origFile)307	prevEndLine := 0        // last line with code; 0 means no code yet308309	for {310		pos, tok, lit := s.Scan()311		if tok == token.EOF {312			break313		}314315		// Skip braces and automatic semicolons: braces are block316		// delimiters, not executable code. The Go spec317		// (https://go.dev/ref/spec#Semicolons) requires the scanner318		// to insert semicolons (with lit == "\n") after }, ), ], etc.319		// These are always on lines already marked by real tokens,320		// except for lone "}" lines. Skipping both prevents a lone321		// "}" from being treated as a separate code range, which322		// would cause counter insertion after return statements.323		if tok == token.LBRACE || tok == token.RBRACE {324			continue325		}326		if tok == token.SEMICOLON && lit == "\n" {327			continue328		}329330		// Use PositionFor with adjusted=false to ignore //line directives.331		startLine := scanFile.PositionFor(pos, false).Line332		endLine := startLine333		if tok == token.STRING {334			// Only string literals can span multiple lines.335			endLine = scanFile.PositionFor(s.End(), false).Line336		}337338		if prevEndLine == 0 {339			// First code token — start the first range.340			codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))341		} else if startLine > prevEndLine+1 {342			// Gap detected — close previous range, start new one.343			codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))344			ranges = append(ranges, Range{pos: codeStart, end: codeEnd})345			codeStart = origFile.Pos(startOffset + scanFile.Offset(pos))346		}347348		if endLine > prevEndLine {349			prevEndLine = endLine350		}351	}352353	// Close any open code range at the end.354	if prevEndLine > 0 {355		if prevEndLine < scanFile.LineCount() {356			// There are non-code lines after the last code line357			// (e.g., a lone "}"). Close at the next line's start.358			codeEnd := origFile.Pos(startOffset + scanFile.Offset(scanFile.LineStart(prevEndLine+1)))359			ranges = append(ranges, Range{pos: codeStart, end: codeEnd})360		} else {361			ranges = append(ranges, Range{pos: codeStart, end: end})362		}363	}364365	// If no code was found, return a zero-width range so that callers366	// still get a counter (needed for pkgcfg function registration)367	// but the range doesn't visually cover any source lines.368	if len(ranges) == 0 {369		return []Range{{pos: start, end: start}}370	}371372	return ranges373}374375// insideStatement reports whether pos falls strictly inside376// (not at the start of) any statement in stmts.377func insideStatement(pos token.Pos, stmts []ast.Stmt) bool {378	// Binary search for the first statement starting at or after pos.379	i, _ := slices.BinarySearchFunc(stmts, pos, func(s ast.Stmt, p token.Pos) int {380		return cmp.Compare(s.Pos(), p)381	})382	// Check if pos falls inside the preceding statement.383	return i > 0 && pos < stmts[i-1].End()384}385386// mergeRangesWithinStatements merges consecutive ranges when a later range's387// start position falls strictly inside a statement. This prevents counter388// insertion inside multi-line statements such as const (...) blocks.389func mergeRangesWithinStatements(ranges []Range, stmts []ast.Stmt) []Range {390	if len(ranges) <= 1 {391		return ranges392	}393	merged := []Range{ranges[0]}394	for _, r := range ranges[1:] {395		if insideStatement(r.pos, stmts) {396			// Extend previous range to cover this one.397			merged[len(merged)-1].end = r.end398		} else {399			merged = append(merged, r)400		}401	}402	return merged403}404405// findText finds text in the original source, starting at pos.406// It correctly skips over comments and assumes it need not407// handle quoted strings.408// It returns a byte offset within f.src.409func (f *File) findText(pos token.Pos, text string) int {410	b := []byte(text)411	start := f.offset(pos)412	i := start413	s := f.content414	for i < len(s) {415		if bytes.HasPrefix(s[i:], b) {416			return i417		}418		if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {419			for i < len(s) && s[i] != '\n' {420				i++421			}422			continue423		}424		if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {425			for i += 2; ; i++ {426				if i+2 > len(s) {427					return 0428				}429				if s[i] == '*' && s[i+1] == '/' {430					i += 2431					break432				}433			}434			continue435		}436		i++437	}438	return -1439}440441// Visit implements the ast.Visitor interface.442func (f *File) Visit(node ast.Node) ast.Visitor {443	switch n := node.(type) {444	case *ast.BlockStmt:445		// If it's a switch or select, the body is a list of case clauses; don't tag the block itself.446		if len(n.List) > 0 {447			switch n.List[0].(type) {448			case *ast.CaseClause: // switch449				for _, n := range n.List {450					clause := n.(*ast.CaseClause)451					f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)452				}453				return f454			case *ast.CommClause: // select455				for _, n := range n.List {456					clause := n.(*ast.CommClause)457					f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)458				}459				return f460			}461		}462		f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true) // +1 to step past closing brace.463	case *ast.IfStmt:464		if n.Init != nil {465			ast.Walk(f, n.Init)466		}467		ast.Walk(f, n.Cond)468		ast.Walk(f, n.Body)469		if n.Else == nil {470			return nil471		}472		// The elses are special, because if we have473		//	if x {474		//	} else if y {475		//	}476		// we want to cover the "if y". To do this, we need a place to drop the counter,477		// so we add a hidden block:478		//	if x {479		//	} else {480		//		if y {481		//		}482		//	}483		elseOffset := f.findText(n.Body.End(), "else")484		if elseOffset < 0 {485			panic("lost else")486		}487		f.edit.Insert(elseOffset+4, "{")488		f.edit.Insert(f.offset(n.Else.End()), "}")489490		// We just created a block, now walk it.491		// Adjust the position of the new block to start after492		// the "else". That will cause it to follow the "{"493		// we inserted above.494		pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)495		switch stmt := n.Else.(type) {496		case *ast.IfStmt:497			block := &ast.BlockStmt{498				Lbrace: pos,499				List:   []ast.Stmt{stmt},500				Rbrace: stmt.End(),501			}502			n.Else = block503		case *ast.BlockStmt:504			stmt.Lbrace = pos505		default:506			panic("unexpected node type in if")507		}508		ast.Walk(f, n.Else)509		return nil510	case *ast.SelectStmt:511		// Don't annotate an empty select - creates a syntax error.512		if n.Body == nil || len(n.Body.List) == 0 {513			return nil514		}515	case *ast.SwitchStmt:516		// Don't annotate an empty switch - creates a syntax error.517		if n.Body == nil || len(n.Body.List) == 0 {518			if n.Init != nil {519				ast.Walk(f, n.Init)520			}521			if n.Tag != nil {522				ast.Walk(f, n.Tag)523			}524			return nil525		}526	case *ast.TypeSwitchStmt:527		// Don't annotate an empty type switch - creates a syntax error.528		if n.Body == nil || len(n.Body.List) == 0 {529			if n.Init != nil {530				ast.Walk(f, n.Init)531			}532			ast.Walk(f, n.Assign)533			return nil534		}535	case *ast.FuncDecl:536		// Don't annotate functions with blank names - they cannot be executed.537		// Similarly for bodyless funcs.538		if n.Name.Name == "_" || n.Body == nil {539			return nil540		}541		fname := n.Name.Name542		// Skip AddUint32 and StoreUint32 if we're instrumenting543		// sync/atomic itself in atomic mode (out of an abundance of544		// caution), since as part of the instrumentation process we545		// add calls to AddUint32/StoreUint32, and we don't want to546		// somehow create an infinite loop.547		//548		// Note that in the current implementation (Go 1.20) both549		// routines are assembly stubs that forward calls to the550		// internal/runtime/atomic equivalents, hence the infinite551		// loop scenario is purely theoretical (maybe if in some552		// future implementation one of these functions might be553		// written in Go). See #57445 for more details.554		if atomicOnAtomic() && (fname == "AddUint32" || fname == "StoreUint32") {555			return nil556		}557		// Determine proper function or method name.558		if r := n.Recv; r != nil && len(r.List) == 1 {559			t := r.List[0].Type560			star := ""561			if p, _ := t.(*ast.StarExpr); p != nil {562				t = p.X563				star = "*"564			}565			if p, _ := t.(*ast.Ident); p != nil {566				fname = star + p.Name + "." + fname567			}568		}569		walkBody := true570		if *pkgcfg != "" {571			f.preFunc(n, fname)572			if pkgconfig.Granularity == "perfunc" {573				walkBody = false574			}575		}576		if walkBody {577			ast.Walk(f, n.Body)578		}579		if *pkgcfg != "" {580			flit := false581			f.postFunc(n, fname, flit, n.Body)582		}583		return nil584	case *ast.FuncLit:585		// For function literals enclosed in functions, just glom the586		// code for the literal in with the enclosing function (for now).587		if f.fn.counterVar != "" {588			return f589		}590591		// Hack: function literals aren't named in the go/ast representation,592		// and we don't know what name the compiler will choose. For now,593		// just make up a descriptive name.594		pos := n.Pos()595		p := f.fset.File(pos).Position(pos)596		fname := fmt.Sprintf("func.L%d.C%d", p.Line, p.Column)597		if *pkgcfg != "" {598			f.preFunc(n, fname)599		}600		if pkgconfig.Granularity != "perfunc" {601			ast.Walk(f, n.Body)602		}603		if *pkgcfg != "" {604			flit := true605			f.postFunc(n, fname, flit, n.Body)606		}607		return nil608	}609	return f610}611612func mkCounterVarName(idx int) string {613	return fmt.Sprintf("%s_%d", *varVar, idx)614}615616func mkPackageIdVar() string {617	return *varVar + "P"618}619620func mkMetaVar() string {621	return *varVar + "M"622}623624func mkPackageIdExpression() string {625	ppath := pkgconfig.PkgPath626	if hcid := coverage.HardCodedPkgID(ppath); hcid != -1 {627		return fmt.Sprintf("uint32(%d)", uint32(hcid))628	}629	return mkPackageIdVar()630}631632func (f *File) preFunc(fn ast.Node, fname string) {633	f.fn.units = f.fn.units[:0]634635	// create a new counter variable for this function.636	cv := mkCounterVarName(len(f.pkg.counterLengths))637	f.fn.counterVar = cv638}639640func (f *File) postFunc(fn ast.Node, funcname string, flit bool, body *ast.BlockStmt) {641642	// Tack on single counter write if we are in "perfunc" mode.643	singleCtr := ""644	if pkgconfig.Granularity == "perfunc" {645		singleCtr = "; " + f.newCounter(fn.Pos(), fn.Pos(), 1)646	}647648	// record the length of the counter var required.649	nc := len(f.fn.units) + coverage.FirstCtrOffset650	f.pkg.counterLengths = append(f.pkg.counterLengths, nc)651652	// FIXME: for windows, do we want "\" and not "/"? Need to test here.653	// Currently filename is formed as packagepath + "/" + basename.654	fnpos := f.fset.Position(fn.Pos())655	ppath := pkgconfig.PkgPath656	filename := ppath + "/" + filepath.Base(fnpos.Filename)657658	// The convention for cmd/cover is that if the go command that659	// kicks off coverage specifies a local import path (e.g. "go test660	// -cover ./thispackage"), the tool will capture full pathnames661	// for source files instead of relative paths, which tend to work662	// more smoothly for "go tool cover -html". See also issue #56433663	// for more details.664	if pkgconfig.Local {665		filename = f.name666	}667668	// Hand off function to meta-data builder.669	fd := coverage.FuncDesc{670		Funcname: funcname,671		Srcfile:  filename,672		Units:    f.fn.units,673		Lit:      flit,674	}675	funcId := f.mdb.AddFunc(fd)676677	hookWrite := func(cv string, which int, val string) string {678		return fmt.Sprintf("%s[%d] = %s", cv, which, val)679	}680	if *mode == "atomic" {681		hookWrite = func(cv string, which int, val string) string {682			return fmt.Sprintf("%sStoreUint32(&%s[%d], %s)",683				atomicPackagePrefix(), cv, which, val)684		}685	}686687	// Generate the registration hook sequence for the function. This688	// sequence looks like689	//690	//   counterVar[0] = <num_units>691	//   counterVar[1] = pkgId692	//   counterVar[2] = fnId693	//694	cv := f.fn.counterVar695	regHook := hookWrite(cv, 0, strconv.Itoa(len(f.fn.units))) + " ; " +696		hookWrite(cv, 1, mkPackageIdExpression()) + " ; " +697		hookWrite(cv, 2, strconv.Itoa(int(funcId))) + singleCtr698699	// Insert the registration sequence into the function. We want this sequence to700	// appear before any counter updates, so use a hack to ensure that this edit701	// applies before the edit corresponding to the prolog counter update.702703	boff := f.offset(body.Pos())704	ipos := f.fset.File(body.Pos()).Pos(boff)705	ip := f.offset(ipos)706	f.edit.Replace(ip, ip+1, string(f.content[ipos-1])+regHook+" ; ")707708	f.fn.counterVar = ""709}710711func annotate(names []string) {712	var p *Package713	if *pkgcfg != "" {714		pp := pkgconfig.PkgPath715		pn := pkgconfig.PkgName716		mp := pkgconfig.ModulePath717		mdb, err := encodemeta.NewCoverageMetaDataBuilder(pp, pn, mp)718		if err != nil {719			log.Fatalf("creating coverage meta-data builder: %v\n", err)720		}721		p = &Package{722			mdb: mdb,723		}724	}725	// TODO: process files in parallel here if it matters.726	for k, name := range names {727		if strings.ContainsAny(name, "\r\n") {728			// annotateFile uses '//line' directives, which don't permit newlines.729			log.Fatalf("cover: input path contains newline character: %q", name)730		}731732		fd := os.Stdout733		isStdout := true734		if *pkgcfg != "" {735			var err error736			fd, err = os.Create(outputfiles[k])737			if err != nil {738				log.Fatalf("cover: %s", err)739			}740			isStdout = false741		} else if *output != "" {742			var err error743			fd, err = os.Create(*output)744			if err != nil {745				log.Fatalf("cover: %s", err)746			}747			isStdout = false748		}749		p.annotateFile(name, fd)750		if !isStdout {751			if err := fd.Close(); err != nil {752				log.Fatalf("cover: %s", err)753			}754		}755	}756757	if *pkgcfg != "" {758		fd, err := os.Create(covervarsoutfile)759		if err != nil {760			log.Fatalf("cover: %s", err)761		}762		p.emitMetaData(fd)763		if err := fd.Close(); err != nil {764			log.Fatalf("cover: %s", err)765		}766	}767}768769func (p *Package) annotateFile(name string, fd io.Writer) {770	fset := token.NewFileSet()771	content, err := os.ReadFile(name)772	if err != nil {773		log.Fatalf("cover: %s: %s", name, err)774	}775	parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments|parser.SkipObjectResolution)776	if err != nil {777		log.Fatalf("cover: %s: %s", name, err)778	}779780	file := &File{781		fset:    fset,782		name:    name,783		content: content,784		edit:    edit.NewBuffer(content),785		astFile: parsedFile,786	}787	if p != nil {788		file.mdb = p.mdb789		file.pkg = p790	}791792	if *mode == "atomic" {793		// Add import of sync/atomic immediately after package clause.794		// We do this even if there is an existing import, because the795		// existing import may be shadowed at any given place we want796		// to refer to it, and our name (_cover_atomic_) is less likely to797		// be shadowed. The one exception is if we're visiting the798		// sync/atomic package itself, in which case we can refer to799		// functions directly without an import prefix. See also #57445.800		if pkgconfig.PkgPath != "sync/atomic" {801			file.edit.Insert(file.offset(file.astFile.Name.End()),802				fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))803		}804	}805	if pkgconfig.PkgName == "main" {806		file.edit.Insert(file.offset(file.astFile.Name.End()),807			"; import _ \"runtime/coverage\"")808	}809810	if counterStmt != nil {811		ast.Walk(file, file.astFile)812	}813	newContent := file.edit.Bytes()814815	if strings.ContainsAny(name, "\r\n") {816		// This should have been checked by the caller already, but we double check817		// here just to be sure we haven't missed a caller somewhere.818		panic(fmt.Sprintf("annotateFile: name contains unexpected newline character: %q", name))819	}820	fmt.Fprintf(fd, "//line %s:1:1\n", name)821	fd.Write(newContent)822823	// After printing the source tree, add some declarations for the824	// counters etc. We could do this by adding to the tree, but it's825	// easier just to print the text.826	file.addVariables(fd)827828	// Emit a reference to the atomic package to avoid829	// import and not used error when there's no code in a file.830	if *mode == "atomic" {831		fmt.Fprintf(fd, "\nvar _ = %sLoadUint32\n", atomicPackagePrefix())832	}833}834835// setCounterStmt returns the expression: __count[23] = 1.836func setCounterStmt(f *File, counter string) string {837	return fmt.Sprintf("%s = 1", counter)838}839840// incCounterStmt returns the expression: __count[23]++.841func incCounterStmt(f *File, counter string) string {842	return fmt.Sprintf("%s++", counter)843}844845// atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1)846func atomicCounterStmt(f *File, counter string) string {847	return fmt.Sprintf("%sAddUint32(&%s, 1)", atomicPackagePrefix(), counter)848}849850// newCounter creates a new counter expression of the appropriate form.851func (f *File) newCounter(start, end token.Pos, numStmt int) string {852	var stmt string853	if *pkgcfg != "" {854		slot := len(f.fn.units) + coverage.FirstCtrOffset855		if f.fn.counterVar == "" {856			panic("internal error: counter var unset")857		}858		stmt = counterStmt(f, fmt.Sprintf("%s[%d]", f.fn.counterVar, slot))859		// Physical positions, ignoring //line directives.860		stpos := f.position(start)861		enpos := f.position(end)862		stpos, enpos = dedup(stpos, enpos)863		unit := coverage.CoverableUnit{864			StLine:  uint32(stpos.Line),865			StCol:   uint32(stpos.Column),866			EnLine:  uint32(enpos.Line),867			EnCol:   uint32(enpos.Column),868			NxStmts: uint32(numStmt),869		}870		f.fn.units = append(f.fn.units, unit)871	} else {872		stmt = counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar,873			len(f.blocks)))874		f.blocks = append(f.blocks, Block{start, end, numStmt})875	}876	return stmt877}878879// addCounters takes a list of statements and adds counters to the beginning of880// each basic block at the top level of that list. For instance, given881//882//	S1883//	if cond {884//		S2885//	}886//	S3887//888// counters will be added before S1 and before S3. The block containing S2889// will be visited in a separate call.890// TODO: Nested simple blocks get unnecessary (but correct) counters891func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {892	// Special case: make sure we add a counter to an empty block. Can't do this below893	// or we will add a counter to an empty statement list after, say, a return statement.894	if len(list) == 0 {895		r := f.codeRanges(insertPos, blockEnd)[0]896		f.edit.Insert(f.offset(r.pos), f.newCounter(r.pos, r.end, 0)+";")897		return898	}899	// Make a copy of the list, as we may mutate it and should leave the900	// existing list intact.901	list = append([]ast.Stmt(nil), list...)902	// We have a block (statement list), but it may have several basic blocks due to the903	// appearance of statements that affect the flow of control.904	for {905		// Find first statement that affects flow of control (break, continue, if, etc.).906		// It will be the last statement of this basic block.907		var last int908		end := blockEnd909		for last = 0; last < len(list); last++ {910			stmt := list[last]911			end = f.statementBoundary(stmt)912			if f.endsBasicSourceBlock(stmt) {913				// If it is a labeled statement, we need to place a counter between914				// the label and its statement because it may be the target of a goto915				// and thus start a basic block. That is, given916				//	foo: stmt917				// we need to create918				//	foo: ; stmt919				// and mark the label as a block-terminating statement.920				// The result will then be921				//	foo: COUNTER[n]++; stmt922				// However, we can't do this if the labeled statement is already923				// a control statement, such as a labeled for.924				if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {925					newLabel := *label926					newLabel.Stmt = &ast.EmptyStmt{927						Semicolon: label.Stmt.Pos(),928						Implicit:  true,929					}930					end = label.Pos() // Previous block ends before the label.931					list[last] = &newLabel932					// Open a gap and drop in the old statement, now without a label.933					list = append(list, nil)934					copy(list[last+1:], list[last:])935					list[last+1] = label.Stmt936				}937				last++938				extendToClosingBrace = false // Block is broken up now.939				break940			}941		}942		if extendToClosingBrace {943			end = blockEnd944		}945		if pos != end { // Can have no source to cover if e.g. blocks abut.946			// Create counters only for executable code ranges.947			// Merge back ranges that fall inside a statement to avoid948			// inserting counters inside multi-line constructs (e.g. const blocks).949			for i, r := range mergeRangesWithinStatements(f.codeRanges(pos, end), list[:last]) {950				insertOffset := f.offset(r.pos)951				if i == 0 {952					insertOffset = f.offset(insertPos)953				}954				f.edit.Insert(insertOffset, f.newCounter(r.pos, r.end, last)+";")955			}956		}957		list = list[last:]958		if len(list) == 0 {959			break960		}961		pos = list[0].Pos()962		insertPos = pos963	}964}965966// hasFuncLiteral reports the existence and position of the first func literal967// in the node, if any. If a func literal appears, it usually marks the termination968// of a basic block because the function body is itself a block.969// Therefore we draw a line at the start of the body of the first function literal we find.970// TODO: what if there's more than one? Probably doesn't matter much.971func hasFuncLiteral(n ast.Node) (bool, token.Pos) {972	if n == nil {973		return false, 0974	}975	var literal funcLitFinder976	ast.Walk(&literal, n)977	return literal.found(), token.Pos(literal)978}979980// statementBoundary finds the location in s that terminates the current basic981// block in the source.982func (f *File) statementBoundary(s ast.Stmt) token.Pos {983	// Control flow statements are easy.984	switch s := s.(type) {985	case *ast.BlockStmt:986		// Treat blocks like basic blocks to avoid overlapping counters.987		return s.Lbrace988	case *ast.IfStmt:989		found, pos := hasFuncLiteral(s.Init)990		if found {991			return pos992		}993		found, pos = hasFuncLiteral(s.Cond)994		if found {995			return pos996		}997		return s.Body.Lbrace998	case *ast.ForStmt:999		found, pos := hasFuncLiteral(s.Init)1000		if found {1001			return pos1002		}1003		found, pos = hasFuncLiteral(s.Cond)1004		if found {1005			return pos1006		}1007		found, pos = hasFuncLiteral(s.Post)1008		if found {1009			return pos1010		}1011		return s.Body.Lbrace1012	case *ast.LabeledStmt:1013		return f.statementBoundary(s.Stmt)1014	case *ast.RangeStmt:1015		found, pos := hasFuncLiteral(s.X)1016		if found {1017			return pos1018		}1019		return s.Body.Lbrace1020	case *ast.SwitchStmt:1021		found, pos := hasFuncLiteral(s.Init)1022		if found {1023			return pos1024		}1025		found, pos = hasFuncLiteral(s.Tag)1026		if found {1027			return pos1028		}1029		return s.Body.Lbrace1030	case *ast.SelectStmt:1031		return s.Body.Lbrace1032	case *ast.TypeSwitchStmt:1033		found, pos := hasFuncLiteral(s.Init)1034		if found {1035			return pos1036		}1037		return s.Body.Lbrace1038	}1039	// If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.1040	// If it does, that's tricky because we want to exclude the body of the function from this block.1041	// Draw a line at the start of the body of the first function literal we find.1042	// TODO: what if there's more than one? Probably doesn't matter much.1043	found, pos := hasFuncLiteral(s)1044	if found {1045		return pos1046	}1047	return s.End()1048}10491050// endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc.,1051// or if it's just problematic, for instance contains a function literal, which will complicate1052// accounting due to the block-within-an expression.1053func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {1054	switch s := s.(type) {1055	case *ast.BlockStmt:1056		// Treat blocks like basic blocks to avoid overlapping counters.1057		return true1058	case *ast.BranchStmt:1059		return true1060	case *ast.ForStmt:1061		return true1062	case *ast.IfStmt:1063		return true1064	case *ast.LabeledStmt:1065		return true // A goto may branch here, starting a new basic block.1066	case *ast.RangeStmt:1067		return true1068	case *ast.SwitchStmt:1069		return true1070	case *ast.SelectStmt:1071		return true1072	case *ast.TypeSwitchStmt:1073		return true1074	case *ast.ExprStmt:1075		// Calls to panic change the flow.1076		// We really should verify that "panic" is the predefined function,1077		// but without type checking we can't and the likelihood of it being1078		// an actual problem is vanishingly small.1079		if call, ok := s.X.(*ast.CallExpr); ok {1080			if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {1081				return true1082			}1083		}1084	}1085	found, _ := hasFuncLiteral(s)1086	return found1087}10881089// isControl reports whether s is a control statement that, if labeled, cannot be1090// separated from its label.1091func (f *File) isControl(s ast.Stmt) bool {1092	switch s.(type) {1093	case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:1094		return true1095	}1096	return false1097}10981099// funcLitFinder implements the ast.Visitor pattern to find the location of any1100// function literal in a subtree.1101type funcLitFinder token.Pos11021103func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {1104	if f.found() {1105		return nil // Prune search.1106	}1107	switch n := node.(type) {1108	case *ast.FuncLit:1109		*f = funcLitFinder(n.Body.Lbrace)1110		return nil // Prune search.1111	}1112	return f1113}11141115func (f *funcLitFinder) found() bool {1116	return token.Pos(*f) != token.NoPos1117}11181119// Sort interface for []block1; used for self-check in addVariables.11201121type block1 struct {1122	Block1123	index int1124}11251126// position returns the Position for pos, ignoring //line directives.1127func (f *File) position(pos token.Pos) token.Position {1128	return f.fset.PositionFor(pos, false)1129}11301131// offset translates a token position into a 0-indexed byte offset.1132func (f *File) offset(pos token.Pos) int {1133	return f.position(pos).Offset1134}11351136// addVariables adds to the end of the file the declarations to set up the counter and position variables.1137func (f *File) addVariables(w io.Writer) {1138	if *pkgcfg != "" {1139		return1140	}1141	// Self-check: Verify that the instrumented basic blocks are disjoint.1142	t := make([]block1, len(f.blocks))1143	for i := range f.blocks {1144		t[i].Block = f.blocks[i]1145		t[i].index = i1146	}1147	slices.SortFunc(t, func(a, b block1) int {1148		return cmp.Compare(a.startByte, b.startByte)1149	})1150	for i := 1; i < len(t); i++ {1151		if t[i-1].endByte > t[i].startByte {1152			fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)1153			// Note: error message is in byte positions, not token positions.1154			fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",1155				f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),1156				f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))1157		}1158	}11591160	// Declare the coverage struct as a package-level variable.1161	fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)1162	fmt.Fprintf(w, "\tCount     [%d]uint32\n", len(f.blocks))1163	fmt.Fprintf(w, "\tPos       [3 * %d]uint32\n", len(f.blocks))1164	fmt.Fprintf(w, "\tNumStmt   [%d]uint16\n", len(f.blocks))1165	fmt.Fprintf(w, "} {\n")11661167	// Initialize the position array field.1168	fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))11691170	// A nice long list of positions. Each position is encoded as follows to reduce size:1171	// - 32-bit starting line number1172	// - 32-bit ending line number1173	// - (16 bit ending column number << 16) | (16-bit starting column number).1174	for i, block := range f.blocks {1175		// Physical positions, ignoring //line directives.1176		start := f.position(block.startByte)1177		end := f.position(block.endByte)11781179		start, end = dedup(start, end)11801181		fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)1182	}11831184	// Close the position array.1185	fmt.Fprintf(w, "\t},\n")11861187	// Initialize the position array field.1188	fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))11891190	// A nice long list of statements-per-block, so we can give a conventional1191	// valuation of "percent covered". To save space, it's a 16-bit number, so we1192	// clamp it if it overflows - won't matter in practice.1193	for i, block := range f.blocks {1194		n := block.numStmt1195		if n > 1<<16-1 {1196			n = 1<<16 - 11197		}1198		fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)1199	}12001201	// Close the statements-per-block array.1202	fmt.Fprintf(w, "\t},\n")12031204	// Close the struct initialization.1205	fmt.Fprintf(w, "}\n")1206}12071208// It is possible for positions to repeat when there is a line1209// directive that does not specify column information and the input1210// has not been passed through gofmt.1211// See issues #27530 and #30746.1212// Tests are TestHtmlUnformatted and TestLineDup.1213// We use a map to avoid duplicates.12141215// pos2 is a pair of token.Position values, used as a map key type.1216type pos2 struct {1217	p1, p2 token.Position1218}12191220// seenPos2 tracks whether we have seen a token.Position pair.1221var seenPos2 = make(map[pos2]bool)12221223// dedup takes a token.Position pair and returns a pair that does not1224// duplicate any existing pair. The returned pair will have the Offset1225// fields cleared.1226func dedup(p1, p2 token.Position) (r1, r2 token.Position) {1227	key := pos2{1228		p1: p1,1229		p2: p2,1230	}12311232	// We want to ignore the Offset fields in the map,1233	// since cover uses only file/line/column.1234	key.p1.Offset = 01235	key.p2.Offset = 012361237	for seenPos2[key] {1238		key.p2.Column++1239	}1240	seenPos2[key] = true12411242	return key.p1, key.p21243}12441245func (p *Package) emitMetaData(w io.Writer) {1246	if *pkgcfg == "" {1247		return1248	}12491250	// If the "EmitMetaFile" path has been set, invoke a helper1251	// that will write out a pre-cooked meta-data file for this package1252	// to the specified location, in effect simulating the execution1253	// of a test binary that doesn't do any testing to speak of.1254	if pkgconfig.EmitMetaFile != "" {1255		p.emitMetaFile(pkgconfig.EmitMetaFile)1256	}12571258	// Something went wrong if regonly/testmain mode is in effect and1259	// we have instrumented functions.1260	if counterStmt == nil && len(p.counterLengths) != 0 {1261		panic("internal error: seen functions with regonly/testmain")1262	}12631264	// Emit package name.1265	fmt.Fprintf(w, "\npackage %s\n\n", pkgconfig.PkgName)12661267	// Emit package ID var.1268	fmt.Fprintf(w, "\nvar %sP uint32\n", *varVar)12691270	// Emit all of the counter variables.1271	for k := range p.counterLengths {1272		cvn := mkCounterVarName(k)1273		fmt.Fprintf(w, "var %s [%d]uint32\n", cvn, p.counterLengths[k])1274	}12751276	// Emit encoded meta-data.1277	var sws slicewriter.WriteSeeker1278	digest, err := p.mdb.Emit(&sws)1279	if err != nil {1280		log.Fatalf("encoding meta-data: %v", err)1281	}1282	p.mdb = nil1283	fmt.Fprintf(w, "var %s = [...]byte{\n", mkMetaVar())1284	payload := sws.BytesWritten()1285	for k, b := range payload {1286		fmt.Fprintf(w, " 0x%x,", b)1287		if k != 0 && k%8 == 0 {1288			fmt.Fprintf(w, "\n")1289		}1290	}1291	fmt.Fprintf(w, "}\n")12921293	fixcfg := covcmd.CoverFixupConfig{1294		Strategy:           "normal",1295		MetaVar:            mkMetaVar(),1296		MetaLen:            len(payload),1297		MetaHash:           fmt.Sprintf("%x", digest),1298		PkgIdVar:           mkPackageIdVar(),1299		CounterPrefix:      *varVar,1300		CounterGranularity: pkgconfig.Granularity,1301		CounterMode:        *mode,1302	}1303	fixdata, err := json.Marshal(fixcfg)1304	if err != nil {1305		log.Fatalf("marshal fixupcfg: %v", err)1306	}1307	if err := os.WriteFile(pkgconfig.OutConfig, fixdata, 0666); err != nil {1308		log.Fatalf("error writing %s: %v", pkgconfig.OutConfig, err)1309	}1310}13111312// atomicOnAtomic returns true if we're instrumenting1313// the sync/atomic package AND using atomic mode.1314func atomicOnAtomic() bool {1315	return *mode == "atomic" && pkgconfig.PkgPath == "sync/atomic"1316}13171318// atomicPackagePrefix returns the import path prefix used to refer to1319// our special import of sync/atomic; this is either set to the1320// constant atomicPackageName plus a dot or the empty string if we're1321// instrumenting the sync/atomic package itself.1322func atomicPackagePrefix() string {1323	if atomicOnAtomic() {1324		return ""1325	}1326	return atomicPackageName + "."1327}13281329func (p *Package) emitMetaFile(outpath string) {1330	// Open output file.1331	of, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)1332	if err != nil {1333		log.Fatalf("opening covmeta %s: %v", outpath, err)1334	}13351336	if len(p.counterLengths) == 0 {1337		// This corresponds to the case where we have no functions1338		// in the package to instrument. Leave the file empty file if1339		// this happens.1340		if err = of.Close(); err != nil {1341			log.Fatalf("closing meta-data file: %v", err)1342		}1343		return1344	}13451346	// Encode meta-data.1347	var sws slicewriter.WriteSeeker1348	digest, err := p.mdb.Emit(&sws)1349	if err != nil {1350		log.Fatalf("encoding meta-data: %v", err)1351	}1352	payload := sws.BytesWritten()1353	blobs := [][]byte{payload}13541355	// Write meta-data file directly.1356	mfw := encodemeta.NewCoverageMetaFileWriter(outpath, of)1357	err = mfw.Write(digest, blobs, cmode, cgran)1358	if err != nil {1359		log.Fatalf("writing meta-data file: %v", err)1360	}1361	if err = of.Close(); err != nil {1362		log.Fatalf("closing meta-data file: %v", err)1363	}1364}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.