Skip to content

Commit e8053df

Browse files
authored
Merge pull request #9 from MichaelUrman/contained
Test and fix cases with nested contexts
2 parents 6be4ab7 + 69e9ae1 commit e8053df

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

pkg/analyzer/analyzer.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"go/ast"
88
"go/printer"
99
"go/token"
10+
1011
"golang.org/x/tools/go/analysis"
1112
"golang.org/x/tools/go/analysis/passes/inspect"
1213
"golang.org/x/tools/go/ast/inspector"
@@ -54,6 +55,15 @@ func run(pass *analysis.Pass) (interface{}, error) {
5455
break
5556
}
5657

58+
// allow assignment to non-pointer children of values defined within the loop
59+
if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
60+
if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
61+
if obj.Pos() >= body.Pos() && obj.Pos() < body.End() {
62+
continue // definition is within the loop
63+
}
64+
}
65+
}
66+
5767
suggestedStmt := ast.AssignStmt{
5868
Lhs: assignStmt.Lhs,
5969
TokPos: assignStmt.TokPos,
@@ -103,6 +113,24 @@ func getBody(node ast.Node) (*ast.BlockStmt, error) {
103113
return nil, errUnknown
104114
}
105115

116+
func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident {
117+
for {
118+
switch n := node.(type) {
119+
case *ast.Ident:
120+
return n
121+
case *ast.IndexExpr:
122+
node = n.X
123+
case *ast.SelectorExpr:
124+
if sel, ok := pass.TypesInfo.Selections[n]; ok && sel.Indirect() {
125+
return nil // indirected (pointer) roots don't imply a (safe) copy
126+
}
127+
node = n.X
128+
default:
129+
return nil
130+
}
131+
}
132+
}
133+
106134
// render returns the pretty-print of the given node
107135
func render(fset *token.FileSet, x interface{}) (string, error) {
108136
var buf bytes.Buffer

testdata/src/example.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,44 @@ func example() {
3030
func wrapContext(ctx context.Context) context.Context {
3131
return context.WithoutCancel(ctx)
3232
}
33+
34+
// storing contexts in a struct isn't recommended, but local copies of a non-pointer struct should act like local copies of a context.
35+
func inStructs(ctx context.Context) {
36+
for i := 0; i < 10; i++ {
37+
c := struct{ Ctx context.Context }{ctx}
38+
c.Ctx = context.WithValue(c.Ctx, "key", i)
39+
c.Ctx = context.WithValue(c.Ctx, "other", "val")
40+
}
41+
42+
for i := 0; i < 10; i++ {
43+
c := []struct{ Ctx context.Context }{{ctx}}
44+
c[0].Ctx = context.WithValue(c[0].Ctx, "key", i)
45+
c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val")
46+
}
47+
48+
c := struct{ Ctx context.Context }{ctx}
49+
for i := 0; i < 10; i++ {
50+
c := c
51+
c.Ctx = context.WithValue(c.Ctx, "key", i)
52+
c.Ctx = context.WithValue(c.Ctx, "other", "val")
53+
}
54+
55+
pc := &struct{ Ctx context.Context }{ctx}
56+
for i := 0; i < 10; i++ {
57+
c := pc
58+
c.Ctx = context.WithValue(c.Ctx, "key", i) // want "nested context in loop"
59+
c.Ctx = context.WithValue(c.Ctx, "other", "val")
60+
}
61+
62+
r := []struct{ Ctx context.Context }{{ctx}}
63+
for i := 0; i < 10; i++ {
64+
r[0].Ctx = context.WithValue(r[0].Ctx, "key", i) // want "nested context in loop"
65+
r[0].Ctx = context.WithValue(r[0].Ctx, "other", "val")
66+
}
67+
68+
rp := []*struct{ Ctx context.Context }{{ctx}}
69+
for i := 0; i < 10; i++ {
70+
rp[0].Ctx = context.WithValue(rp[0].Ctx, "key", i) // want "nested context in loop"
71+
rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val")
72+
}
73+
}

0 commit comments

Comments
 (0)