mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
455 lines
12 KiB
Go
455 lines
12 KiB
Go
package compiler_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func checkCallCount(t *testing.T, src string, expectedCall, expectedInitSlot, expectedLocalsMain int) {
|
|
checkInstrCount(t, src, -1, expectedCall, expectedInitSlot, expectedLocalsMain)
|
|
}
|
|
|
|
func checkInstrCount(t *testing.T, src string, expectedSSlotCount, expectedCall, expectedInitSlot, expectedLocalsMain int) {
|
|
v, sp, _ := vmAndCompileInterop(t, src)
|
|
mainStart := -1
|
|
for _, m := range sp.info.Methods {
|
|
if m.Name.Name == "main" {
|
|
mainStart = int(m.Range.Start)
|
|
}
|
|
}
|
|
require.True(t, mainStart >= 0)
|
|
|
|
ctx := v.Context()
|
|
actualCall := 0
|
|
actualInitSlot := 0
|
|
|
|
for op, param, err := ctx.Next(); ; op, param, err = ctx.Next() {
|
|
require.NoError(t, err)
|
|
switch op {
|
|
case opcode.INITSSLOT:
|
|
if expectedSSlotCount == -1 {
|
|
continue
|
|
}
|
|
if expectedSSlotCount == 0 {
|
|
t.Fatalf("no INITSSLOT expected, found at %d with %d cells", ctx.IP(), param[0])
|
|
}
|
|
require.Equal(t, expectedSSlotCount, int(param[0]))
|
|
case opcode.CALL, opcode.CALLL:
|
|
actualCall++
|
|
case opcode.INITSLOT:
|
|
actualInitSlot++
|
|
if ctx.IP() == mainStart && expectedLocalsMain >= 0 {
|
|
require.Equal(t, expectedLocalsMain, int(param[0]))
|
|
}
|
|
}
|
|
if ctx.IP() == ctx.LenInstr() {
|
|
break
|
|
}
|
|
}
|
|
if expectedCall != -1 {
|
|
require.Equal(t, expectedCall, actualCall)
|
|
}
|
|
if expectedInitSlot != -1 {
|
|
require.Equal(t, expectedInitSlot, actualInitSlot)
|
|
}
|
|
}
|
|
|
|
func TestInline(t *testing.T) {
|
|
srcTmpl := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/foo"
|
|
var _ = foo.Dummy
|
|
type pair struct { a, b int }
|
|
type triple struct {
|
|
a int
|
|
b pair
|
|
}
|
|
var Num = 1
|
|
func Main() int {
|
|
%s
|
|
}
|
|
// local alias
|
|
func sum(a, b int) int {
|
|
return 42
|
|
}`
|
|
t.Run("no return", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `inline.NoArgsNoReturn()
|
|
return 1`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(1))
|
|
})
|
|
t.Run("has return, dropped", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `inline.NoArgsReturn1()
|
|
return 2`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(2))
|
|
})
|
|
t.Run("drop twice", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `inline.DropInsideInline()
|
|
return 42`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("no args return 1", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.NoArgsReturn1()`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(1))
|
|
})
|
|
t.Run("sum", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(1, 2)`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
t.Run("sum squared (nested inline)", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.SumSquared(1, 2)`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(9))
|
|
})
|
|
t.Run("inline function in inline function parameter", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(inline.SumSquared(1, 2), inline.Sum(3, 4))`)
|
|
checkCallCount(t, src, 0, 1, 2)
|
|
eval(t, src, big.NewInt(9+3+4))
|
|
})
|
|
t.Run("global name clash", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.GetSumSameName()`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("local name clash", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(inline.SumSquared(1, 2), sum(3, 4))`)
|
|
checkCallCount(t, src, 1, 2, 2)
|
|
eval(t, src, big.NewInt(51))
|
|
})
|
|
t.Run("var args, empty", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.VarSum(11)`)
|
|
checkCallCount(t, src, 0, 1, 3)
|
|
eval(t, src, big.NewInt(11))
|
|
})
|
|
t.Run("var args, direct", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.VarSum(11, 14, 17)`)
|
|
checkCallCount(t, src, 0, 1, 3)
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("var args, array", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `arr := []int{14, 17}
|
|
return inline.VarSum(11, arr...)`)
|
|
checkCallCount(t, src, 0, 1, 3)
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("globals", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Concat(Num)`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(221))
|
|
})
|
|
t.Run("locals, alias", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `num := 1; return inline.Concat(num)`)
|
|
checkCallCount(t, src, 0, 1, 1)
|
|
eval(t, src, big.NewInt(221))
|
|
})
|
|
t.Run("selector, global", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(inline.A, 2)`)
|
|
checkCallCount(t, src, 0, 0, 0)
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
t.Run("selector, struct, simple", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `x := pair{a: 1, b: 2}; return inline.Sum(x.b, 1)`)
|
|
checkCallCount(t, src, 0, 1, 1)
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
t.Run("selector, struct, complex", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `x := triple{a: 1, b: pair{a: 2, b: 3}}
|
|
return inline.Sum(x.b.a, 1)`)
|
|
checkCallCount(t, src, 0, 1, 1)
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
t.Run("expression", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `x, y := 1, 2
|
|
return inline.Sum(x+y, y*2)`)
|
|
checkCallCount(t, src, 0, 1, 2)
|
|
eval(t, src, big.NewInt(7))
|
|
})
|
|
t.Run("foreign package call", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(foo.Bar(), foo.Dummy+1)`)
|
|
checkCallCount(t, src, 1, 1, 1)
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
}
|
|
|
|
func TestIssue1879(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
func Main() int {
|
|
data := "main is called"
|
|
runtime.Log("log " + string(data))
|
|
return 42
|
|
}`
|
|
checkCallCount(t, src, 0, 1, 1)
|
|
}
|
|
|
|
func TestInlineInLoop(t *testing.T) {
|
|
t.Run("simple", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
sum := 0
|
|
values := []int{10, 11}
|
|
for _, v := range values {
|
|
_ = v // use 'v'
|
|
storage.GetContext() // push something on stack to check it's dropped
|
|
sum += inline.VarSum(1, 2, 3, 4)
|
|
}
|
|
return sum
|
|
}`
|
|
eval(t, src, big.NewInt(20))
|
|
})
|
|
t.Run("inlined argument", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
sum := 0
|
|
values := []int{10, 11}
|
|
for _, v := range values {
|
|
_ = v // use 'v'
|
|
storage.GetContext() // push something on stack to check it's dropped
|
|
sum += inline.VarSum(1, 2, 3, runtime.GetTime()) // runtime.GetTime always returns 4 in these tests
|
|
}
|
|
return sum
|
|
}`
|
|
eval(t, src, big.NewInt(20))
|
|
})
|
|
t.Run("check clean stack on return", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
values := []int{10, 11, 12}
|
|
for _, v := range values {
|
|
storage.GetContext() // push something on stack to check it's dropped
|
|
if v == 11 {
|
|
return inline.VarSum(2, 20, 200)
|
|
}
|
|
}
|
|
return 0
|
|
}`
|
|
eval(t, src, big.NewInt(222))
|
|
})
|
|
}
|
|
|
|
func TestInlineInSwitch(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
switch inline.VarSum(1, 2) {
|
|
case inline.VarSum(3, 1):
|
|
return 10
|
|
case inline.VarSum(4, -1):
|
|
return 11
|
|
default:
|
|
return 12
|
|
}
|
|
}`
|
|
eval(t, src, big.NewInt(11))
|
|
}
|
|
|
|
func TestInlineGlobalVariable(t *testing.T) {
|
|
t.Run("simple", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
var a = inline.Sum(1, 2)
|
|
func Main() int {
|
|
return a
|
|
}`
|
|
eval(t, src, big.NewInt(3))
|
|
})
|
|
t.Run("complex", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
var a = inline.Sum(3, 4)
|
|
var b = inline.SumSquared(1, 2)
|
|
var c = a + b
|
|
func init() {
|
|
c--
|
|
}
|
|
func Main() int {
|
|
return c
|
|
}`
|
|
eval(t, src, big.NewInt(15))
|
|
})
|
|
}
|
|
|
|
func TestInlineVariadicInInlinedCall(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
return inline.SumSquared(inline.SumVar(3, 4) - 2, 3)
|
|
}`
|
|
eval(t, src, big.NewInt(64))
|
|
}
|
|
|
|
func TestInlineConversion(t *testing.T) {
|
|
src1 := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
var _ = inline.A
|
|
func Main() int {
|
|
a := 2
|
|
return inline.SumSquared(1, a)
|
|
}`
|
|
b1, err := compiler.Compile("foo.go", strings.NewReader(src1))
|
|
require.NoError(t, err)
|
|
|
|
src2 := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
var _ = inline.A
|
|
func Main() int {
|
|
a := 2
|
|
{
|
|
return (1 + a) * (1 + a)
|
|
}
|
|
}`
|
|
b2, err := compiler.Compile("foo.go", strings.NewReader(src2))
|
|
require.NoError(t, err)
|
|
require.Equal(t, b2, b1)
|
|
}
|
|
|
|
func TestInlineConversionQualified(t *testing.T) {
|
|
src1 := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
var A = 1
|
|
func Main() int {
|
|
return inline.Concat(A)
|
|
}`
|
|
b1, err := compiler.Compile("foo.go", strings.NewReader(src1))
|
|
require.NoError(t, err)
|
|
|
|
src2 := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/b"
|
|
var A = 1
|
|
func Main() int {
|
|
return A * 100 + b.A * 10 + inline.A
|
|
}`
|
|
b2, err := compiler.Compile("foo.go", strings.NewReader(src2))
|
|
require.NoError(t, err)
|
|
require.Equal(t, b2, b1)
|
|
}
|
|
|
|
func TestPackageVarsInInlinedCalls(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/b"
|
|
func Main() int {
|
|
return inline.Sum(inline.A, b.A)
|
|
}`
|
|
eval(t, src, big.NewInt(13))
|
|
}
|
|
|
|
func TestInlinedMethod(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
// It's important for this variable to not be named 't'.
|
|
var z inline.T
|
|
i := z.Inc(42)
|
|
if i != 0 || z.N != 42 {
|
|
return 0
|
|
}
|
|
i = z.Inc(100500)
|
|
if i != 42 {
|
|
return 0
|
|
}
|
|
return z.N
|
|
}`
|
|
eval(t, src, big.NewInt(100542))
|
|
}
|
|
|
|
func TestInlinedMethodWithPointer(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
func Main() int {
|
|
// It's important for this variable to not be named 't'.
|
|
var z = &inline.T{}
|
|
i := z.Inc(42)
|
|
if i != 0 || z.N != 42 {
|
|
return 0
|
|
}
|
|
i = z.Inc(100500)
|
|
if i != 42 {
|
|
return 0
|
|
}
|
|
return z.N
|
|
}`
|
|
eval(t, src, big.NewInt(100542))
|
|
}
|
|
|
|
func TestInlineConditionalReturn(t *testing.T) {
|
|
srcTmpl := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/c"
|
|
func Main() int {
|
|
x := %d
|
|
if c.Is42(x) {
|
|
return 100
|
|
}
|
|
return 10
|
|
}`
|
|
t.Run("true", func(t *testing.T) {
|
|
eval(t, fmt.Sprintf(srcTmpl, 123), big.NewInt(10))
|
|
})
|
|
t.Run("false", func(t *testing.T) {
|
|
eval(t, fmt.Sprintf(srcTmpl, 42), big.NewInt(100))
|
|
})
|
|
}
|
|
|
|
func TestInlineDoubleConditionalReturn(t *testing.T) {
|
|
srcTmpl := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline/c"
|
|
func Main() int {
|
|
return c.Transform(%d, %d)
|
|
}`
|
|
|
|
testCase := []struct {
|
|
name string
|
|
a, b, result int
|
|
}{
|
|
{"true, true, small", 42, 3, 6},
|
|
{"true, true, big", 42, 15, 15},
|
|
{"true, false", 42, 42, 42},
|
|
{"false, true", 3, 11, 6},
|
|
{"false, false", 3, 42, 6},
|
|
}
|
|
|
|
for _, tc := range testCase {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
eval(t, fmt.Sprintf(srcTmpl, tc.a, tc.b), big.NewInt(int64(tc.result)))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInlineAppendStatement(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
|
|
func Main() []byte {
|
|
val := []byte{4, 5, 6}
|
|
return inline.AppendInsideInline(val)
|
|
}`
|
|
eval(t, src, []byte{1, 2, 3, 4, 5, 6})
|
|
}
|
|
|
|
func TestInlineForeignType(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
|
|
func Main() int {
|
|
return inline.ForeignTypeInsideInline()
|
|
}`
|
|
eval(t, src, big.NewInt(29))
|
|
}
|