cmd/gc: fix capturing by value for range statements
Kindly detected by race builders by failing TestRaceRange.
ORANGE typecheck does not increment decldepth around body.
Change-Id: I0df5f310cb3370a904c94d9647a9cf0f15729075
Reviewed-on: https://go-review.googlesource.com/3507
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/src/cmd/gc/range.c b/src/cmd/gc/range.c
index 5d6a562..ff05820 100644
--- a/src/cmd/gc/range.c
+++ b/src/cmd/gc/range.c
@@ -18,14 +18,25 @@
Node *v1, *v2;
NodeList *ll;
+ // Typechecking order is important here:
+ // 0. first typecheck range expression (slice/map/chan),
+ // it is evaluated only once and so logically it is not part of the loop.
+ // 1. typcheck produced values,
+ // this part can declare new vars and so it must be typechecked before body,
+ // because body can contain a closure that captures the vars.
+ // 2. decldepth++ to denote loop body.
+ // 3. typecheck body.
+ // 4. decldepth--.
+
+ typecheck(&n->right, Erv);
+ if((t = n->right->type) == T)
+ goto out;
+
// delicate little dance. see typecheckas2
for(ll=n->list; ll; ll=ll->next)
if(ll->n->defn != n)
typecheck(&ll->n, Erv | Easgn);
- typecheck(&n->right, Erv);
- if((t = n->right->type) == T)
- goto out;
if(isptr[t->etype] && isfixedarray(t->type))
t = t->type;
n->type = t;
@@ -106,7 +117,9 @@
if(ll->n->typecheck == 0)
typecheck(&ll->n, Erv | Easgn);
+ decldepth++;
typechecklist(n->nbody, Etop);
+ decldepth--;
}
void
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 0699ca1..635d2c4 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2828,7 +2828,8 @@
{
Node *r, *l;
- if(n->defn != stmt) {
+ // Variables declared in ORANGE are assigned on every iteration.
+ if(n->defn != stmt || stmt->op == ORANGE) {
r = outervalue(n);
for(l = n; l != r; l = l->left) {
l->assigned = 1;
diff --git a/test/closure2.go b/test/closure2.go
index 8947450..4d61b45 100644
--- a/test/closure2.go
+++ b/test/closure2.go
@@ -10,56 +10,109 @@
package main
func main() {
- type X struct {
- v int
- }
- var x X
- func() {
- x.v++
- }()
- if x.v != 1 {
- panic("x.v != 1")
- }
-
- type Y struct {
- X
- }
- var y Y
- func() {
- y.v = 1
- }()
- if y.v != 1 {
- panic("y.v != 1")
- }
-
- type Z struct {
- a [3]byte
- }
- var z Z
- func() {
- i := 0
- for z.a[1] = 1; i < 10; i++ {
+ {
+ type X struct {
+ v int
}
- }()
- if z.a[1] != 1 {
- panic("z.a[1] != 1")
- }
-
- w := 0
- tmp := 0
- f := func() {
- if w != 1 {
- panic("w != 1")
- }
- }
- func() {
- tmp = w // force capture of w, but do not write to it yet
- _ = tmp
+ var x X
func() {
+ x.v++
+ }()
+ if x.v != 1 {
+ panic("x.v != 1")
+ }
+
+ type Y struct {
+ X
+ }
+ var y Y
+ func() {
+ y.v = 1
+ }()
+ if y.v != 1 {
+ panic("y.v != 1")
+ }
+ }
+
+ {
+ type Z struct {
+ a [3]byte
+ }
+ var z Z
+ func() {
+ i := 0
+ for z.a[1] = 1; i < 10; i++ {
+ }
+ }()
+ if z.a[1] != 1 {
+ panic("z.a[1] != 1")
+ }
+ }
+
+ {
+ w := 0
+ tmp := 0
+ f := func() {
+ if w != 1 {
+ panic("w != 1")
+ }
+ }
+ func() {
+ tmp = w // force capture of w, but do not write to it yet
+ _ = tmp
func() {
- w++ // write in a nested closure
+ func() {
+ w++ // write in a nested closure
+ }()
}()
}()
- }()
- f()
+ f()
+ }
+
+ {
+ var g func() int
+ for i := range [2]int{} {
+ if i == 0 {
+ g = func() int {
+ return i // test that we capture by ref here, i is mutated on every interation
+ }
+ }
+ }
+ if g() != 1 {
+ panic("g() != 1")
+ }
+ }
+
+ {
+ var g func() int
+ q := 0
+ for range [2]int{} {
+ q++
+ g = func() int {
+ return q // test that we capture by ref here
+ // q++ must on a different decldepth than q declaration
+ }
+ }
+ if g() != 2 {
+ panic("g() != 2")
+ }
+ }
+
+ {
+ var g func() int
+ var a [2]int
+ q := 0
+ for a[func() int {
+ q++
+ return 0
+ }()] = range [2]int{} {
+ g = func() int {
+ return q // test that we capture by ref here
+ // q++ must on a different decldepth than q declaration
+ }
+ }
+ if g() != 2 {
+ panic("g() != 2")
+ }
+ }
}