Skip to content

Commit 659c5c4

Browse files
authored
Ignore when ranging over a slice of pointers (#6)
1 parent 8efdc91 commit 659c5c4

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed

exportloopref.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package exportloopref
33
import (
44
"go/ast"
55
"go/token"
6+
"go/types"
67

78
"golang.org/x/tools/go/analysis"
89
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -29,6 +30,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
2930
search := &Searcher{
3031
Stats: map[token.Pos]struct{}{},
3132
Vars: map[token.Pos]map[token.Pos]struct{}{},
33+
Types: pass.TypesInfo.Types,
3234
}
3335

3436
nodeFilter := []ast.Node{
@@ -51,10 +53,16 @@ func run(pass *analysis.Pass) (interface{}, error) {
5153
}
5254

5355
type Searcher struct {
54-
// statement variables
56+
// Statement variables : map to collect positions that
57+
// variables are declared like below.
58+
// - for <KEY>, <VALUE> := range ...
59+
// - var <X> int
60+
// - D := ...
5561
Stats map[token.Pos]struct{}
56-
// internal variables
57-
Vars map[token.Pos]map[token.Pos]struct{}
62+
// Internal variables maps loop-position, decl-location to ignore
63+
// safe pointers for variable which declared in the loop.
64+
Vars map[token.Pos]map[token.Pos]struct{}
65+
Types map[ast.Expr]types.TypeAndValue
5866
}
5967

6068
func (s *Searcher) Check(n ast.Node, stack []ast.Node) (*ast.Ident, bool) {
@@ -67,6 +75,7 @@ func (s *Searcher) Check(n ast.Node, stack []ast.Node) (*ast.Ident, bool) {
6775
s.parseDeclStmt(typed, stack)
6876
case *ast.AssignStmt:
6977
s.parseAssignStmt(typed, stack)
78+
7079
case *ast.UnaryExpr:
7180
return s.checkUnaryExpr(typed, stack)
7281
}
@@ -162,7 +171,7 @@ func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Iden
162171
}
163172

164173
// Get identity of the referred item
165-
id := getIdentity(n.X)
174+
id := s.getIdentity(n.X)
166175
if id == nil {
167176
return nil, true
168177
}
@@ -245,20 +254,27 @@ func (s *Searcher) isVar(loop ast.Node, expr ast.Expr) bool {
245254
}
246255

247256
// Get variable identity
248-
func getIdentity(expr ast.Expr) *ast.Ident {
257+
func (s *Searcher) getIdentity(expr ast.Expr) *ast.Ident {
249258
switch typed := expr.(type) {
250259
case *ast.SelectorExpr:
251260
// Get parent identity; i.e. `a` of the `a.b`.
252261
parent, ok := typed.X.(*ast.Ident)
253262
if !ok {
254263
return nil
255264
}
265+
256266
// parent is a package name identity
257267
if parent.Obj == nil {
258268
return nil
259269
}
270+
271+
// Ignore if the parent is pointer ref (fix for #2)
272+
if _, ok := s.Types[parent].Type.(*types.Pointer); ok {
273+
return nil
274+
}
275+
260276
// NOTE: If that is descendants member like `a.b.c`,
261-
// typed.X will be `*ast.SelectorExpr`.
277+
// typed.X will be `*ast.SelectorExpr` `a.b`.
262278
return parent
263279

264280
case *ast.Ident:

exportloopref_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,32 @@ import (
77
"golang.org/x/tools/go/analysis/analysistest"
88
)
99

10-
func Test(t *testing.T) {
10+
func TestSimple(t *testing.T) {
1111
testdata := analysistest.TestData()
1212
analysistest.Run(t, testdata, exportloopref.Analyzer, "simple")
13+
}
14+
15+
func TestStruct(t *testing.T) {
16+
testdata := analysistest.TestData()
1317
analysistest.Run(t, testdata, exportloopref.Analyzer, "struct")
18+
}
19+
20+
func TestComplex(t *testing.T) {
21+
testdata := analysistest.TestData()
1422
analysistest.Run(t, testdata, exportloopref.Analyzer, "complex")
23+
}
24+
25+
func TestFixed(t *testing.T) {
26+
testdata := analysistest.TestData()
1527
analysistest.Run(t, testdata, exportloopref.Analyzer, "fixed")
1628
}
29+
30+
func TestPslice(t *testing.T) {
31+
testdata := analysistest.TestData()
32+
analysistest.Run(t, testdata, exportloopref.Analyzer, "pslice")
33+
}
34+
35+
func TestIssue2(t *testing.T) {
36+
testdata := analysistest.TestData()
37+
analysistest.Run(t, testdata, exportloopref.Analyzer, "issue2")
38+
}

testdata/src/issue2/issue2.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
// Issue #2 (https://github.com/kyoh86/exportloopref/issues/2
4+
// This is valid code because n takes new pointer values, so &n.name points correctly to different names.
5+
6+
import (
7+
"fmt"
8+
)
9+
10+
type Node struct {
11+
name string
12+
}
13+
14+
type Nodes []*Node
15+
16+
type Graph struct {
17+
nodes Nodes
18+
}
19+
20+
func main() {
21+
var graph Graph
22+
var s *string
23+
24+
graph.nodes = Nodes{&Node{"hello"}, &Node{"world"}, &Node{"foo"}, &Node{"bar"}, &Node{"baz"}}
25+
26+
for i, n := range graph.nodes {
27+
if i == 2 {
28+
s = &n.name // here
29+
}
30+
}
31+
32+
fmt.Println(*s)
33+
}

testdata/src/pslice/pslice.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
func main() {
4+
var pslice []*int
5+
var ppslice []**int
6+
7+
println("loop expecting 10, 11, 12, 13")
8+
for _, p := range []int{10, 11, 12, 13} {
9+
p := p
10+
pslice = append(pslice, &p) // not a diagnostic (fixed p)
11+
}
12+
13+
for _, p := range pslice {
14+
ppslice = append(ppslice, &p) // want "exporting a pointer for the loop variable p"
15+
}
16+
17+
println(`ppslice expecting "10, 11, 12, 13" but "13, 13, 13, 13"`)
18+
for _, p := range ppslice {
19+
printp(*p)
20+
}
21+
}
22+
23+
func printp(p *int) {
24+
println(*p)
25+
}

0 commit comments

Comments
 (0)