diff --git a/Makefile b/Makefile index 1e8509c65..8437b846a 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,6 @@ generate: gen_all_syscall tests: GO111MODULE=off go test -v ./... + GO111MODULE=off go test -race ./interp .PHONY: check gen_all_syscall gen_tests diff --git a/cmd/yaegi/yaegi_test.go b/cmd/yaegi/yaegi_test.go new file mode 100644 index 000000000..01063f104 --- /dev/null +++ b/cmd/yaegi/yaegi_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" + "time" +) + +func TestYaegiCmdCancel(t *testing.T) { + tmp, err := ioutil.TempDir("", "yaegi-") + if err != nil { + t.Fatalf("failed to create tmp directory: %v", err) + } + defer func() { + err = os.RemoveAll(tmp) + if err != nil { + t.Errorf("failed to clean up %v: %v", tmp, err) + } + }() + + yaegi := filepath.Join(tmp, "yaegi") + build := exec.Command("go", "build", "-race", "-o", yaegi, ".") + err = build.Run() + if err != nil { + t.Fatalf("failed to build yaegi command: %v", err) + } + + // Test src must be terminated by a single newline. + tests := []string{ + "for {}\n", + "select {}\n", + } + for _, src := range tests { + cmd := exec.Command(yaegi) + in, err := cmd.StdinPipe() + if err != nil { + t.Errorf("failed to get stdin pipe to yaegi command: %v", err) + } + var outBuf, errBuf bytes.Buffer + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + + // https://golang.org/doc/articles/race_detector.html#Options + cmd.Env = []string{`GORACE="halt_on_error=1"`} + + err = cmd.Start() + if err != nil { + t.Fatalf("failed to start yaegi command: %v", err) + } + + _, err = in.Write([]byte(src)) + if err != nil { + t.Errorf("failed pipe test source to yaegi command: %v", err) + } + time.Sleep(100 * time.Millisecond) + err = cmd.Process.Signal(os.Interrupt) + if err != nil { + t.Errorf("failed to send os.Interrupt to yaegi command: %v", err) + } + + _, err = in.Write([]byte("1+1\n")) + if err != nil { + t.Errorf("failed to probe race: %v", err) + } + err = in.Close() + if err != nil { + t.Errorf("failed to close stdin pipe: %v", err) + } + + err = cmd.Wait() + if err != nil { + if cmd.ProcessState.ExitCode() == 66 { // See race_detector.html article. + t.Errorf("race detected running yaegi command canceling %q: %v", src, err) + if testing.Verbose() { + t.Log(&errBuf) + } + } else { + t.Errorf("error running yaegi command for %q: %v", src, err) + } + continue + } + + if outBuf.String() != "context canceled\n2\n" { + t.Errorf("unexpected output: %q", &outBuf) + } + } +} diff --git a/interp/ast.go b/interp/ast.go index dd070607e..b6c42f93e 100644 --- a/interp/ast.go +++ b/interp/ast.go @@ -8,6 +8,7 @@ import ( "go/token" "reflect" "strconv" + "sync/atomic" ) // nkind defines the kind of AST, i.e. the grammar category @@ -356,9 +357,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) { var pkgName string addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node { - interp.nindex++ var i interface{} - n := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]} + nindex := atomic.AddInt64(&interp.nindex, 1) + n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]} n.start = n if anc.node == nil { *root = n @@ -369,15 +370,15 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) { if len(ancAst.List)+len(ancAst.Body) == len(anc.node.child) { // All case clause children are collected. // Split children in condition and body nodes to desambiguify the AST. - interp.nindex++ - body := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop} + nindex = atomic.AddInt64(&interp.nindex, 1) + body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop} if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign { // In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol // in each clause body, so a different guard type can be set in each clause name := ts.child[1].child[0].ident - interp.nindex++ - gn := &node{anc: body, interp: interp, ident: name, index: interp.nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop} + nindex = atomic.AddInt64(&interp.nindex, 1) + gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop} body.child = append(body.child, gn) } @@ -553,6 +554,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) { case *ast.CommClause: st.push(addChild(&root, anc, pos, commClause, aNop), nod) + case *ast.CommentGroup: + return false + case *ast.CompositeLit: st.push(addChild(&root, anc, pos, compositeLitExpr, aCompositeLit), nod) @@ -844,9 +848,9 @@ func (s *nodestack) top() astNode { // dup returns a duplicated node subtree func (interp *Interpreter) dup(nod, anc *node) *node { - interp.nindex++ + nindex := atomic.AddInt64(&interp.nindex, 1) n := *nod - n.index = interp.nindex + n.index = nindex n.anc = anc n.start = &n n.pos = anc.pos diff --git a/interp/interp.go b/interp/interp.go index 1dec8ab51..bb35a106a 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -2,14 +2,18 @@ package interp import ( "bufio" + "context" "fmt" "go/build" "go/scanner" "go/token" "io" "os" + "os/signal" "reflect" "strconv" + "sync" + "sync/atomic" ) // Interpreter node structure for AST and CFG @@ -21,7 +25,7 @@ type node struct { fnext *node // false branch successor (CFG) interp *Interpreter // interpreter context frame *frame // frame pointer used for closures only (TODO: suppress this) - index int // node index (dot display) + 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 nleft int // number of children in left part (assign) @@ -49,10 +53,42 @@ type receiver struct { // frame contains values for the current execution level (a function context) type frame struct { - anc *frame // ancestor frame (global space) - data []reflect.Value // values - deferred [][]reflect.Value // defer stack - recovered interface{} // to handle panic recover + anc *frame // ancestor frame (global space) + data []reflect.Value // values + + id uint64 // for cancellation, only access via newFrame/runid/setrunid/clone. + + mutex sync.RWMutex + deferred [][]reflect.Value // defer stack + recovered interface{} // to handle panic recover + done reflect.SelectCase // for cancellation of channel operations +} + +func newFrame(anc *frame, len int, id uint64) *frame { + f := &frame{ + anc: anc, + data: make([]reflect.Value, len), + id: id, + } + if anc != nil { + f.done = anc.done + } + return f +} + +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() *frame { + f.mutex.RLock() + defer f.mutex.RUnlock() + return &frame{ + anc: f.anc, + data: f.data, + deferred: f.deferred, + recovered: f.recovered, + id: f.runid(), + done: f.done, + } } // Exports stores the map of binary packages per package path @@ -63,24 +99,32 @@ type imports map[string]map[string]*symbol // opt stores interpreter options type opt struct { - astDot bool // display AST graph (debug) - cfgDot bool // display CFG graph (debug) - noRun bool // compile, but do not run - context build.Context // build context: GOPATH, build constraints + astDot bool // display AST graph (debug) + cfgDot bool // display CFG graph (debug) + noRun bool // compile, but do not run + fastChan bool // disable cancellable chan operations + context build.Context // build context: GOPATH, build constraints } // Interpreter contains global resources and state type Interpreter struct { Name string // program name - opt + + opt // user settable options + cancelChan bool // enables cancellable chan operations + nindex int64 // next node index + fset *token.FileSet // fileset to locate node in source code + binPkg Exports // binary packages used in interpreter, indexed by path + rdir map[string]bool // for src import cycle detection + + id uint64 // for cancellation, only accessed via runid/stop + + mutex sync.RWMutex frame *frame // program data storage during execution - nindex int // next node index - fset *token.FileSet // fileset to locate node in source code universe *scope // interpreter global level scope scopes map[string]*scope // package level scopes, indexed by package name - binPkg Exports // binary packages used in interpreter, indexed by path srcPkg imports // source packages used in interpreter, indexed by path - rdir map[string]bool // for src import cycle detection + done chan struct{} // for cancellation of channel operations } const ( @@ -147,15 +191,17 @@ func New(options Options) *Interpreter { i.opt.context.BuildTags = options.BuildTags } - // AstDot activates AST graph display for the interpreter + // astDot activates AST graph display for the interpreter i.opt.astDot, _ = strconv.ParseBool(os.Getenv("YAEGI_AST_DOT")) - // CfgDot activates AST graph display for the interpreter + // cfgDot activates AST graph display for the interpreter i.opt.cfgDot, _ = strconv.ParseBool(os.Getenv("YAEGI_CFG_DOT")) - // NoRun disable the execution (but not the compilation) in the interpreter + // noRun disables the execution (but not the compilation) in the interpreter i.opt.noRun, _ = strconv.ParseBool(os.Getenv("YAEGI_NO_RUN")) + // fastChan disables the cancellable version of channel operations in evalWithContext + i.opt.fastChan, _ = strconv.ParseBool(os.Getenv("YAEGI_FAST_CHAN")) return &i } @@ -228,6 +274,8 @@ func (interp *Interpreter) resizeFrame() { } func (interp *Interpreter) main() *node { + interp.mutex.RLock() + defer interp.mutex.RUnlock() if m, ok := interp.scopes[mainID]; ok && m.sym[mainID] != nil { return m.sym[mainID].node } @@ -278,11 +326,13 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) { // REPL may skip package statement setExec(root.start) } + interp.mutex.Lock() if interp.universe.sym[pkgName] == nil { // Make the package visible under a path identical to its name interp.srcPkg[pkgName] = interp.scopes[pkgName].sym interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}} } + interp.mutex.Unlock() if interp.cfgDot { root.cfgDot(dotX()) @@ -292,11 +342,18 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) { return res, err } - // Execute CFG + // Generate node exec closures if err = genRun(root); err != nil { return res, err } + + // Init interpreter execution memory frame + interp.frame.setrunid(interp.runid()) + interp.frame.mutex.Lock() interp.resizeFrame() + interp.frame.mutex.Unlock() + + // Execute node closures interp.run(root, nil) for _, n := range initNodes { @@ -315,6 +372,42 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) { return res, err } +// EvalWithContext evaluates Go code represented as a string. It returns +// a map on current interpreted package exported symbols. +func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (reflect.Value, error) { + var v reflect.Value + var err error + + interp.mutex.Lock() + interp.done = make(chan struct{}) + interp.cancelChan = !interp.opt.fastChan + interp.mutex.Unlock() + + done := make(chan struct{}) + go func() { + defer close(done) + v, err = interp.Eval(src) + }() + + select { + case <-ctx.Done(): + interp.stop() + return reflect.Value{}, ctx.Err() + case <-done: + return v, err + } +} + +// stop sends a semaphore to all running frames and closes the chan +// operation short circuit channel. stop may only be called once per +// invocation of EvalWithContext. +func (interp *Interpreter) stop() { + atomic.AddUint64(&interp.id, 1) + close(interp.done) +} + +func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) } + // getWrapper returns the wrapper type of the corresponding interface, or nil if not found func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type { if p, ok := interp.binPkg[t.PkgPath()]; ok { @@ -340,7 +433,12 @@ func (interp *Interpreter) REPL(in io.Reader, out io.Writer) { src := "" for s.Scan() { src += s.Text() + "\n" - if v, err := interp.Eval(src); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + handleSignal(ctx, cancel) + v, err := interp.EvalWithContext(ctx, src) + signal.Reset() + if err != nil { switch err.(type) { case scanner.ErrorList: // Early failure in the scanner: the source is incomplete @@ -377,3 +475,16 @@ func getPrompt(in io.Reader, out io.Writer) func() { } return func() {} } + +// handleSignal wraps signal handling for eval cancellation. +func handleSignal(ctx context.Context, cancel context.CancelFunc) { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + select { + case <-c: + cancel() + case <-ctx.Done(): + } + }() +} diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index d41034b08..305bd00de 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -1,6 +1,7 @@ package interp_test import ( + "context" "fmt" "log" "net/http" @@ -396,6 +397,100 @@ func TestEvalMissingSymbol(t *testing.T) { } } +func TestEvalWithContext(t *testing.T) { + tests := []testCase{ + { + desc: "for {}", + src: `(func() { + for {} + })()`, + }, + { + desc: "select {}", + src: `(func() { + select {} + })()`, + }, + { + desc: "blocked chan send", + src: `(func() { + c := make(chan int) + c <- 1 + })()`, + }, + { + desc: "blocked chan recv", + src: `(func() { + c := make(chan int) + <-c + })()`, + }, + { + desc: "blocked chan recv2", + src: `(func() { + c := make(chan int) + _, _ = <-c + })()`, + }, + { + desc: "blocked range chan", + src: `(func() { + c := make(chan int) + for range c {} + })()`, + }, + { + desc: "double lock", + src: `(func() { + var mu sync.Mutex + mu.Lock() + mu.Lock() + })()`, + }, + } + + for _, test := range tests { + done := make(chan struct{}) + src := test.src + go func() { + defer close(done) + i := interp.New(interp.Options{}) + i.Use(stdlib.Symbols) + _, err := i.Eval(`import "sync"`) + if err != nil { + t.Errorf(`failed to import "sync": %v`, err) + return + } + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + _, err = i.EvalWithContext(ctx, src) + switch err { + case context.DeadlineExceeded: + // Successful cancellation. + + // Check we can still execute an expression. + v, err := i.EvalWithContext(context.Background(), "1+1\n") //nolint:govet + if err != nil { + t.Errorf("failed to evaluate expression after cancellation: %v", err) + } + got := v.Interface() + if got != 2 { + t.Errorf("unexpected result of eval(1+1): got %v, want 2", got) + } + case nil: + t.Errorf("unexpected success evaluating expression %q", test.desc) + default: + t.Errorf("failed to evaluate expression %q: %v", test.desc, err) + } + }() + select { + case <-time.After(time.Second): + t.Errorf("timeout failed to terminate execution of %q", test.desc) + case <-done: + } + } +} + func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { diff --git a/interp/run.go b/interp/run.go index ef8a285f3..f839f919a 100644 --- a/interp/run.go +++ b/interp/run.go @@ -84,8 +84,11 @@ func (interp *Interpreter) run(n *node, cf *frame) { if cf == nil { f = interp.frame } else { - f = &frame{anc: cf, data: make([]reflect.Value, len(n.types))} + f = newFrame(cf, len(n.types), interp.runid()) } + interp.mutex.RLock() + f.done = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(interp.done)} + interp.mutex.RUnlock() for i, t := range n.types { f.data[i] = reflect.New(t).Elem() @@ -98,17 +101,20 @@ func (interp *Interpreter) run(n *node, cf *frame) { // runCfg executes a node AST by walking its CFG and running node builtin at each step func runCfg(n *node, f *frame) { defer func() { + f.mutex.Lock() f.recovered = recover() for _, val := range f.deferred { val[0].Call(val[1:]) } if f.recovered != nil { fmt.Println(n.cfgErrorf("panic")) + f.mutex.Unlock() panic(f.recovered) } + f.mutex.Unlock() }() - for exec := n.exec; exec != nil; { + for exec := n.exec; exec != nil && f.runid() == n.interp.runid(); { exec = exec(f) } } @@ -442,7 +448,7 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value { } return reflect.MakeFunc(n.typ.TypeOf(), func(in []reflect.Value) []reflect.Value { // Allocate and init local frame. All values to be settable and addressable. - fr := frame{anc: f, data: make([]reflect.Value, len(def.types))} + fr := newFrame(f, len(def.types), f.runid()) d := fr.data for i, t := range def.types { d[i] = reflect.New(t).Elem() @@ -471,7 +477,7 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value { } // Interpreter code execution - runCfg(start, &fr) + runCfg(start, fr) result := fr.data[:numRet] for i, r := range result { @@ -679,7 +685,7 @@ func call(n *node) { if def.frame != nil { anc = def.frame } - nf := frame{anc: anc, data: make([]reflect.Value, len(def.types))} + nf := newFrame(anc, len(def.types), anc.runid()) var vararg reflect.Value // Init return values @@ -744,10 +750,10 @@ func call(n *node) { // Execute function body if goroutine { - go runCfg(def.child[3].start, &nf) + go runCfg(def.child[3].start, nf) return tnext } - runCfg(def.child[3].start, &nf) + runCfg(def.child[3].start, nf) // Handle branching according to boolean result if fnext != nil && !nf.data[0].Bool() { @@ -1049,10 +1055,10 @@ func getFunc(n *node) { next := getExec(n.tnext) n.exec = func(f *frame) bltn { - fr := *f + fr := f.clone() nod := *n nod.val = &nod - nod.frame = &fr + nod.frame = fr dest(f).Set(reflect.ValueOf(&nod)) return next } @@ -1063,11 +1069,11 @@ func getMethod(n *node) { next := getExec(n.tnext) n.exec = func(f *frame) bltn { - fr := *f + fr := f.clone() nod := *(n.val.(*node)) nod.val = &nod nod.recv = n.recv - nod.frame = &fr + nod.frame = fr f.data[i] = reflect.ValueOf(&nod) return next } @@ -1082,11 +1088,11 @@ func getMethodByName(n *node) { n.exec = func(f *frame) bltn { val := value0(f).Interface().(valueInterface) m, li := val.node.typ.lookupMethod(name) - fr := *f + fr := f.clone() nod := *m nod.val = &nod nod.recv = &receiver{nil, val.value, li} - nod.frame = &fr + nod.frame = fr f.data[i] = reflect.ValueOf(&nod) return next } @@ -1618,7 +1624,10 @@ func rangeChan(n *node) { tnext := getExec(n.tnext) n.exec = func(f *frame) bltn { - v, ok := value(f).Recv() + chosen, v, ok := reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: value(f)}}) + if chosen == 0 { + return nil + } if !ok { return fnext } @@ -2050,19 +2059,63 @@ func recv(n *node) { value := genValue(n.child[0]) tnext := getExec(n.tnext) - if n.fnext != nil { - fnext := getExec(n.fnext) - n.exec = func(f *frame) bltn { - if v, _ := value(f).Recv(); v.Bool() { + if n.interp.cancelChan { + // Cancellable channel read + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + ch := value(f) + // Fast: channel read doesn't block + if x, ok := ch.TryRecv(); ok { + if x.Bool() { + return tnext + } + return fnext + } + // Slow: channel read blocks, allow cancel + chosen, v, _ := reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } + if v.Bool() { + return tnext + } + return fnext + } + } else { + i := n.findex + n.exec = func(f *frame) bltn { + // Fast: channel read doesn't block + var ok bool + ch := value(f) + if f.data[i], ok = ch.TryRecv(); ok { + return tnext + } + // Slow: channel is blocked, allow cancel + var chosen int + chosen, f.data[i], _ = reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } return tnext } - return fnext } } else { - i := n.findex - n.exec = func(f *frame) bltn { - f.data[i], _ = value(f).Recv() - return tnext + // Blocking channel read (less overhead) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if v, _ := value(f).Recv(); v.Bool() { + return tnext + } + return fnext + } + } else { + i := n.findex + n.exec = func(f *frame) bltn { + f.data[i], _ = value(f).Recv() + return tnext + } } } } @@ -2073,11 +2126,33 @@ func recv2(n *node) { vok := genValue(n.anc.child[1]) // status tnext := getExec(n.tnext) - n.exec = func(f *frame) bltn { - v, ok := vchan(f).Recv() - vres(f).Set(v) - vok(f).SetBool(ok) - return tnext + if n.interp.cancelChan { + // Cancellable channel read + n.exec = func(f *frame) bltn { + ch, result, status := vchan(f), vres(f), vok(f) + // Fast: channel read doesn't block + if v, ok := ch.TryRecv(); ok { + result.Set(v) + status.SetBool(true) + return tnext + } + // Slow: channel is blocked, allow cancel + chosen, v, ok := reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } + result.Set(v) + status.SetBool(ok) + return tnext + } + } else { + // Blocking channel read (less overhead) + n.exec = func(f *frame) bltn { + v, ok := vchan(f).Recv() + vres(f).Set(v) + vok(f).SetBool(ok) + return tnext + } } } @@ -2099,9 +2174,27 @@ func send(n *node) { convertLiteralValue(n.child[1], n.child[0].typ.val.TypeOf()) value1 := genValue(n.child[1]) // value to send - n.exec = func(f *frame) bltn { - value0(f).Send(value1(f)) - return next + if n.interp.cancelChan { + // Cancellable send + n.exec = func(f *frame) bltn { + ch, data := value0(f), value1(f) + // Fast: send on channel doesn't block + if ok := ch.TrySend(data); ok { + return next + } + // Slow: send on channel blocks, allow cancel + chosen, _, _ := reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectSend, Chan: ch, Send: data}}) + if chosen == 0 { + return nil + } + return next + } + } else { + // Blocking send (less overhead) + n.exec = func(f *frame) bltn { + value0(f).Send(value1(f)) + return next + } } } @@ -2143,7 +2236,7 @@ func _select(n *node) { chanValues := make([]func(*frame) reflect.Value, nbClause) assignedValues := make([]func(*frame) reflect.Value, nbClause) okValues := make([]func(*frame) reflect.Value, nbClause) - cases := make([]reflect.SelectCase, nbClause) + cases := make([]reflect.SelectCase, nbClause+1) for i := 0; i < nbClause; i++ { if len(n.child[i].child) > 1 { @@ -2163,7 +2256,8 @@ func _select(n *node) { } n.exec = func(f *frame) bltn { - for i := range cases { + cases[nbClause] = f.done + for i := range cases[:nbClause] { switch cases[i].Dir { case reflect.SelectRecv: cases[i].Chan = chanValues[i](f) @@ -2175,6 +2269,9 @@ func _select(n *node) { } } j, v, s := reflect.Select(cases) + if j == nbClause { + return nil + } if cases[j].Dir == reflect.SelectRecv && assignedValues[j] != nil { assignedValues[j](f).Set(v) if ok[j] != nil { diff --git a/interp/scope.go b/interp/scope.go index 8a985d697..0dda7f425 100644 --- a/interp/scope.go +++ b/interp/scope.go @@ -164,10 +164,11 @@ func (interp *Interpreter) initScopePkg(n *node) (*scope, string) { pkgName = p.child[0].ident } + interp.mutex.Lock() if _, ok := interp.scopes[pkgName]; !ok { interp.scopes[pkgName] = sc.pushBloc() } - sc = interp.scopes[pkgName] + interp.mutex.Unlock() return sc, pkgName } diff --git a/interp/src.go b/interp/src.go index 055edb488..ddc652ad4 100644 --- a/interp/src.go +++ b/interp/src.go @@ -102,6 +102,7 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error { // Register source package in the interpreter. The package contains only // the global symbols in the package scope. + interp.mutex.Lock() interp.srcPkg[path] = interp.scopes[pkgName].sym // Rename imported pkgName to alias if they are different @@ -110,7 +111,10 @@ func (interp *Interpreter) importSrc(rPath, path, alias string) error { delete(interp.scopes, pkgName) } + interp.frame.mutex.Lock() interp.resizeFrame() + interp.frame.mutex.Unlock() + interp.mutex.Unlock() // Once all package sources have been parsed, execute entry points then init functions for _, n := range rootNodes { diff --git a/interp/value.go b/interp/value.go index d6d7c864e..997afe425 100644 --- a/interp/value.go +++ b/interp/value.go @@ -7,21 +7,31 @@ import ( func valueGenerator(n *node, i int) func(*frame) reflect.Value { switch n.level { case 0: - return func(f *frame) reflect.Value { return f.data[i] } + return func(f *frame) reflect.Value { return valueOf(f.data, i) } case 1: - return func(f *frame) reflect.Value { return f.anc.data[i] } + return func(f *frame) reflect.Value { return valueOf(f.anc.data, i) } case 2: - return func(f *frame) reflect.Value { return f.anc.anc.data[i] } + return func(f *frame) reflect.Value { return valueOf(f.anc.anc.data, i) } default: return func(f *frame) reflect.Value { for level := n.level; level > 0; level-- { f = f.anc } - return f.data[i] + return valueOf(f.data, i) } } } +// valueOf safely recovers the ith element of data. This is necessary +// because a cancellation prior to any evaluation result may leave +// the frame's data empty. +func valueOf(data []reflect.Value, i int) reflect.Value { + if i < len(data) { + return data[i] + } + return reflect.Value{} +} + func genValueRecvIndirect(n *node) func(*frame) reflect.Value { v := genValueRecv(n) return func(f *frame) reflect.Value { return v(f).Elem() }