neo-go/pkg/compiler/defer_test.go
Evgeniy Stratonikov b5afe24090 compiler: properly process defer in conditional statements
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2022-02-04 11:04:03 +03:00

301 lines
6.2 KiB
Go

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))
}