github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/range-val-address.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  
     8  	"github.com/mgechev/revive/lint"
     9  )
    10  
    11  // RangeValAddress lints
    12  type RangeValAddress struct{}
    13  
    14  // Apply applies the rule to given file.
    15  func (r *RangeValAddress) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    16  	var failures []lint.Failure
    17  
    18  	walker := rangeValAddress{
    19  		onFailure: func(failure lint.Failure) {
    20  			failures = append(failures, failure)
    21  		},
    22  	}
    23  
    24  	ast.Walk(walker, file.AST)
    25  
    26  	return failures
    27  }
    28  
    29  // Name returns the rule name.
    30  func (r *RangeValAddress) Name() string {
    31  	return "range-val-address"
    32  }
    33  
    34  type rangeValAddress struct {
    35  	onFailure func(lint.Failure)
    36  }
    37  
    38  func (w rangeValAddress) Visit(node ast.Node) ast.Visitor {
    39  	n, ok := node.(*ast.RangeStmt)
    40  	if !ok {
    41  		return w
    42  	}
    43  
    44  	value, ok := n.Value.(*ast.Ident)
    45  	if !ok {
    46  		return w
    47  	}
    48  
    49  	ast.Walk(rangeBodyVisitor{
    50  		valueID:   value.Obj,
    51  		onFailure: w.onFailure,
    52  	}, n.Body)
    53  
    54  	return w
    55  }
    56  
    57  type rangeBodyVisitor struct {
    58  	valueID   *ast.Object
    59  	onFailure func(lint.Failure)
    60  }
    61  
    62  func (bw rangeBodyVisitor) Visit(node ast.Node) ast.Visitor {
    63  	asgmt, ok := node.(*ast.AssignStmt)
    64  	if !ok {
    65  		return bw
    66  	}
    67  
    68  	for _, exp := range asgmt.Lhs {
    69  		e, ok := exp.(*ast.IndexExpr)
    70  		if !ok {
    71  			continue
    72  		}
    73  		if bw.isAccessingRangeValueAddress(e.Index) { // e.g. a[&value]...
    74  			bw.onFailure(bw.newFailure(e.Index))
    75  		}
    76  	}
    77  
    78  	for _, exp := range asgmt.Rhs {
    79  		switch e := exp.(type) {
    80  		case *ast.UnaryExpr: // e.g. ...&value, ...&value.id
    81  			if bw.isAccessingRangeValueAddress(e) {
    82  				bw.onFailure(bw.newFailure(e))
    83  			}
    84  		case *ast.CallExpr:
    85  			if fun, ok := e.Fun.(*ast.Ident); ok && fun.Name == "append" { // e.g. ...append(arr, &value)
    86  				for _, v := range e.Args {
    87  					if bw.isAccessingRangeValueAddress(v) {
    88  						bw.onFailure(bw.newFailure(e))
    89  					}
    90  				}
    91  			}
    92  		}
    93  	}
    94  	return bw
    95  }
    96  
    97  func (bw rangeBodyVisitor) isAccessingRangeValueAddress(exp ast.Expr) bool {
    98  	u, ok := exp.(*ast.UnaryExpr)
    99  	if !ok {
   100  		return false
   101  	}
   102  
   103  	if u.Op != token.AND {
   104  		return false
   105  	}
   106  
   107  	v, ok := u.X.(*ast.Ident)
   108  	if !ok {
   109  		var s *ast.SelectorExpr
   110  		s, ok = u.X.(*ast.SelectorExpr)
   111  		if !ok {
   112  			return false
   113  		}
   114  		v, ok = s.X.(*ast.Ident)
   115  	}
   116  
   117  	return ok && v.Obj == bw.valueID
   118  }
   119  
   120  func (bw rangeBodyVisitor) newFailure(node ast.Node) lint.Failure {
   121  	return lint.Failure{
   122  		Node:       node,
   123  		Confidence: 1,
   124  		Failure:    fmt.Sprintf("suspicious assignment of '%s'. range-loop variables always have the same address", bw.valueID.Name),
   125  	}
   126  }