Skip to content

Commit cc3ad5f

Browse files
authored
fix #1066 by ignoring what seems legit modification of value receivers
1 parent ce69652 commit cc3ad5f

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

rule/modifies_value_receiver.go

+54-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package rule
22

33
import (
44
"go/ast"
5+
"go/token"
56
"strings"
67

78
"github.com/mgechev/revive/lint"
@@ -60,14 +61,14 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
6061
return nil // skip, anonymous receiver
6162
}
6263

63-
fselect := func(n ast.Node) bool {
64+
receiverAssignmentFinder := func(n ast.Node) bool {
6465
// look for assignments with the receiver in the right hand
65-
asgmt, ok := n.(*ast.AssignStmt)
66+
assignment, ok := n.(*ast.AssignStmt)
6667
if !ok {
6768
return false
6869
}
6970

70-
for _, exp := range asgmt.Lhs {
71+
for _, exp := range assignment.Lhs {
7172
switch e := exp.(type) {
7273
case *ast.IndexExpr: // receiver...[] = ...
7374
continue
@@ -92,7 +93,15 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
9293
return false
9394
}
9495

95-
assignmentsToReceiver := pick(n.Body, fselect)
96+
assignmentsToReceiver := pick(n.Body, receiverAssignmentFinder)
97+
if len(assignmentsToReceiver) == 0 {
98+
return nil // receiver is not modified
99+
}
100+
101+
methodReturnsReceiver := len(w.findReturnReceiverStatements(receiverName, n.Body)) > 0
102+
if methodReturnsReceiver {
103+
return nil // modification seems legit (see issue #1066)
104+
}
96105

97106
for _, assignment := range assignmentsToReceiver {
98107
w.onFailure(lint.Failure{
@@ -127,3 +136,44 @@ func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
127136

128137
return ident.Name
129138
}
139+
140+
func (w lintModifiesValRecRule) findReturnReceiverStatements(receiverName string, target ast.Node) []ast.Node {
141+
finder := func(n ast.Node) bool {
142+
// look for returns with the receiver as value
143+
returnStatement, ok := n.(*ast.ReturnStmt)
144+
if !ok {
145+
return false
146+
}
147+
148+
for _, exp := range returnStatement.Results {
149+
switch e := exp.(type) {
150+
case *ast.SelectorExpr: // receiver.field = ...
151+
name := w.getNameFromExpr(e.X)
152+
if name == "" || name != receiverName {
153+
continue
154+
}
155+
case *ast.Ident: // receiver := ...
156+
if e.Name != receiverName {
157+
continue
158+
}
159+
case *ast.UnaryExpr:
160+
if e.Op != token.AND {
161+
continue
162+
}
163+
name := w.getNameFromExpr(e.X)
164+
if name == "" || name != receiverName {
165+
continue
166+
}
167+
168+
default:
169+
continue
170+
}
171+
172+
return true
173+
}
174+
175+
return false
176+
}
177+
178+
return pick(target, finder)
179+
}

testdata/modifies_value_receiver.go

+16
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,19 @@ func (this data) vmethod() {
1212
this.items = make(map[string]bool) // MATCH /suspicious assignment to a by-value method receiver/
1313
this.items["vmethod"] = true
1414
}
15+
16+
func (a A) Foo() *A {
17+
a.whatever = true
18+
return &a
19+
}
20+
21+
func (a A) Clone() (*A, error) {
22+
a.whatever = true
23+
return &a, nil
24+
}
25+
26+
// WithBin will set the specific bin path to the builder.
27+
func (b JailerCommandBuilder) WithBin(bin string) JailerCommandBuilder {
28+
b.bin = bin
29+
return b
30+
}

0 commit comments

Comments
 (0)