diff --git a/.gitignore b/.gitignore index 3a5347a17..1c088b819 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .*.swo .*.swp *.dot +*.out .idea/ /yaegi internal/cmd/extract/extract diff --git a/_test/assign19.go b/_test/assign19.go new file mode 100644 index 000000000..497ee5058 --- /dev/null +++ b/_test/assign19.go @@ -0,0 +1,9 @@ +package main + +func main() { + a, b, c := 1, 2 + _, _, _ = a, b, c +} + +// Error: +// _test/assign19.go:4:2: cannot assign 2 values to 3 variables diff --git a/_test/closure10.go b/_test/closure10.go index 667404574..c502b3cb8 100644 --- a/_test/closure10.go +++ b/_test/closure10.go @@ -13,6 +13,6 @@ func main() { } // Output: -// 3 0 0 -// 3 1 1 -// 3 2 2 +// 0 0 0 +// 1 1 1 +// 2 2 2 diff --git a/_test/closure11.go b/_test/closure11.go index d619504c6..bdc86555a 100644 --- a/_test/closure11.go +++ b/_test/closure11.go @@ -17,6 +17,6 @@ func main() { } // Output: -// 3 0 -// 3 1 -// 3 2 +// 0 0 +// 1 1 +// 2 2 diff --git a/_test/closure12.go b/_test/closure12.go index a38d884ac..9f1a3f842 100644 --- a/_test/closure12.go +++ b/_test/closure12.go @@ -20,6 +20,6 @@ func main() { } // Output: -// 3 0 i=0 -// 3 1 i=1 -// 3 2 i=2 +// 0 0 i=0 +// 1 1 i=1 +// 2 2 i=2 diff --git a/_test/closure15.go b/_test/closure15.go new file mode 100644 index 000000000..033d4f395 --- /dev/null +++ b/_test/closure15.go @@ -0,0 +1,18 @@ +package main + +func main() { + foos := []func(){} + + for i := range 3 { + a := i + foos = append(foos, func() { println(i, a) }) + } + foos[0]() + foos[1]() + foos[2]() +} + +// Output: +// 0 0 +// 1 1 +// 2 2 diff --git a/_test/closure16.go b/_test/closure16.go new file mode 100644 index 000000000..a535f9a42 --- /dev/null +++ b/_test/closure16.go @@ -0,0 +1,18 @@ +package main + +func main() { + foos := []func(){} + + for i := range 3 { + a, b := i, i + foos = append(foos, func() { println(i, a, b) }) + } + foos[0]() + foos[1]() + foos[2]() +} + +// Output: +// 0 0 0 +// 1 1 1 +// 2 2 2 diff --git a/_test/closure17.go b/_test/closure17.go new file mode 100644 index 000000000..d5b80d379 --- /dev/null +++ b/_test/closure17.go @@ -0,0 +1,22 @@ +package main + +type T struct { + F func() +} + +func main() { + foos := []T{} + + for i := range 3 { + a := i + foos = append(foos, T{func() { println(i, a) }}) + } + foos[0].F() + foos[1].F() + foos[2].F() +} + +// Output: +// 0 0 +// 1 1 +// 2 2 diff --git a/_test/closure18.go b/_test/closure18.go new file mode 100644 index 000000000..f5dc43d72 --- /dev/null +++ b/_test/closure18.go @@ -0,0 +1,25 @@ +package main + +import "fmt" + +type T struct { + F func() +} + +func main() { + foos := []T{} + + for i := range 3 { + a := i + n := fmt.Sprintf("i=%d", i) + foos = append(foos, T{func() { println(i, a, n) }}) + } + foos[0].F() + foos[1].F() + foos[2].F() +} + +// Output: +// 0 0 i=0 +// 1 1 i=1 +// 2 2 i=2 diff --git a/_test/closure19.go b/_test/closure19.go new file mode 100644 index 000000000..7ad87fb54 --- /dev/null +++ b/_test/closure19.go @@ -0,0 +1,18 @@ +package main + +func main() { + foos := []func(){} + + for i := 0; i < 3; i++ { + i := i + foos = append(foos, func() { println(i) }) + } + foos[0]() + foos[1]() + foos[2]() +} + +// Output: +// 0 +// 1 +// 2 diff --git a/_test/closure20.go b/_test/closure20.go new file mode 100644 index 000000000..7afb67084 --- /dev/null +++ b/_test/closure20.go @@ -0,0 +1,18 @@ +package main + +func main() { + foos := []func(){} + + for i := range 3 { + i := i + foos = append(foos, func() { println(i) }) + } + foos[0]() + foos[1]() + foos[2]() +} + +// Output: +// 0 +// 1 +// 2 diff --git a/_test/closure9.go b/_test/closure9.go index 24e937b10..d1dab2218 100644 --- a/_test/closure9.go +++ b/_test/closure9.go @@ -13,6 +13,6 @@ func main() { } // Output: -// 3 0 -// 3 1 -// 3 2 +// 0 0 +// 1 1 +// 2 2 diff --git a/_test/for17.go b/_test/for17.go new file mode 100644 index 000000000..59d769589 --- /dev/null +++ b/_test/for17.go @@ -0,0 +1,13 @@ +package main + +func main() { + mx := 3 + for i := range mx { + println(i) + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/_test/for18.go b/_test/for18.go new file mode 100644 index 000000000..d70f55697 --- /dev/null +++ b/_test/for18.go @@ -0,0 +1,12 @@ +package main + +func main() { + for i := range 3 { + println(i) + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/_test/for19.go b/_test/for19.go new file mode 100644 index 000000000..3d98c9cb0 --- /dev/null +++ b/_test/for19.go @@ -0,0 +1,12 @@ +package main + +func main() { + for range 3 { + println("i") + } +} + +// Output: +// i +// i +// i diff --git a/_test/issue-1618.go b/_test/issue-1618.go new file mode 100644 index 000000000..1db8bbea7 --- /dev/null +++ b/_test/issue-1618.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "runtime" + "sync" +) + +func humanizeBytes(bytes uint64) string { + const ( + _ = iota + kB uint64 = 1 << (10 * iota) + mB + gB + tB + pB + ) + + switch { + case bytes < kB: + return fmt.Sprintf("%dB", bytes) + case bytes < mB: + return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kB)) + case bytes < gB: + return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mB)) + case bytes < tB: + return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gB)) + case bytes < pB: + return fmt.Sprintf("%.2fTB", float64(bytes)/float64(tB)) + default: + return fmt.Sprintf("%dB", bytes) + } +} + +func main() { + i := 0 + wg := sync.WaitGroup{} + + for { + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("#%d: alloc = %s, routines = %d, gc = %d\n", i, humanizeBytes(m.Alloc), runtime.NumGoroutine(), m.NumGC) + + wg.Add(1) + go func() { + wg.Done() + }() + wg.Wait() + i = i + 1 + } +} diff --git a/_test/issue-1640.go b/_test/issue-1640.go new file mode 100644 index 000000000..426dd8647 --- /dev/null +++ b/_test/issue-1640.go @@ -0,0 +1,23 @@ +package main + +import ( + "errors" +) + +func ShortVariableDeclarations() (i int, err error) { + r, err := 1, errors.New("test") + i = r + return +} + +func main() { + _, er := ShortVariableDeclarations() + if er != nil { + println("ShortVariableDeclarations ok") + } else { + println("ShortVariableDeclarations not ok") + } +} + +// Output: +// ShortVariableDeclarations ok diff --git a/_test/issue-1653.go b/_test/issue-1653.go new file mode 100644 index 000000000..8986d1f68 --- /dev/null +++ b/_test/issue-1653.go @@ -0,0 +1,12 @@ +package main + +func f(b uint) uint { + return uint(1) + (0x1 >> b) +} + +func main() { + println(f(1)) +} + +// Output: +// 1 diff --git a/_test/map31.go b/_test/map31.go new file mode 100644 index 000000000..c649b8c3e --- /dev/null +++ b/_test/map31.go @@ -0,0 +1,13 @@ +package main + +func main() { + myMap := map[string]int{"a":2} + + for s, _ := range myMap { + _ = s + } + println("ok") +} + +// Output: +// ok diff --git a/_test/op10.go b/_test/op10.go new file mode 100644 index 000000000..5db580944 --- /dev/null +++ b/_test/op10.go @@ -0,0 +1,9 @@ +package main + +func main() { + _ = 1 + 1 + println("ok") +} + +// Output: +// ok diff --git a/_test/op11.go b/_test/op11.go new file mode 100644 index 000000000..ab2ecfa3a --- /dev/null +++ b/_test/op11.go @@ -0,0 +1,10 @@ +package main + +func main() { + a, b := 1, 2 + _ = a + b + println("ok") +} + +// Output: +// ok diff --git a/interp/ast.go b/interp/ast.go index 1e71ac439..a94354b5b 100644 --- a/interp/ast.go +++ b/interp/ast.go @@ -597,7 +597,22 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) { st.push(addChild(&root, anc, pos, kind, act), nod) case *ast.BlockStmt: - st.push(addChild(&root, anc, pos, blockStmt, aNop), nod) + b := addChild(&root, anc, pos, blockStmt, aNop) + st.push(b, nod) + var kind nkind + if anc.node != nil { + kind = anc.node.kind + } + switch kind { + case rangeStmt: + k := addChild(&root, astNode{b, nod}, pos, identExpr, aNop) + k.ident = "_" + v := addChild(&root, astNode{b, nod}, pos, identExpr, aNop) + v.ident = "_" + case forStmt7: + k := addChild(&root, astNode{b, nod}, pos, identExpr, aNop) + k.ident = "_" + } case *ast.BranchStmt: var kind nkind diff --git a/interp/cfg.go b/interp/cfg.go index 39133a4c8..e5eae1ff2 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -121,6 +121,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } case blockStmt: + var rangek, rangev *node if n.anc != nil && n.anc.kind == rangeStmt { // For range block: ensure that array or map type is propagated to iterators // prior to process block. We cannot perform this at RangeStmt pre-order because @@ -147,6 +148,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string var k, v, o *node if len(n.anc.child) == 4 { k, v, o = n.anc.child[0], n.anc.child[1], n.anc.child[2] + if v.ident == "_" { + v = nil // Do not assign to _ value. + } } else { k, o = n.anc.child[0], n.anc.child[1] } @@ -197,18 +201,24 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range ktyp = sc.getType("int") vtyp = o.typ.val + case intT: + n.anc.gen = rangeInt + sc.add(sc.getType("int")) + ktyp = sc.getType("int") } kindex := sc.add(ktyp) sc.sym[k.ident] = &symbol{index: kindex, kind: varSym, typ: ktyp} k.typ = ktyp k.findex = kindex + rangek = k if v != nil { vindex := sc.add(vtyp) sc.sym[v.ident] = &symbol{index: vindex, kind: varSym, typ: vtyp} v.typ = vtyp v.findex = vindex + rangev = v } } } @@ -216,6 +226,41 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string n.findex = -1 n.val = nil sc = sc.pushBloc() + + if n.anc != nil && n.anc.kind == rangeStmt { + lk := n.child[0] + if rangek != nil { + lk.ident = rangek.ident + lk.typ = rangek.typ + kindex := sc.add(lk.typ) + sc.sym[lk.ident] = &symbol{index: kindex, kind: varSym, typ: lk.typ} + lk.findex = kindex + lk.gen = loopVarKey + } + lv := n.child[1] + if rangev != nil { + lv.ident = rangev.ident + lv.typ = rangev.typ + vindex := sc.add(lv.typ) + sc.sym[lv.ident] = &symbol{index: vindex, kind: varSym, typ: lv.typ} + lv.findex = vindex + lv.gen = loopVarVal + } + } + if n.anc != nil && n.anc.kind == forStmt7 { + lv := n.child[0] + init := n.anc.child[0] + if init.kind == defineStmt && len(init.child) >= 2 && init.child[0].kind == identExpr { + fi := init.child[0] + lv.ident = fi.ident + lv.typ = fi.typ + vindex := sc.add(lv.typ) + sc.sym[lv.ident] = &symbol{index: vindex, kind: varSym, typ: lv.typ} + lv.findex = vindex + lv.gen = loopVarFor + } + } + // Pre-define symbols for labels defined in this block, so we are sure that // they are already defined when met. // TODO(marc): labels must be stored outside of symbols to avoid collisions. @@ -644,6 +689,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string sbase = len(n.child) - n.nright } + // If len(RHS) > 1, each node must be single-valued, and the nth expression + // on the right is assigned to the nth operand on the left, so the number of + // nodes on the left and right sides must be equal + if n.nright > 1 && n.nright != n.nleft { + err = n.cfgErrorf("cannot assign %d values to %d variables", n.nright, n.nleft) + return + } + wireChild(n) for i := 0; i < n.nleft; i++ { dest, src := n.child[i], n.child[sbase+i] @@ -651,7 +704,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string var sym *symbol var level int - if dest.rval.IsValid() && isConstType(dest.typ) { + if dest.rval.IsValid() && !dest.rval.CanSet() && isConstType(dest.typ) { err = n.cfgErrorf("cannot assign to %s (%s constant)", dest.rval, dest.typ.str) break } @@ -678,7 +731,23 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string if dest.typ.incomplete { return } - if sc.global { + if sc.global || sc.isRedeclared(dest) { + if n.anc != nil && n.anc.anc != nil && (n.anc.anc.kind == forStmt7 || n.anc.anc.kind == rangeStmt) { + // check for redefine of for loop variables, which are now auto-defined in go1.22 + init := n.anc.anc.child[0] + var fi *node // for ident + if n.anc.anc.kind == forStmt7 { + if init.kind == defineStmt && len(init.child) >= 2 && init.child[0].kind == identExpr { + fi = init.child[0] + } + } else { // range + fi = init + } + if fi != nil && dest.ident == fi.ident { + n.gen = nop + break + } + } // Do not overload existing symbols (defined in GTA) in global scope. sym, _, _ = sc.lookup(dest.ident) } @@ -913,6 +982,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string // Allocate a new location in frame, and store the result here. n.findex = sc.add(n.typ) } + if n.typ != nil && !n.typ.untyped { + fixUntyped(n, sc) + } case indexExpr: if isBlank(n.child[0]) { @@ -1510,6 +1582,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string err = cond.cfgErrorf("non-bool used as for condition") } n.start = init.start + body.start = body.child[0] // loopvar if cond.rval.IsValid() { // Condition is known at compile time, bypass test. if cond.rval.Bool() { @@ -1742,12 +1815,13 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } else { k, o, body = n.child[0], n.child[1], n.child[2] } - n.start = o.start // Get array or map object - o.tnext = k.start // then go to iterator init - k.tnext = n // then go to range function - n.tnext = body.start // then go to range body - body.tnext = n // then body go to range function (loop) - k.gen = empty // init filled later by generator + n.start = o.start // Get array or map object + o.tnext = k.start // then go to iterator init + k.tnext = n // then go to range function + body.start = body.child[0] // loopvar + n.tnext = body.start // then go to range body + body.tnext = n // then body go to range function (loop) + k.gen = empty // init filled later by generator } case returnStmt: @@ -2229,6 +2303,20 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string return initNodes, err } +// fixUntyped propagates implicit type conversions for untyped binary expressions. +func fixUntyped(nod *node, sc *scope) { + nod.Walk(func(n *node) bool { + if n == nod || (n.kind != binaryExpr && n.kind != parenExpr) || !n.typ.untyped { + return true + } + n.typ = nod.typ + if n.findex >= 0 { + sc.types[n.findex] = nod.typ.frameType() + } + return true + }, nil) +} + func compDefineX(sc *scope, n *node) error { l := len(n.child) - 1 types := []*itype{} diff --git a/interp/debugger.go b/interp/debugger.go index 8b6858941..b6d8a9f00 100644 --- a/interp/debugger.go +++ b/interp/debugger.go @@ -272,10 +272,6 @@ func (dbg *Debugger) enterCall(nFunc, nCall *node, f *frame) { switch nFunc.kind { case funcLit: f.debug.kind = frameCall - if nFunc.frame != nil { - nFunc.frame.debug.kind = frameClosure - nFunc.frame.debug.node = nFunc - } case funcDecl: f.debug.kind = frameCall diff --git a/interp/interp.go b/interp/interp.go index bb3c24392..e5f66f6a3 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -33,7 +33,6 @@ type node struct { tnext *node // true branch successor (CFG) fnext *node // false branch successor (CFG) interp *Interpreter // interpreter context - frame *frame // frame pointer used for closures only (TODO: suppress this) index int64 // node index (dot display) findex int // index of value in frame or frame size (func def, type def) level int // number of frame indirections to access value @@ -138,7 +137,7 @@ func newFrame(anc *frame, length int, id uint64) *frame { func (f *frame) runid() uint64 { return atomic.LoadUint64(&f.id) } func (f *frame) setrunid(id uint64) { atomic.StoreUint64(&f.id, id) } -func (f *frame) clone(fork bool) *frame { +func (f *frame) clone() *frame { f.mutex.RLock() defer f.mutex.RUnlock() nf := &frame{ @@ -150,12 +149,8 @@ func (f *frame) clone(fork bool) *frame { done: f.done, debug: f.debug, } - if fork { - nf.data = make([]reflect.Value, len(f.data)) - copy(nf.data, f.data) - } else { - nf.data = f.data - } + nf.data = make([]reflect.Value, len(f.data)) + copy(nf.data, f.data) return nf } @@ -498,6 +493,14 @@ func (interp *Interpreter) resizeFrame() { // Eval evaluates Go code represented as a string. Eval returns the last result // computed by the interpreter, and a non nil error in case of failure. func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + var pc [64]uintptr + n := runtime.Callers(1, pc[:]) + err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} + } + }() + return interp.eval(src, "", true) } @@ -505,6 +508,14 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) { // by the interpreter, and a non nil error in case of failure. // The main function of the main package is executed if present. func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + var pc [64]uintptr + n := runtime.Callers(1, pc[:]) + err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} + } + }() + if !isFile(interp.opt.filesystem, path) { _, err := interp.importSrc(mainID, path, NoTest) return res, err @@ -581,14 +592,7 @@ func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (ref done := make(chan struct{}) go func() { - defer func() { - if r := recover(); r != nil { - var pc [64]uintptr - n := runtime.Callers(1, pc[:]) - err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} - } - close(done) - }() + defer close(done) v, err = interp.Eval(src) }() diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index b7d4817d5..697a7c5a3 100644 --- a/interp/interp_consistent_test.go +++ b/interp/interp_consistent_test.go @@ -15,6 +15,10 @@ import ( "github.com/traefik/yaegi/stdlib/unsafe" ) +var testsToSkipGo122 = map[string]bool{} + +var go122 = strings.HasPrefix(runtime.Version(), "go1.22") + func TestInterpConsistencyBuild(t *testing.T) { if testing.Short() { t.Skip("short mode") @@ -37,6 +41,7 @@ func TestInterpConsistencyBuild(t *testing.T) { file.Name() == "assign11.go" || // expect error file.Name() == "assign12.go" || // expect error file.Name() == "assign15.go" || // expect error + file.Name() == "assign19.go" || // expect error file.Name() == "bad0.go" || // expect error file.Name() == "break0.go" || // expect error file.Name() == "cont3.go" || // expect error @@ -72,6 +77,7 @@ func TestInterpConsistencyBuild(t *testing.T) { file.Name() == "time0.go" || // display time (similar to random number) file.Name() == "factor.go" || // bench file.Name() == "fib.go" || // bench + file.Name() == "issue-1618.go" || // bench (infinite running) file.Name() == "type5.go" || // used to illustrate a limitation with no workaround, related to the fact that the reflect package does not allow the creation of named types file.Name() == "type6.go" || // used to illustrate a limitation with no workaround, related to the fact that the reflect package does not allow the creation of named types diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index 59bd7dd2d..d8227ade7 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -1902,3 +1902,27 @@ func TestIssue1383(t *testing.T) { t.Fatal(err) } } + +func TestIssue1623(t *testing.T) { + var f float64 + var j int + var s string = "foo" + + i := interp.New(interp.Options{}) + if err := i.Use(interp.Exports{ + "pkg/pkg": map[string]reflect.Value{ + "F": reflect.ValueOf(&f).Elem(), + "J": reflect.ValueOf(&j).Elem(), + "S": reflect.ValueOf(&s).Elem(), + }, + }); err != nil { + t.Fatal(err) + } + i.ImportUsed() + + runTests(t, i, []testCase{ + {desc: "pkg.F = 2.0", src: "pkg.F = 2.0; pkg.F", res: "2"}, + {desc: "pkg.J = 3", src: "pkg.J = 3; pkg.J", res: "3"}, + {desc: `pkg.S = "bar"`, src: `pkg.S = "bar"; pkg.S`, res: "bar"}, + }) +} diff --git a/interp/interp_file_test.go b/interp/interp_file_test.go index b2bdde0a2..03533cf66 100644 --- a/interp/interp_file_test.go +++ b/interp/interp_file_test.go @@ -16,6 +16,15 @@ import ( "github.com/traefik/yaegi/stdlib/unsafe" ) +// The following tests sometimes (not always) crash with go1.21 but not with go1.20 or go1.22. +// The reason of failure is not obvious, maybe due to the runtime itself, and will be investigated separately. +// Also, the closure tests depend on an incompatible language change in go1.22, where `for` variables are now +// defined in body (thus reallocated at each loop). This is now the behavior in yaegi, so 1.21 produces +// different results. +var testsToSkipGo121 = map[string]bool{"cli6.go": true, "cli7.go": true, "issue-1276.go": true, "issue-1330.go": true, "struct11.go": true, "closure9.go": true, "closure10.go": true, "closure11.go": true, "closure12.go": true, "closure15.go": true, "closure16.go": true, "closure17.go": true, "closure18.go": true, "closure20.go": true, "for17.go": true, "for18.go": true, "for19.go": true} + +var go121 = strings.HasPrefix(runtime.Version(), "go1.21") + func TestFile(t *testing.T) { filePath := "../_test/str.go" runCheck(t, filePath) diff --git a/interp/interp_issue_1634_test.go b/interp/interp_issue_1634_test.go new file mode 100644 index 000000000..384a519db --- /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 b4225ed0e..e2c90fe6f 100644 --- a/interp/run.go +++ b/interp/run.go @@ -987,10 +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 { - if n.frame != nil { // Use closure context if defined. - f = n.frame + 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()) @@ -1292,14 +1302,13 @@ func call(n *node) { } n.exec = func(f *frame) bltn { - var def *node - var ok bool - + f.mutex.Lock() bf := value(f) - - if def, ok = bf.Interface().(*node); ok { + def, ok := bf.Interface().(*node) + if ok { bf = def.rval } + f.mutex.Unlock() // Call bin func if defined if bf.IsValid() { @@ -1343,12 +1352,7 @@ func call(n *node) { return tnext } - anc := f - // Get closure frame context (if any) - if def.frame != nil { - anc = def.frame - } - nf := newFrame(anc, len(def.types), anc.runid()) + nf := newFrame(f, len(def.types), f.runid()) var vararg reflect.Value // Init return values @@ -1409,6 +1413,13 @@ func call(n *node) { } runCfg(def.child[3].start, nf, def, n) + // Set return values + for i, v := range rvalues { + if v != nil { + v(f).Set(nf.data[i]) + } + } + // Handle branching according to boolean result if fnext != nil && !nf.data[0].Bool() { return fnext @@ -1887,27 +1898,22 @@ func getIndexMap2(n *node) { } } -const fork = true // Duplicate frame in frame.clone(). - // getFunc compiles a closure function generator for anonymous functions. func getFunc(n *node) { i := n.findex l := n.level next := getExec(n.tnext) + numRet := len(n.typ.ret) n.exec = func(f *frame) bltn { - fr := f.clone(fork) - nod := *n - nod.val = &nod - nod.frame = fr - def := &nod - numRet := len(def.typ.ret) + fr := f.clone() + o := getFrame(f, l).data[i] - fct := reflect.MakeFunc(nod.typ.TypeOf(), func(in []reflect.Value) []reflect.Value { + fct := reflect.MakeFunc(n.typ.TypeOf(), func(in []reflect.Value) []reflect.Value { // Allocate and init local frame. All values to be settable and addressable. - fr2 := newFrame(fr, len(def.types), fr.runid()) + fr2 := newFrame(fr, len(n.types), fr.runid()) d := fr2.data - for i, t := range def.types { + for i, t := range n.types { d[i] = reflect.New(t).Elem() } d = d[numRet:] @@ -1918,7 +1924,7 @@ func getFunc(n *node) { // In case of unused arg, there may be not even a frame entry allocated, just skip. break } - typ := def.typ.arg[i] + typ := n.typ.arg[i] switch { case isEmptyInterface(typ) || typ.TypeOf() == valueInterfaceType: d[i].Set(arg) @@ -1930,12 +1936,19 @@ func getFunc(n *node) { } // Interpreter code execution. - runCfg(def.child[3].start, fr2, def, n) + runCfg(n.child[3].start, fr2, n, n) + + f.mutex.Lock() + getFrame(f, l).data[i] = o + f.mutex.Unlock() return fr2.data[:numRet] }) + f.mutex.Lock() getFrame(f, l).data[i] = fct + f.mutex.Unlock() + return next } } @@ -1946,11 +1959,9 @@ func getMethod(n *node) { next := getExec(n.tnext) n.exec = func(f *frame) bltn { - fr := f.clone(!fork) nod := *(n.val.(*node)) nod.val = &nod nod.recv = n.recv - nod.frame = fr getFrame(f, l).data[i] = genFuncValue(&nod)(f) return next } @@ -2021,11 +2032,9 @@ func getMethodByName(n *node) { panic(n.cfgErrorf("method not found: %s", name)) } - fr := f.clone(!fork) nod := *m nod.val = &nod nod.recv = &receiver{nil, val.value, li} - nod.frame = fr getFrame(f, l).data[i] = genFuncValue(&nod)(f) return next } @@ -2884,6 +2893,71 @@ func _range(n *node) { } } +func rangeInt(n *node) { + ixn := n.child[0] + index0 := ixn.findex // array index location in frame + index2 := index0 - 1 // max + fnext := getExec(n.fnext) + tnext := getExec(n.tnext) + + var value func(*frame) reflect.Value + mxn := n.child[1] + value = genValue(mxn) + n.exec = func(f *frame) bltn { + rv := f.data[index0] + rv.SetInt(rv.Int() + 1) + if int(rv.Int()) >= int(f.data[index2].Int()) { + return fnext + } + return tnext + } + + // Init sequence + next := n.exec + index := index0 + ixn.exec = func(f *frame) bltn { + f.data[index2] = value(f) // set max + f.data[index].SetInt(-1) // assing index value + return next + } +} + +func loopVarKey(n *node) { + ixn := n.anc.anc.child[0] + next := getExec(n.tnext) + n.exec = func(f *frame) bltn { + rv := f.data[ixn.findex] + nv := reflect.New(rv.Type()).Elem() + nv.Set(rv) + f.data[n.findex] = nv + return next + } +} + +func loopVarVal(n *node) { + vln := n.anc.anc.child[1] + next := getExec(n.tnext) + n.exec = func(f *frame) bltn { + rv := f.data[vln.findex] + nv := reflect.New(rv.Type()).Elem() + nv.Set(rv) + f.data[n.findex] = nv + return next + } +} + +func loopVarFor(n *node) { + ixn := n.anc.anc.child[0].child[0] + next := getExec(n.tnext) + n.exec = func(f *frame) bltn { + fv := f.data[ixn.findex] + nv := reflect.New(fv.Type()).Elem() + nv.Set(fv) + f.data[n.findex] = nv + return next + } +} + func rangeChan(n *node) { i := n.child[0].findex // element index location in frame value := genValue(n.child[1]) // chan @@ -2912,11 +2986,10 @@ func rangeMap(n *node) { index2 := index0 - 1 // iterator for range, always just behind index0 fnext := getExec(n.fnext) tnext := getExec(n.tnext) + value := genValue(n.child[len(n.child)-2]) // map value - var value func(*frame) reflect.Value - if len(n.child) == 4 { - index1 := n.child[1].findex // map value location in frame - value = genValue(n.child[2]) // map + if len(n.child) == 4 && n.child[1].ident != "_" { + index1 := n.child[1].findex // map value location in frame n.exec = func(f *frame) bltn { iter := f.data[index2].Interface().(*reflect.MapIter) if !iter.Next() { @@ -2927,7 +3000,6 @@ func rangeMap(n *node) { return tnext } } else { - value = genValue(n.child[1]) // map n.exec = func(f *frame) bltn { iter := f.data[index2].Interface().(*reflect.MapIter) if !iter.Next() { diff --git a/interp/scope.go b/interp/scope.go index 52087aee9..b3a9e109b 100644 --- a/interp/scope.go +++ b/interp/scope.go @@ -145,6 +145,14 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) { return nil, 0, false } +func (s *scope) isRedeclared(n *node) bool { + if !isNewDefine(n, s) { + return false + } + // Existing symbol in the scope indicates a redeclaration. + return s.sym[n.ident] != nil +} + func (s *scope) rangeChanType(n *node) *itype { if sym, _, found := s.lookup(n.child[1].ident); found { if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) { diff --git a/interp/type.go b/interp/type.go index ddfbbea09..6de957ad1 100644 --- a/interp/type.go +++ b/interp/type.go @@ -2386,7 +2386,7 @@ func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map } func isPtr(t *itype) bool { return t.TypeOf().Kind() == reflect.Ptr } func isEmptyInterface(t *itype) bool { - return t.cat == interfaceT && len(t.field) == 0 + return t != nil && t.cat == interfaceT && len(t.field) == 0 } func isGeneric(t *itype) bool {