github.com/prysmaticlabs/prysm@v1.4.4/tools/analyzers/ineffassign/ineffassign.go (about)

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