package compiler_test import ( "math/big" "strings" "testing" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) func TestDefer(t *testing.T) { t.Run("Simple", func(t *testing.T) { src := `package main var a int func Main() int { return h() + a } func h() int { defer f() return 1 } func f() { a += 2 }` eval(t, src, big.NewInt(3)) }) t.Run("ValueUnchanged", func(t *testing.T) { src := `package main var a int func Main() int { defer f() a = 3 return a } func f() { a += 2 }` eval(t, src, big.NewInt(3)) }) t.Run("Function", func(t *testing.T) { src := `package main var a int func Main() int { return h() + a } func h() int { defer f() a = 3 return g() } func g() int { a++ return a } func f() { a += 2 }` eval(t, src, big.NewInt(10)) }) t.Run("DeferAfterInterop", func(t *testing.T) { src := `package main import ( "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) func Main() { defer func() { }() storage.GetContext() }` vm := vmAndCompile(t, src) err := vm.Run() require.NoError(t, err) require.Equal(t, 0, vm.Estack().Len(), "stack contains unexpected items") }) t.Run("MultipleDefers", func(t *testing.T) { src := `package main var a int func Main() int { return h() + a } func h() int { defer f() defer g() a = 3 return a } func g() { a *= 2 } func f() { a += 2 }` eval(t, src, big.NewInt(11)) }) t.Run("FunctionLiteral", func(t *testing.T) { src := `package main var a int func Main() int { return h() + a } func h() int { defer func() { a = 10 }() a = 3 return a }` eval(t, src, big.NewInt(13)) }) t.Run("NoReturnReturn", func(t *testing.T) { src := `package main var i int func Main() { defer func() { i++ }() return }` vm := vmAndCompile(t, src) err := vm.Run() require.NoError(t, err) require.Equal(t, 0, vm.Estack().Len(), "stack contains unexpected items") }) t.Run("NoReturnNoReturn", func(t *testing.T) { src := `package main var i int func Main() { defer func() { i++ }() }` vm := vmAndCompile(t, src) err := vm.Run() require.NoError(t, err) require.Equal(t, 0, vm.Estack().Len(), "stack contains unexpected items") }) t.Run("CodeDuplication", func(t *testing.T) { src := `package main var i int func Main() { defer func() { var j int i += j }() if i == 1 { return } if i == 2 { return } if i == 3 { return } if i == 4 { return } if i == 5 { return } }` checkCallCount(t, src, 0 /* defer body + Main */, 2, -1) }) } func TestConditionalDefer(t *testing.T) { type testCase struct { a []bool result int64 } t.Run("no panic", func(t *testing.T) { src := `package foo var i int func Main(a []bool) int { return f(a[0], a[1], a[2]) + i } func g() { i += 10 } func f(a bool, b bool, c bool) int { if a { defer func() { i += 1 }() } if b { defer g() } if c { defer func() { i += 100 }() } return 0 }` testCases := []testCase{ {[]bool{false, false, false}, 0}, {[]bool{false, false, true}, 100}, {[]bool{false, true, false}, 10}, {[]bool{false, true, true}, 110}, {[]bool{true, false, false}, 1}, {[]bool{true, false, true}, 101}, {[]bool{true, true, false}, 11}, {[]bool{true, true, true}, 111}, } for _, tc := range testCases { args := []stackitem.Item{stackitem.Make(tc.a[0]), stackitem.Make(tc.a[1]), stackitem.Make(tc.a[2])} evalWithArgs(t, src, nil, args, big.NewInt(tc.result)) } }) t.Run("panic between ifs", func(t *testing.T) { src := `package foo var i int func Main(a []bool) int { if a[1] { defer func() { recover() }() }; return f(a[0], a[1]) + i } func f(a, b bool) int { if a { defer func() { i += 1; recover() }() } panic("totally expected") if b { defer func() { i += 100; recover() }() } return 0 }` args := []stackitem.Item{stackitem.Make(false), stackitem.Make(false)} v := vmAndCompile(t, src) v.Estack().PushVal(args) err := v.Run() require.Error(t, err) require.True(t, strings.Contains(err.Error(), "totally expected")) testCases := []testCase{ {[]bool{false, true}, 0}, {[]bool{true, false}, 1}, {[]bool{true, true}, 1}, } for _, tc := range testCases { args := []stackitem.Item{stackitem.Make(tc.a[0]), stackitem.Make(tc.a[1])} evalWithArgs(t, src, nil, args, big.NewInt(tc.result)) } }) t.Run("panic in conditional", func(t *testing.T) { src := `package foo var i int func Main(a []bool) int { if a[1] { defer func() { recover() }() }; return f(a[0], a[1]) + i } func f(a, b bool) int { if a { defer func() { i += 1; recover() }() panic("somewhat expected") } if b { defer func() { i += 100; recover() }() } return 0 }` testCases := []testCase{ {[]bool{false, false}, 0}, {[]bool{false, true}, 100}, {[]bool{true, false}, 1}, {[]bool{true, true}, 1}, } for _, tc := range testCases { args := []stackitem.Item{stackitem.Make(tc.a[0]), stackitem.Make(tc.a[1])} evalWithArgs(t, src, nil, args, big.NewInt(tc.result)) } }) } func TestRecover(t *testing.T) { t.Run("Panic", func(t *testing.T) { src := `package foo var a int func Main() int { return h() + a } func h() int { defer func() { if r := recover(); r != nil { a = 3 } else { a = 4 } }() a = 1 panic("msg") return a }` eval(t, src, big.NewInt(3)) }) t.Run("NoPanic", func(t *testing.T) { src := `package foo var a int func Main() int { return h() + a } func h() int { defer func() { if r := recover(); r != nil { a = 3 } else { a = 4 } }() a = 1 return a }` eval(t, src, big.NewInt(5)) }) t.Run("PanicInDefer", func(t *testing.T) { src := `package foo var a int func Main() int { return h() + a } func h() int { defer func() { a += 2; recover() }() defer func() { a *= 3; recover(); panic("again") }() a = 1 panic("msg") return a }` eval(t, src, big.NewInt(5)) }) } func TestDeferNoGlobals(t *testing.T) { src := `package foo func Main() int { a := 1 defer func() { recover() }() panic("msg") return a }` eval(t, src, big.NewInt(0)) }