-
Notifications
You must be signed in to change notification settings - Fork 349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interpreted closures mixed with "binary" functions don't capture values correctly #1634
Comments
for
loop values correctly
Pulling the closure that captures the loop variables out of the arglist of the "binary" function Change this: for i, e := range cp.Es {
i, e := i, e
ch = append(ch,
// breaks
R(i, e.E, func() string {
TLogf("inside R's f: %d, %v", i, e.E)
return e.E
}),
)
} To this: for i, e := range cp.Es {
i, e := i, e
// works
f := func() string {
TLogf("inside R's f: %d, %v", i, e.E)
return e.E
}
ch = append(ch, R(i, e.E, f))
} Also, I think that the fact that these are loop variables is a red herring, I think this is probably a problem with any closure being passed as a literal to a "binary" function. Unfortunately, the code I'm playing with is awash with such things. :) |
for
loop values correctly
This issue is very general -- we encountered it as well, and were also able to work around it by assigning a variable to the funLit function literal. This suggests that the general solution is to add an exec to a funcLit that makes a new reflect.Value copy of the func lit in the frame. Ideally it would detect the outer context of being passed as an arg to another function (doable), in a for loop (probably pretty hard in general). I just experimented with this and it doesn't seem to work: in run.go:
func funcLitCopy(n *node) {
next := getExec(n.tnext)
n.exec = func(f *frame) bltn {
ov := f.data[n.findex]
nv := reflect.New(ov.Type()).Elem()
nv.Set(ov)
f.data[n.findex] = nv
return next
}
}
in cfg.go, post-processing:
case funcLit:
n.types, n.scope = sc.types, sc
sc = sc.pop()
err = genRun(n)
n.gen = funcLitCopy unfortunately, in this test: func TestForRangeClosure(t *testing.T) {
i := New(Options{})
_, err := i.Eval(`
func main() {
fs := []func()
for i := range 3 {
println(i, &i)
fs = append(fs, func() { println(i, &i) })
}
for _, f := range fs {
f()
}
}
`)
if err != nil {
t.Error(err)
}
} it triggers a "reflect.Value.Call: call of nil function" -- presumably copying the reflect value doesn't do what one might have hoped it would? it would probably make more sense to figure out what the process of defining a new var for the funcLit is actually accomplishing, and then just replicate that in the exec. apparently making a reflect clone is not it.. |
finally found and fixed the issue! in run.go, see comments at the end: func genFunctionWrapper(n *node) func(*frame) reflect.Value {
var def *node
var ok bool
if def, ok = n.val.(*node); !ok {
return genValueAsFunctionWrapper(n)
}
start := def.child[3].start
numRet := len(def.typ.ret)
var rcvr func(*frame) reflect.Value
if n.recv != nil {
rcvr = genValueRecv(n)
}
funcType := n.typ.TypeOf()
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.Kind() == reflect.Func {
// per #1634, if v is already a func, then don't re-wrap! critically, the original wrapping
// clones the frame, whereas the one here (below) does _not_ clone the frame, so it doesn't
// generate the proper closure capture effects!
// this path is the same as genValueAsFunctionWrapper which is the path taken above if
// the value has an associated node, which happens when you do f := func() ..
return v
}
... the tests reveal that this does break a defer test, so it needs to be further qualified somehow, but at least the crux of the issue here is clear. |
…originally coded!
This fixes issue #1634 includes special case for defer function. I could remove or significantly reduce the comment description, and just have that here for future reference: // per #1634, if v is already a func, then don't re-wrap! critically, the original wrapping // clones the frame, whereas the one here (below) does _not_ clone the frame, so it doesn't // generate the proper closure capture effects! // this path is the same as genValueAsFunctionWrapper which is the path taken above if // the value has an associated node, which happens when you do f := func() ..
Fixed by #1646. Thanks, @rcoreilly! |
Updated Yaegi version fixes traefik/yaegi#1634, "Interpreted closures mixed with "binary" functions don't capture values correctly".
The following test case
TestIssue1634
ininterp/interp_eval_test.go
triggers an unexpected resultExpected result
All tests pass; interpreted output matches compiled output
Got
Yaegi Version
381e045
Additional Notes
No response
The text was updated successfully, but these errors were encountered: