Skip to content

Commit 20c8f5e

Browse files
authored
interp: correctly init variables assigned from function call
In the case of a Go short definition (i.e. `a, b := f()`), the new defined variables must be (re-)created in order to preserve the previous value (if in a loop) which can be still in use in the context of a closure. This must not apply to redeclared variables which simply see their value reassigned. The problem was both occuring in callBin() for runtime functions and assignFromCall() for functions created in the interpreter. Fixes #1497.
1 parent ce2bb79 commit 20c8f5e

File tree

5 files changed

+122
-32
lines changed

5 files changed

+122
-32
lines changed

_test/closure13.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
)
7+
8+
type monkey struct {
9+
test func() int
10+
}
11+
12+
func main() {
13+
input := []string{"1", "2", "3"}
14+
15+
var monkeys []*monkey
16+
17+
for _, v := range input {
18+
kong := monkey{}
19+
divisor, err := strconv.Atoi(v)
20+
if err != nil {
21+
panic(err)
22+
}
23+
fmt.Print(divisor, " ")
24+
kong.test = func() int {
25+
return divisor
26+
}
27+
monkeys = append(monkeys, &kong)
28+
}
29+
30+
for _, mk := range monkeys {
31+
fmt.Print(mk.test(), " ")
32+
}
33+
}
34+
35+
// Output:
36+
// 1 2 3 1 2 3

_test/closure14.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import "fmt"
4+
5+
type monkey struct {
6+
test func() int
7+
}
8+
9+
func getk(k int) (int, error) { return k, nil }
10+
11+
func main() {
12+
input := []string{"1", "2", "3"}
13+
14+
var monkeys []*monkey
15+
16+
for k := range input {
17+
kong := monkey{}
18+
divisor, _ := getk(k)
19+
fmt.Print(divisor, " ")
20+
kong.test = func() int {
21+
return divisor
22+
}
23+
monkeys = append(monkeys, &kong)
24+
}
25+
26+
for _, mk := range monkeys {
27+
fmt.Print(mk.test(), " ")
28+
}
29+
}
30+
31+
// Output:
32+
// 0 1 2 0 1 2

interp/cfg.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
672672
return
673673
}
674674
if sc.global {
675-
// Do not overload existing symbols (defined in GTA) in global scope
675+
// Do not overload existing symbols (defined in GTA) in global scope.
676676
sym, _, _ = sc.lookup(dest.ident)
677677
}
678678
if sym == nil {
@@ -2323,6 +2323,7 @@ func compDefineX(sc *scope, n *node) error {
23232323
canRedeclare := hasNewSymbol && len(symIsNew) > 1 && !symIsNew[id] && ok
23242324
if canRedeclare && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() {
23252325
index = sym.index
2326+
n.child[i].redeclared = true
23262327
} else {
23272328
index = sc.add(t)
23282329
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}

interp/interp.go

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,35 @@ import (
2525

2626
// Interpreter node structure for AST and CFG.
2727
type node struct {
28-
debug *nodeDebugData // debug info
29-
child []*node // child subtrees (AST)
30-
anc *node // ancestor (AST)
31-
param []*itype // generic parameter nodes (AST)
32-
start *node // entry point in subtree (CFG)
33-
tnext *node // true branch successor (CFG)
34-
fnext *node // false branch successor (CFG)
35-
interp *Interpreter // interpreter context
36-
frame *frame // frame pointer used for closures only (TODO: suppress this)
37-
index int64 // node index (dot display)
38-
findex int // index of value in frame or frame size (func def, type def)
39-
level int // number of frame indirections to access value
40-
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
41-
nright int // number of children in right part (assign)
42-
kind nkind // kind of node
43-
pos token.Pos // position in source code, relative to fset
44-
sym *symbol // associated symbol
45-
typ *itype // type of value in frame, or nil
46-
recv *receiver // method receiver node for call, or nil
47-
types []reflect.Type // frame types, used by function literals only
48-
scope *scope // frame scope
49-
action action // action
50-
exec bltn // generated function to execute
51-
gen bltnGenerator // generator function to produce above bltn
52-
val interface{} // static generic value (CFG execution)
53-
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
54-
ident string // set if node is a var or func
55-
meta interface{} // meta stores meta information between gta runs, like errors
28+
debug *nodeDebugData // debug info
29+
child []*node // child subtrees (AST)
30+
anc *node // ancestor (AST)
31+
param []*itype // generic parameter nodes (AST)
32+
start *node // entry point in subtree (CFG)
33+
tnext *node // true branch successor (CFG)
34+
fnext *node // false branch successor (CFG)
35+
interp *Interpreter // interpreter context
36+
frame *frame // frame pointer used for closures only (TODO: suppress this)
37+
index int64 // node index (dot display)
38+
findex int // index of value in frame or frame size (func def, type def)
39+
level int // number of frame indirections to access value
40+
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
41+
nright int // number of children in right part (assign)
42+
kind nkind // kind of node
43+
pos token.Pos // position in source code, relative to fset
44+
sym *symbol // associated symbol
45+
typ *itype // type of value in frame, or nil
46+
recv *receiver // method receiver node for call, or nil
47+
types []reflect.Type // frame types, used by function literals only
48+
scope *scope // frame scope
49+
action action // action
50+
exec bltn // generated function to execute
51+
gen bltnGenerator // generator function to produce above bltn
52+
val interface{} // static generic value (CFG execution)
53+
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
54+
ident string // set if node is a var or func
55+
redeclared bool // set if node is a redeclared variable (CFG)
56+
meta interface{} // meta stores meta information between gta runs, like errors
5657
}
5758

5859
func (n *node) shouldBreak() bool {
@@ -105,7 +106,7 @@ type receiver struct {
105106
type frame struct {
106107
// id is an atomic counter used for cancellation, only accessed
107108
// via newFrame/runid/setrunid/clone.
108-
// Located at start of struct to ensure proper aligment.
109+
// Located at start of struct to ensure proper alignment.
109110
id uint64
110111

111112
debug *frameDebugData

interp/run.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,15 @@ func assignFromCall(n *node) {
636636
continue
637637
}
638638
s := f.data[ncall.findex+i]
639+
c := n.child[i]
640+
if n.kind == defineXStmt && !c.redeclared {
641+
// Recreate destination value in case of define statement,
642+
// to preserve previous value possibly in use by a closure.
643+
data := getFrame(f, c.level).data
644+
data[c.findex] = reflect.New(data[c.findex].Type()).Elem()
645+
data[c.findex].Set(s)
646+
continue
647+
}
639648
v(f).Set(s)
640649
}
641650
return next
@@ -1587,9 +1596,20 @@ func callBin(n *node) {
15871596
}
15881597
out := callFn(value(f), in)
15891598
for i, v := range rvalues {
1590-
if v != nil {
1591-
v(f).Set(out[i])
1599+
if v == nil {
1600+
continue // Skip assign "_".
15921601
}
1602+
c := n.anc.child[i]
1603+
if n.anc.kind == defineXStmt && !c.redeclared {
1604+
// In case of a define statement, the destination value in the frame
1605+
// must be recreated. This is necessary to preserve the previous value
1606+
// which may be still used in a separate closure.
1607+
data := getFrame(f, c.level).data
1608+
data[c.findex] = reflect.New(data[c.findex].Type()).Elem()
1609+
data[c.findex].Set(out[i])
1610+
continue
1611+
}
1612+
v(f).Set(out[i])
15931613
}
15941614
return tnext
15951615
}

0 commit comments

Comments
 (0)