1
0
mirror of https://github.com/mgechev/revive.git synced 2024-12-12 10:44:59 +02:00
revive/rule/range-val-address.go
dominiquelefevre 4242f24f4d
Add support for the new implementation of for loop variables in go 1.22. (#993)
* Add support for the new behaviour of for loops in go 1.22.

Go 1.22 has changed the behaviour of for loops. Every iteration
makes new loop variables. It is now safe to take their addresses
because they are guaranteed to be unique. Similarly, it is now
safe to capture loop variables in functions.

* adds documentation for public function

---------

Co-authored-by: chavacava <salvadorcavadini+github@gmail.com>
2024-06-02 11:55:26 +02:00

167 lines
3.3 KiB
Go

package rule
import (
"fmt"
"go/ast"
"go/token"
"strings"
"github.com/mgechev/revive/lint"
)
// RangeValAddress lints
type RangeValAddress struct{}
// Apply applies the rule to given file.
func (*RangeValAddress) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
if file.Pkg.IsAtLeastGo122() {
return failures
}
walker := rangeValAddress{
file: file,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
file.Pkg.TypeCheck()
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (*RangeValAddress) Name() string {
return "range-val-address"
}
type rangeValAddress struct {
file *lint.File
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
}
valueIsStarExpr := false
if t := w.file.Pkg.TypeOf(value); t != nil {
valueIsStarExpr = strings.HasPrefix(t.String(), "*")
}
ast.Walk(rangeBodyVisitor{
valueIsStarExpr: valueIsStarExpr,
valueID: value.Obj,
onFailure: w.onFailure,
}, n.Body)
return w
}
type rangeBodyVisitor struct {
valueIsStarExpr bool
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, ...&value.id
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 lit, ok := v.(*ast.CompositeLit); ok { // e.g. ...append(arr, v{id:&value})
bw.checkCompositeLit(lit)
continue
}
if bw.isAccessingRangeValueAddress(v) { // e.g. ...append(arr, &value)
bw.onFailure(bw.newFailure(v))
}
}
}
case *ast.CompositeLit: // e.g. ...v{id:&value}
bw.checkCompositeLit(e)
}
}
return bw
}
func (bw rangeBodyVisitor) checkCompositeLit(comp *ast.CompositeLit) {
for _, exp := range comp.Elts {
e, ok := exp.(*ast.KeyValueExpr)
if !ok {
continue
}
if bw.isAccessingRangeValueAddress(e.Value) {
bw.onFailure(bw.newFailure(e.Value))
}
}
}
func (bw rangeBodyVisitor) isAccessingRangeValueAddress(exp ast.Expr) bool {
u, ok := exp.(*ast.UnaryExpr)
if !ok {
return false
}
if u.Op != token.AND {
return false
}
v, ok := u.X.(*ast.Ident)
if !ok {
var s *ast.SelectorExpr
s, ok = u.X.(*ast.SelectorExpr)
if !ok {
return false
}
v, ok = s.X.(*ast.Ident)
if !ok {
return false
}
if bw.valueIsStarExpr { // check type of value
return false
}
}
return ok && 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),
}
}