1
0
mirror of https://github.com/mgechev/revive.git synced 2025-01-10 03:17:11 +02:00
revive/rule/datarace.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

145 lines
3.1 KiB
Go

package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/lint"
)
// DataRaceRule lints assignments to value method-receivers.
type DataRaceRule struct{}
// Apply applies the rule to given file.
func (*DataRaceRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintDataRaces{onFailure: onFailure, go122for: file.Pkg.IsAtLeastGo122()}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (*DataRaceRule) Name() string {
return "datarace"
}
type lintDataRaces struct {
onFailure func(failure lint.Failure)
go122for bool
}
func (w lintDataRaces) Visit(n ast.Node) ast.Visitor {
node, ok := n.(*ast.FuncDecl)
if !ok {
return w // not function declaration
}
if node.Body == nil {
return nil // empty body
}
results := node.Type.Results
returnIDs := map[*ast.Object]struct{}{}
if results != nil {
returnIDs = w.ExtractReturnIDs(results.List)
}
fl := &lintFunctionForDataRaces{onFailure: w.onFailure, returnIDs: returnIDs, rangeIDs: map[*ast.Object]struct{}{}, go122for: w.go122for}
ast.Walk(fl, node.Body)
return nil
}
func (lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} {
r := map[*ast.Object]struct{}{}
for _, f := range fields {
for _, id := range f.Names {
r[id.Obj] = struct{}{}
}
}
return r
}
type lintFunctionForDataRaces struct {
_ struct{}
onFailure func(failure lint.Failure)
returnIDs map[*ast.Object]struct{}
rangeIDs map[*ast.Object]struct{}
go122for bool
}
func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.RangeStmt:
if n.Body == nil {
return nil
}
getIds := func(exprs ...ast.Expr) []*ast.Ident {
r := []*ast.Ident{}
for _, expr := range exprs {
if id, ok := expr.(*ast.Ident); ok {
r = append(r, id)
}
}
return r
}
ids := getIds(n.Key, n.Value)
for _, id := range ids {
w.rangeIDs[id.Obj] = struct{}{}
}
ast.Walk(w, n.Body)
for _, id := range ids {
delete(w.rangeIDs, id.Obj)
}
return nil // do not visit the body of the range, it has been already visited
case *ast.GoStmt:
f := n.Call.Fun
funcLit, ok := f.(*ast.FuncLit)
if !ok {
return nil
}
selectIDs := func(n ast.Node) bool {
_, ok := n.(*ast.Ident)
return ok
}
ids := pick(funcLit.Body, selectIDs)
for _, id := range ids {
id := id.(*ast.Ident)
_, isRangeID := w.rangeIDs[id.Obj]
_, isReturnID := w.returnIDs[id.Obj]
switch {
case isRangeID && !w.go122for:
w.onFailure(lint.Failure{
Confidence: 1,
Node: id,
Category: "logic",
Failure: fmt.Sprintf("datarace: range value %s is captured (by-reference) in goroutine", id.Name),
})
case isReturnID:
w.onFailure(lint.Failure{
Confidence: 0.8,
Node: id,
Category: "logic",
Failure: fmt.Sprintf("potential datarace: return value %s is captured (by-reference) in goroutine", id.Name),
})
}
}
return nil
}
return w
}