gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/gordonklaus/ineffassign/ineffassign.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/parser"
     8  	"go/token"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  const invalidArgumentExitCode = 3
    16  
    17  var dontRecurseFlag = flag.Bool("n", false, "don't recursively check paths")
    18  
    19  func main() {
    20  	flag.Parse()
    21  
    22  	if len(flag.Args()) == 0 {
    23  		fmt.Println("missing argument: filepath")
    24  		os.Exit(invalidArgumentExitCode)
    25  	}
    26  
    27  	lintFailed := false
    28  	for _, path := range flag.Args() {
    29  		root, err := filepath.Abs(path)
    30  		if err != nil {
    31  			fmt.Printf("Error finding absolute path: %s", err)
    32  			os.Exit(invalidArgumentExitCode)
    33  		}
    34  		if walkPath(root) {
    35  			lintFailed = true
    36  		}
    37  	}
    38  	if lintFailed {
    39  		os.Exit(1)
    40  	}
    41  }
    42  
    43  func walkPath(root string) bool {
    44  	lintFailed := false
    45  	filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    46  		if err != nil {
    47  			fmt.Printf("Error during filesystem walk: %v\n", err)
    48  			return nil
    49  		}
    50  		if fi.IsDir() {
    51  			if path != root && (*dontRecurseFlag ||
    52  				filepath.Base(path) == "testdata" ||
    53  				filepath.Base(path) == "vendor") {
    54  				return filepath.SkipDir
    55  			}
    56  			return nil
    57  		}
    58  		if !strings.HasSuffix(path, ".go") {
    59  			return nil
    60  		}
    61  		fset, _, ineff := checkPath(path)
    62  		for _, id := range ineff {
    63  			fmt.Printf("%s: ineffectual assignment to %s\n", fset.Position(id.Pos()), id.Name)
    64  			lintFailed = true
    65  		}
    66  		return nil
    67  	})
    68  	return lintFailed
    69  }
    70  
    71  func checkPath(path string) (*token.FileSet, []*ast.CommentGroup, []*ast.Ident) {
    72  	fset := token.NewFileSet()
    73  	f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
    74  	if err != nil {
    75  		return nil, nil, nil
    76  	}
    77  
    78  	bld := &builder{vars: map[*ast.Object]*variable{}}
    79  	bld.walk(f)
    80  
    81  	chk := &checker{vars: bld.vars, seen: map[*block]bool{}}
    82  	for _, b := range bld.roots {
    83  		chk.check(b)
    84  	}
    85  	sort.Sort(chk.ineff)
    86  
    87  	return fset, f.Comments, chk.ineff
    88  }
    89  
    90  type builder struct {
    91  	roots     []*block
    92  	block     *block
    93  	vars      map[*ast.Object]*variable
    94  	results   []*ast.FieldList
    95  	breaks    branchStack
    96  	continues branchStack
    97  	gotos     branchStack
    98  	labelStmt *ast.LabeledStmt
    99  }
   100  
   101  type block struct {
   102  	children []*block
   103  	ops      map[*ast.Object][]operation
   104  }
   105  
   106  func (b *block) addChild(c *block) {
   107  	b.children = append(b.children, c)
   108  }
   109  
   110  type operation struct {
   111  	id     *ast.Ident
   112  	assign bool
   113  }
   114  
   115  type variable struct {
   116  	fundept int
   117  	escapes bool
   118  }
   119  
   120  func (bld *builder) walk(n ast.Node) {
   121  	if n != nil {
   122  		ast.Walk(bld, n)
   123  	}
   124  }
   125  
   126  func (bld *builder) Visit(n ast.Node) ast.Visitor {
   127  	switch n := n.(type) {
   128  	case *ast.FuncDecl:
   129  		if n.Body != nil {
   130  			bld.fun(n.Type, n.Body)
   131  		}
   132  	case *ast.FuncLit:
   133  		bld.fun(n.Type, n.Body)
   134  	case *ast.IfStmt:
   135  		bld.walk(n.Init)
   136  		bld.walk(n.Cond)
   137  		b0 := bld.block
   138  		bld.newBlock(b0)
   139  		bld.walk(n.Body)
   140  		b1 := bld.block
   141  		if n.Else != nil {
   142  			bld.newBlock(b0)
   143  			bld.walk(n.Else)
   144  			b0 = bld.block
   145  		}
   146  		bld.newBlock(b0, b1)
   147  	case *ast.ForStmt:
   148  		lbl := bld.stmtLabel(n)
   149  		brek := bld.breaks.push(lbl)
   150  		continu := bld.continues.push(lbl)
   151  		bld.walk(n.Init)
   152  		start := bld.newBlock(bld.block)
   153  		bld.walk(n.Cond)
   154  		cond := bld.block
   155  		bld.newBlock(cond)
   156  		bld.walk(n.Body)
   157  		continu.setDestination(bld.newBlock(bld.block))
   158  		bld.walk(n.Post)
   159  		bld.block.addChild(start)
   160  		brek.setDestination(bld.newBlock(cond))
   161  		bld.breaks.pop()
   162  		bld.continues.pop()
   163  	case *ast.RangeStmt:
   164  		lbl := bld.stmtLabel(n)
   165  		brek := bld.breaks.push(lbl)
   166  		continu := bld.continues.push(lbl)
   167  		bld.walk(n.X)
   168  		pre := bld.newBlock(bld.block)
   169  		start := bld.newBlock(pre)
   170  		if n.Key != nil {
   171  			lhs := []ast.Expr{n.Key}
   172  			if n.Value != nil {
   173  				lhs = append(lhs, n.Value)
   174  			}
   175  			bld.walk(&ast.AssignStmt{Lhs: lhs, Tok: n.Tok, TokPos: n.TokPos, Rhs: []ast.Expr{&ast.Ident{NamePos: n.X.End()}}})
   176  		}
   177  		bld.walk(n.Body)
   178  		bld.block.addChild(start)
   179  		continu.setDestination(pre)
   180  		brek.setDestination(bld.newBlock(pre, bld.block))
   181  		bld.breaks.pop()
   182  		bld.continues.pop()
   183  	case *ast.SwitchStmt:
   184  		bld.walk(n.Init)
   185  		bld.walk(n.Tag)
   186  		bld.swtch(n, n.Body.List)
   187  	case *ast.TypeSwitchStmt:
   188  		bld.walk(n.Init)
   189  		bld.walk(n.Assign)
   190  		bld.swtch(n, n.Body.List)
   191  	case *ast.SelectStmt:
   192  		brek := bld.breaks.push(bld.stmtLabel(n))
   193  		for _, c := range n.Body.List {
   194  			c := c.(*ast.CommClause).Comm
   195  			if s, ok := c.(*ast.AssignStmt); ok {
   196  				bld.walk(s.Rhs[0])
   197  			} else {
   198  				bld.walk(c)
   199  			}
   200  		}
   201  		b0 := bld.block
   202  		exits := make([]*block, len(n.Body.List))
   203  		dfault := false
   204  		for i, c := range n.Body.List {
   205  			c := c.(*ast.CommClause)
   206  			bld.newBlock(b0)
   207  			bld.walk(c)
   208  			exits[i] = bld.block
   209  			dfault = dfault || c.Comm == nil
   210  		}
   211  		if !dfault {
   212  			exits = append(exits, b0)
   213  		}
   214  		brek.setDestination(bld.newBlock(exits...))
   215  		bld.breaks.pop()
   216  	case *ast.LabeledStmt:
   217  		bld.gotos.get(n.Label).setDestination(bld.newBlock(bld.block))
   218  		bld.labelStmt = n
   219  		bld.walk(n.Stmt)
   220  	case *ast.BranchStmt:
   221  		switch n.Tok {
   222  		case token.BREAK:
   223  			bld.breaks.get(n.Label).addSource(bld.block)
   224  			bld.newBlock()
   225  		case token.CONTINUE:
   226  			bld.continues.get(n.Label).addSource(bld.block)
   227  			bld.newBlock()
   228  		case token.GOTO:
   229  			bld.gotos.get(n.Label).addSource(bld.block)
   230  			bld.newBlock()
   231  		}
   232  
   233  	case *ast.AssignStmt:
   234  		if n.Tok == token.QUO_ASSIGN || n.Tok == token.REM_ASSIGN {
   235  			bld.maybePanic()
   236  		}
   237  
   238  		for _, x := range n.Rhs {
   239  			bld.walk(x)
   240  		}
   241  		for i, x := range n.Lhs {
   242  			if id, ok := ident(x); ok {
   243  				if n.Tok >= token.ADD_ASSIGN && n.Tok <= token.AND_NOT_ASSIGN {
   244  					bld.use(id)
   245  				}
   246  				// Don't treat explicit initialization to zero as assignment; it is often used as shorthand for a bare declaration.
   247  				if n.Tok == token.DEFINE && i < len(n.Rhs) && isZeroLiteral(n.Rhs[i]) {
   248  					bld.use(id)
   249  				} else {
   250  					bld.assign(id)
   251  				}
   252  			} else {
   253  				bld.walk(x)
   254  			}
   255  		}
   256  	case *ast.GenDecl:
   257  		if n.Tok == token.VAR {
   258  			for _, s := range n.Specs {
   259  				s := s.(*ast.ValueSpec)
   260  				for _, x := range s.Values {
   261  					bld.walk(x)
   262  				}
   263  				for _, id := range s.Names {
   264  					if len(s.Values) > 0 {
   265  						bld.assign(id)
   266  					} else {
   267  						bld.use(id)
   268  					}
   269  				}
   270  			}
   271  		}
   272  	case *ast.IncDecStmt:
   273  		if id, ok := ident(n.X); ok {
   274  			bld.use(id)
   275  			bld.assign(id)
   276  		} else {
   277  			bld.walk(n.X)
   278  		}
   279  	case *ast.Ident:
   280  		bld.use(n)
   281  	case *ast.ReturnStmt:
   282  		for _, x := range n.Results {
   283  			bld.walk(x)
   284  		}
   285  		res := bld.results[len(bld.results)-1]
   286  		if res == nil {
   287  			break
   288  		}
   289  		for _, f := range res.List {
   290  			for _, id := range f.Names {
   291  				if n.Results != nil {
   292  					bld.assign(id)
   293  				}
   294  				bld.use(id)
   295  			}
   296  		}
   297  	case *ast.SendStmt:
   298  		bld.maybePanic()
   299  		return bld
   300  
   301  	case *ast.BinaryExpr:
   302  		if n.Op == token.EQL || n.Op == token.QUO || n.Op == token.REM {
   303  			bld.maybePanic()
   304  		}
   305  		return bld
   306  	case *ast.CallExpr:
   307  		bld.maybePanic()
   308  		return bld
   309  	case *ast.IndexExpr:
   310  		bld.maybePanic()
   311  		return bld
   312  	case *ast.UnaryExpr:
   313  		id, ok := ident(n.X)
   314  		if ix, isIx := n.X.(*ast.IndexExpr); isIx {
   315  			// We don't care about indexing into slices, but without type information we can do no better.
   316  			id, ok = ident(ix.X)
   317  		}
   318  		if ok && n.Op == token.AND {
   319  			if v, ok := bld.vars[id.Obj]; ok {
   320  				v.escapes = true
   321  			}
   322  		}
   323  		return bld
   324  	case *ast.SelectorExpr:
   325  		bld.maybePanic()
   326  		// A method call (possibly delayed via a method value) might implicitly take
   327  		// the address of its receiver, causing it to escape.
   328  		// We can't do any better here without knowing the variable's type.
   329  		if id, ok := ident(n.X); ok {
   330  			if v, ok := bld.vars[id.Obj]; ok {
   331  				v.escapes = true
   332  			}
   333  		}
   334  		return bld
   335  	case *ast.SliceExpr:
   336  		bld.maybePanic()
   337  		// We don't care about slicing into slices, but without type information we can do no better.
   338  		if id, ok := ident(n.X); ok {
   339  			if v, ok := bld.vars[id.Obj]; ok {
   340  				v.escapes = true
   341  			}
   342  		}
   343  		return bld
   344  	case *ast.StarExpr:
   345  		bld.maybePanic()
   346  		return bld
   347  	case *ast.TypeAssertExpr:
   348  		bld.maybePanic()
   349  		return bld
   350  
   351  	default:
   352  		return bld
   353  	}
   354  	return nil
   355  }
   356  
   357  func isZeroLiteral(x ast.Expr) bool {
   358  	b, ok := x.(*ast.BasicLit)
   359  	if !ok {
   360  		return false
   361  	}
   362  	switch b.Value {
   363  	case "0", "0.0", "0.", ".0", `""`:
   364  		return true
   365  	}
   366  	return false
   367  }
   368  
   369  func (bld *builder) fun(typ *ast.FuncType, body *ast.BlockStmt) {
   370  	for _, v := range bld.vars {
   371  		v.fundept++
   372  	}
   373  	bld.results = append(bld.results, typ.Results)
   374  
   375  	b := bld.block
   376  	bld.newBlock()
   377  	bld.roots = append(bld.roots, bld.block)
   378  	bld.walk(typ)
   379  	bld.walk(body)
   380  	bld.block = b
   381  
   382  	bld.results = bld.results[:len(bld.results)-1]
   383  	for _, v := range bld.vars {
   384  		v.fundept--
   385  	}
   386  }
   387  
   388  func (bld *builder) swtch(stmt ast.Stmt, cases []ast.Stmt) {
   389  	brek := bld.breaks.push(bld.stmtLabel(stmt))
   390  	b0 := bld.block
   391  	list := b0
   392  	exits := make([]*block, 0, len(cases)+1)
   393  	var dfault, fallthru *block
   394  	for _, c := range cases {
   395  		c := c.(*ast.CaseClause)
   396  
   397  		if c.List != nil {
   398  			list = bld.newBlock(list)
   399  			for _, x := range c.List {
   400  				bld.walk(x)
   401  			}
   402  		}
   403  
   404  		parents := []*block{}
   405  		if c.List != nil {
   406  			parents = append(parents, list)
   407  		}
   408  		if fallthru != nil {
   409  			parents = append(parents, fallthru)
   410  			fallthru = nil
   411  		}
   412  		bld.newBlock(parents...)
   413  		if c.List == nil {
   414  			dfault = bld.block
   415  		}
   416  		for _, s := range c.Body {
   417  			bld.walk(s)
   418  			if s, ok := s.(*ast.BranchStmt); ok && s.Tok == token.FALLTHROUGH {
   419  				fallthru = bld.block
   420  			}
   421  		}
   422  
   423  		if fallthru == nil {
   424  			exits = append(exits, bld.block)
   425  		}
   426  	}
   427  	if dfault != nil {
   428  		list.addChild(dfault)
   429  	} else {
   430  		exits = append(exits, b0)
   431  	}
   432  	brek.setDestination(bld.newBlock(exits...))
   433  	bld.breaks.pop()
   434  }
   435  
   436  // An operation that might panic marks named function results as used.
   437  func (bld *builder) maybePanic() {
   438  	if len(bld.results) == 0 {
   439  		return
   440  	}
   441  	res := bld.results[len(bld.results)-1]
   442  	if res == nil {
   443  		return
   444  	}
   445  	for _, f := range res.List {
   446  		for _, id := range f.Names {
   447  			bld.use(id)
   448  		}
   449  	}
   450  }
   451  
   452  func (bld *builder) newBlock(parents ...*block) *block {
   453  	bld.block = &block{ops: map[*ast.Object][]operation{}}
   454  	for _, b := range parents {
   455  		b.addChild(bld.block)
   456  	}
   457  	return bld.block
   458  }
   459  
   460  func (bld *builder) stmtLabel(s ast.Stmt) *ast.Object {
   461  	if ls := bld.labelStmt; ls != nil && ls.Stmt == s {
   462  		return ls.Label.Obj
   463  	}
   464  	return nil
   465  }
   466  
   467  func (bld *builder) assign(id *ast.Ident) {
   468  	bld.newOp(id, true)
   469  }
   470  
   471  func (bld *builder) use(id *ast.Ident) {
   472  	bld.newOp(id, false)
   473  }
   474  
   475  func (bld *builder) newOp(id *ast.Ident, assign bool) {
   476  	if id.Name == "_" || id.Obj == nil {
   477  		return
   478  	}
   479  
   480  	v, ok := bld.vars[id.Obj]
   481  	if !ok {
   482  		v = &variable{}
   483  		bld.vars[id.Obj] = v
   484  	}
   485  	v.escapes = v.escapes || v.fundept > 0 || bld.block == nil
   486  
   487  	if b := bld.block; b != nil {
   488  		b.ops[id.Obj] = append(b.ops[id.Obj], operation{id, assign})
   489  	}
   490  }
   491  
   492  type branchStack []*branch
   493  
   494  type branch struct {
   495  	label *ast.Object
   496  	srcs  []*block
   497  	dst   *block
   498  }
   499  
   500  func (s *branchStack) push(lbl *ast.Object) *branch {
   501  	br := &branch{label: lbl}
   502  	*s = append(*s, br)
   503  	return br
   504  }
   505  
   506  func (s *branchStack) get(lbl *ast.Ident) *branch {
   507  	for i := len(*s) - 1; i >= 0; i-- {
   508  		if br := (*s)[i]; lbl == nil || br.label == lbl.Obj {
   509  			return br
   510  		}
   511  	}
   512  	return s.push(lbl.Obj)
   513  }
   514  
   515  func (br *branch) addSource(src *block) {
   516  	br.srcs = append(br.srcs, src)
   517  	if br.dst != nil {
   518  		src.addChild(br.dst)
   519  	}
   520  }
   521  
   522  func (br *branch) setDestination(dst *block) {
   523  	br.dst = dst
   524  	for _, src := range br.srcs {
   525  		src.addChild(dst)
   526  	}
   527  }
   528  
   529  func (s *branchStack) pop() {
   530  	*s = (*s)[:len(*s)-1]
   531  }
   532  
   533  func ident(x ast.Expr) (*ast.Ident, bool) {
   534  	if p, ok := x.(*ast.ParenExpr); ok {
   535  		return ident(p.X)
   536  	}
   537  	id, ok := x.(*ast.Ident)
   538  	return id, ok
   539  }
   540  
   541  type checker struct {
   542  	vars  map[*ast.Object]*variable
   543  	seen  map[*block]bool
   544  	ineff idents
   545  }
   546  
   547  func (chk *checker) check(b *block) {
   548  	if chk.seen[b] {
   549  		return
   550  	}
   551  	chk.seen[b] = true
   552  
   553  	for obj, ops := range b.ops {
   554  		if chk.vars[obj].escapes {
   555  			continue
   556  		}
   557  	ops:
   558  		for i, op := range ops {
   559  			if !op.assign {
   560  				continue
   561  			}
   562  			if i+1 < len(ops) {
   563  				if ops[i+1].assign {
   564  					chk.ineff = append(chk.ineff, op.id)
   565  				}
   566  				continue
   567  			}
   568  			seen := map[*block]bool{}
   569  			for _, b := range b.children {
   570  				if used(obj, b, seen) {
   571  					continue ops
   572  				}
   573  			}
   574  			chk.ineff = append(chk.ineff, op.id)
   575  		}
   576  	}
   577  
   578  	for _, b := range b.children {
   579  		chk.check(b)
   580  	}
   581  }
   582  
   583  func used(obj *ast.Object, b *block, seen map[*block]bool) bool {
   584  	if seen[b] {
   585  		return false
   586  	}
   587  	seen[b] = true
   588  
   589  	if ops := b.ops[obj]; len(ops) > 0 {
   590  		return !ops[0].assign
   591  	}
   592  	for _, b := range b.children {
   593  		if used(obj, b, seen) {
   594  			return true
   595  		}
   596  	}
   597  	return false
   598  }
   599  
   600  type idents []*ast.Ident
   601  
   602  func (ids idents) Len() int           { return len(ids) }
   603  func (ids idents) Less(i, j int) bool { return ids[i].Pos() < ids[j].Pos() }
   604  func (ids idents) Swap(i, j int)      { ids[i], ids[j] = ids[j], ids[i] }