github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/range-val-address.go (about)

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