Skip to content

Commit 3e9e29f

Browse files
committed
Test and fix cases with nested contexts
As long as the context is rooted in a non-pointer value that has a new copy in the loop, it is as safe to copy that value as it is to copy the context. So only report such cases when they are indirected and thus shared.
1 parent 6be4ab7 commit 3e9e29f

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

pkg/analyzer/analyzer.go

Lines changed: 27 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,14 @@ func run(pass *analysis.Pass) (interface{}, error) {
5455
break
5556
}
5657

58+
if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
59+
if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
60+
if obj.Pos() >= body.Pos() && obj.Pos() < body.End() {
61+
continue
62+
}
63+
}
64+
}
65+
5766
suggestedStmt := ast.AssignStmt{
5867
Lhs: assignStmt.Lhs,
5968
TokPos: assignStmt.TokPos,
@@ -103,6 +112,24 @@ func getBody(node ast.Node) (*ast.BlockStmt, error) {
103112
return nil, errUnknown
104113
}
105114

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

testdata/src/example.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,38 @@ 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+
for i := 0; i < 10; i++ {
63+
c := []*struct{ Ctx context.Context }{{ctx}}
64+
c[0].Ctx = context.WithValue(c[0].Ctx, "key", i) // want "nested context in loop"
65+
c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val")
66+
}
67+
}

0 commit comments

Comments
 (0)