From da5f537f1092dd43c75f3f9a2a4d9517c326b61c Mon Sep 17 00:00:00 2001 From: Randall O'Reilly Date: Fri, 19 Jul 2024 10:32:04 -0700 Subject: [PATCH] fix for issue #1634 -- for passing a closure to exported Go function 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() .. --- interp/interp_issue_1634_test.go | 59 ++++++++++++++++++++++++++++++++ interp/run.go | 13 +++++++ 2 files changed, 72 insertions(+) create mode 100644 interp/interp_issue_1634_test.go diff --git a/interp/interp_issue_1634_test.go b/interp/interp_issue_1634_test.go new file mode 100644 index 00000000..384a519d --- /dev/null +++ b/interp/interp_issue_1634_test.go @@ -0,0 +1,59 @@ +package interp + +import ( + "bytes" + "io" + "os" + "reflect" + "testing" +) + +func TestExportClosureArg(t *testing.T) { + outExp := []byte("0\n1\n2\n") + // catch stdout + backupStdout := os.Stdout + defer func() { + os.Stdout = backupStdout + }() + r, w, _ := os.Pipe() + os.Stdout = w + + i := New(Options{}) + err := i.Use(Exports{ + "tmp/tmp": map[string]reflect.Value{ + "Func": reflect.ValueOf(func(s *[]func(), f func()) { *s = append(*s, f) }), + }, + }) + if err != nil { + t.Error(err) + } + i.ImportUsed() + + _, err = i.Eval(` +func main() { + fs := []func(){} + + for i := 0; i < 3; i++ { + i := i + tmp.Func(&fs, func() { println(i) }) + } + for _, f := range fs { + f() + } +} +`) + if err != nil { + t.Error(err) + } + // read stdout + if err = w.Close(); err != nil { + t.Fatal(err) + } + outInterp, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(outInterp, outExp) { + t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outExp)) + } +} diff --git a/interp/run.go b/interp/run.go index ba98be75..b8caa908 100644 --- a/interp/run.go +++ b/interp/run.go @@ -987,7 +987,20 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value { } funcType := n.typ.TypeOf() + value := genValue(n) + isDefer := false + if n.anc != nil && n.anc.anc != nil && n.anc.anc.kind == deferStmt { + isDefer = true + } + return func(f *frame) reflect.Value { + v := value(f) + if !isDefer && v.Kind() == reflect.Func { + // fixes #1634, if v is already a func, then don't re-wrap + // because original wrapping cloned the frame but this doesn't + return v + } + return reflect.MakeFunc(funcType, func(in []reflect.Value) []reflect.Value { // Allocate and init local frame. All values to be settable and addressable. fr := newFrame(f, len(def.types), f.runid())