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 }