diff --git a/cmpl_evaluate_statement.go b/cmpl_evaluate_statement.go index e16c6ac..056d795 100644 --- a/cmpl_evaluate_statement.go +++ b/cmpl_evaluate_statement.go @@ -259,6 +259,17 @@ resultBreak: break } } + + // this is to prevent for cycles with no body from running forever + if len(body) == 0 && self.otto.Interrupt != nil { + runtime.Gosched() + select { + case value := <-self.otto.Interrupt: + value() + default: + } + } + for _, node := range body { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { diff --git a/otto_test.go b/otto_test.go index cdbf95c..fc30cd6 100644 --- a/otto_test.go +++ b/otto_test.go @@ -2,10 +2,14 @@ package otto import ( "bytes" + "errors" + "fmt" "io" "testing" + "time" "github.com/robertkrimen/otto/parser" + "github.com/stretchr/testify/require" ) func TestOtto(t *testing.T) { @@ -1738,6 +1742,56 @@ func Test_stackLimit(t *testing.T) { }) } +func TestOttoInterrupt(t *testing.T) { + tests := []struct { + name string + script string + }{ + { + name: "empty-for-loop", + script: "for(;;) {}", + }, + { + name: "empty-do-while", + script: "do{} while(true)", + }, + } + + halt := errors.New("interrupt") + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + vm := New() + vm.Interrupt = make(chan func(), 1) + ec := make(chan error, 1) + go func() { + defer func() { + if caught := recover(); caught != nil { + if caught == halt { + fmt.Println(caught) + ec <- nil + return + } + panic(caught) + } + }() + _, err := vm.Run(tc.script) + ec <- err + }() + + // Give the vm chance to execute the loop. + time.Sleep(time.Millisecond * 100) + vm.Interrupt <- func() { panic(halt) } + + select { + case <-time.After(time.Second): + t.Fatal("timeout") + case err := <-ec: + require.NoError(t, err) + } + }) + } +} + func BenchmarkNew(b *testing.B) { for i := 0; i < b.N; i++ { New()