forked from NYANDEV/forgejo
114 lines
2.2 KiB
Go
114 lines
2.2 KiB
Go
|
package rule
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
|
||
|
"github.com/mgechev/revive/lint"
|
||
|
)
|
||
|
|
||
|
// RangeValAddress lints
|
||
|
type RangeValAddress struct{}
|
||
|
|
||
|
// Apply applies the rule to given file.
|
||
|
func (r *RangeValAddress) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
||
|
var failures []lint.Failure
|
||
|
|
||
|
walker := rangeValAddress{
|
||
|
onFailure: func(failure lint.Failure) {
|
||
|
failures = append(failures, failure)
|
||
|
},
|
||
|
}
|
||
|
|
||
|
ast.Walk(walker, file.AST)
|
||
|
|
||
|
return failures
|
||
|
}
|
||
|
|
||
|
// Name returns the rule name.
|
||
|
func (r *RangeValAddress) Name() string {
|
||
|
return "range-val-address"
|
||
|
}
|
||
|
|
||
|
type rangeValAddress struct {
|
||
|
onFailure func(lint.Failure)
|
||
|
}
|
||
|
|
||
|
func (w rangeValAddress) Visit(node ast.Node) ast.Visitor {
|
||
|
n, ok := node.(*ast.RangeStmt)
|
||
|
if !ok {
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
value, ok := n.Value.(*ast.Ident)
|
||
|
if !ok {
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
ast.Walk(rangeBodyVisitor{
|
||
|
valueID: value.Obj,
|
||
|
onFailure: w.onFailure,
|
||
|
}, n.Body)
|
||
|
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
type rangeBodyVisitor struct {
|
||
|
valueID *ast.Object
|
||
|
onFailure func(lint.Failure)
|
||
|
}
|
||
|
|
||
|
func (bw rangeBodyVisitor) Visit(node ast.Node) ast.Visitor {
|
||
|
asgmt, ok := node.(*ast.AssignStmt)
|
||
|
if !ok {
|
||
|
return bw
|
||
|
}
|
||
|
|
||
|
for _, exp := range asgmt.Lhs {
|
||
|
e, ok := exp.(*ast.IndexExpr)
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
if bw.isAccessingRangeValueAddress(e.Index) { // e.g. a[&value]...
|
||
|
bw.onFailure(bw.newFailure(e.Index))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, exp := range asgmt.Rhs {
|
||
|
switch e := exp.(type) {
|
||
|
case *ast.UnaryExpr: // e.g. ...&value
|
||
|
if bw.isAccessingRangeValueAddress(e) {
|
||
|
bw.onFailure(bw.newFailure(e))
|
||
|
}
|
||
|
case *ast.CallExpr:
|
||
|
if fun, ok := e.Fun.(*ast.Ident); ok && fun.Name == "append" { // e.g. ...append(arr, &value)
|
||
|
for _, v := range e.Args {
|
||
|
if bw.isAccessingRangeValueAddress(v) {
|
||
|
bw.onFailure(bw.newFailure(e))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bw
|
||
|
}
|
||
|
|
||
|
func (bw rangeBodyVisitor) isAccessingRangeValueAddress(exp ast.Expr) bool {
|
||
|
u, ok := exp.(*ast.UnaryExpr)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
v, ok := u.X.(*ast.Ident)
|
||
|
return ok && u.Op == token.AND && v.Obj == bw.valueID
|
||
|
}
|
||
|
|
||
|
func (bw rangeBodyVisitor) newFailure(node ast.Node) lint.Failure {
|
||
|
return lint.Failure{
|
||
|
Node: node,
|
||
|
Confidence: 1,
|
||
|
Failure: fmt.Sprintf("suspicious assignment of '%s'. range-loop variables always have the same address", bw.valueID.Name),
|
||
|
}
|
||
|
}
|