mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
0e86073a77
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
590 lines
11 KiB
Go
590 lines
11 KiB
Go
package compiler_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var sliceTestCases = []testCase{
|
|
{
|
|
"constant index",
|
|
`func F%d() int {
|
|
a := []int{0,0}
|
|
a[1] = 42
|
|
return a[1]+0
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"variable index",
|
|
`func F%d() int {
|
|
a := []int{0,0}
|
|
i := 1
|
|
a[i] = 42
|
|
return a[1]+0
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"increase slice element with +=",
|
|
`func F%d() int {
|
|
a := []int{1, 2, 3}
|
|
a[1] += 40
|
|
return a[1]
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"complex test",
|
|
`func F%d() int {
|
|
a := []int{1,2,3}
|
|
x := a[0]
|
|
a[x] = a[x] + 4
|
|
a[x] = a[x] + a[2]
|
|
return a[1]
|
|
}
|
|
`,
|
|
big.NewInt(9),
|
|
},
|
|
{
|
|
"slice literals with variables",
|
|
`func F%d() int {
|
|
elem := 7
|
|
a := []int{6, elem, 8}
|
|
return a[1]
|
|
}
|
|
`,
|
|
big.NewInt(7),
|
|
},
|
|
{
|
|
"slice literals with expressions",
|
|
`func F%d() int {
|
|
elem := []int{3, 7}
|
|
a := []int{6, elem[1]*2+1, 24}
|
|
return a[1]
|
|
}
|
|
`,
|
|
big.NewInt(15),
|
|
},
|
|
{
|
|
"sub-slice with literal bounds",
|
|
`func F%d() []byte {
|
|
a := []byte{0, 1, 2, 3}
|
|
b := a[1:3]
|
|
return b
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"sub-slice with constant bounds",
|
|
`const x = 1
|
|
const y = 3
|
|
func F%d() []byte {
|
|
a := []byte{0, 1, 2, 3}
|
|
b := a[x:y]
|
|
return b
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"sub-slice with variable bounds",
|
|
`func F%d() []byte {
|
|
a := []byte{0, 1, 2, 3}
|
|
x := 1
|
|
y := 3
|
|
b := a[x:y]
|
|
return b
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"sub-slice with no lower bound",
|
|
`func F%d() []byte {
|
|
a := []byte{0, 1, 2, 3}
|
|
b := a[:3]
|
|
return b
|
|
}
|
|
`,
|
|
[]byte{0, 1, 2},
|
|
},
|
|
{
|
|
"sub-slice with no upper bound",
|
|
`func F%d() []byte {
|
|
a := []byte{0, 1, 2, 3}
|
|
b := a[2:]
|
|
return b
|
|
}
|
|
`,
|
|
[]byte{2, 3},
|
|
},
|
|
{
|
|
"declare byte slice",
|
|
`func F%d() []byte {
|
|
var a []byte
|
|
a = append(a, 1)
|
|
a = append(a, 2)
|
|
return a
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"append multiple bytes to a slice",
|
|
`func F%d() []byte {
|
|
var a []byte
|
|
a = append(a, 1, 2)
|
|
return a
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"append multiple ints to a slice",
|
|
`func F%d() []int {
|
|
var a []int
|
|
a = append(a, 1, 2, 3)
|
|
a = append(a, 4, 5)
|
|
return a
|
|
}
|
|
`,
|
|
[]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(1)),
|
|
stackitem.NewBigInteger(big.NewInt(2)),
|
|
stackitem.NewBigInteger(big.NewInt(3)),
|
|
stackitem.NewBigInteger(big.NewInt(4)),
|
|
stackitem.NewBigInteger(big.NewInt(5)),
|
|
},
|
|
},
|
|
{
|
|
"int slice, append slice",
|
|
` func getByte() byte { return 0x80 }
|
|
func F%d() []int {
|
|
x := []int{1}
|
|
y := []int{2, 3}
|
|
x = append(x, y...)
|
|
x = append(x, y...)
|
|
return x
|
|
}
|
|
`,
|
|
[]stackitem.Item{
|
|
stackitem.Make(1),
|
|
stackitem.Make(2), stackitem.Make(3),
|
|
stackitem.Make(2), stackitem.Make(3),
|
|
},
|
|
},
|
|
{
|
|
"declare compound slice",
|
|
`func F%d() []string {
|
|
var a []string
|
|
a = append(a, "a")
|
|
a = append(a, "b")
|
|
return a
|
|
}
|
|
`,
|
|
[]stackitem.Item{
|
|
stackitem.NewByteArray([]byte("a")),
|
|
stackitem.NewByteArray([]byte("b")),
|
|
},
|
|
},
|
|
{
|
|
"declare compound slice alias",
|
|
`type strs []string
|
|
func F%d() []string {
|
|
var a strs
|
|
a = append(a, "a")
|
|
a = append(a, "b")
|
|
return a
|
|
}
|
|
`,
|
|
[]stackitem.Item{
|
|
stackitem.NewByteArray([]byte("a")),
|
|
stackitem.NewByteArray([]byte("b")),
|
|
},
|
|
},
|
|
{
|
|
"byte-slice assignment",
|
|
`func F%d() []byte {
|
|
a := []byte{0, 1, 2}
|
|
a[1] = 42
|
|
return a
|
|
}
|
|
`,
|
|
[]byte{0, 42, 2},
|
|
},
|
|
{
|
|
"byte-slice assignment after string conversion",
|
|
`func F%d() []byte {
|
|
a := "abc"
|
|
b := []byte(a)
|
|
b[1] = 42
|
|
return []byte(a)
|
|
}
|
|
`,
|
|
[]byte{0x61, 0x62, 0x63},
|
|
},
|
|
{
|
|
"declare and append byte-slice",
|
|
`func F%d() []byte {
|
|
var a []byte
|
|
a = append(a, 1)
|
|
a = append(a, 2)
|
|
return a
|
|
}
|
|
`,
|
|
[]byte{1, 2},
|
|
},
|
|
{
|
|
"nested slice assignment",
|
|
`func F%d() int {
|
|
a := [][]int{[]int{1, 2}, []int{3, 4}}
|
|
a[1][0] = 42
|
|
return a[1][0]
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"nested slice omitted type (slice)",
|
|
`func F%d() int {
|
|
a := [][]int{{1, 2}, {3, 4}}
|
|
a[1][0] = 42
|
|
return a[1][0]
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"nested slice omitted type (struct)",
|
|
`type pairA struct { a, b int }
|
|
func F%d() int {
|
|
a := []pairA{{a: 1, b: 2}, {a: 3, b: 4}}
|
|
a[1].a = 42
|
|
return a[1].a
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"defaults to nil for byte slice",
|
|
`func F%d() int {
|
|
var a []byte
|
|
if a != nil { return 1}
|
|
return 2
|
|
}
|
|
`,
|
|
big.NewInt(2),
|
|
},
|
|
{
|
|
"defaults to nil for int slice",
|
|
`func F%d() int {
|
|
var a []int
|
|
if a != nil { return 1}
|
|
return 2
|
|
}
|
|
`,
|
|
big.NewInt(2),
|
|
},
|
|
{
|
|
"defaults to nil for struct slice",
|
|
`type pairB struct { a, b int }
|
|
func F%d() int {
|
|
var a []pairB
|
|
if a != nil { return 1}
|
|
return 2
|
|
}
|
|
`,
|
|
big.NewInt(2),
|
|
},
|
|
{
|
|
"literal byte-slice with variable values",
|
|
`const sym1 = 's'
|
|
func F%d() []byte {
|
|
sym2 := byte('t')
|
|
sym4 := byte('i')
|
|
return []byte{sym1, sym2, 'r', sym4, 'n', 'g'}
|
|
}
|
|
`,
|
|
[]byte("string"),
|
|
},
|
|
{
|
|
"literal slice with function call",
|
|
`func fn() byte { return 't' }
|
|
func F%d() []byte {
|
|
return []byte{'s', fn(), 'r'}
|
|
}
|
|
`,
|
|
[]byte("str"),
|
|
},
|
|
}
|
|
|
|
func TestSliceOperations(t *testing.T) {
|
|
srcBuilder := bytes.NewBuffer([]byte("package testcase\n"))
|
|
for i, tc := range sliceTestCases {
|
|
srcBuilder.WriteString(fmt.Sprintf(tc.src, i))
|
|
}
|
|
|
|
ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, tc := range sliceTestCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
v := vm.New()
|
|
invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
|
|
runAndCheck(t, v, tc.result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestByteSlices(t *testing.T) {
|
|
testCases := []testCase{
|
|
{
|
|
"append bytes bigger than 0x79",
|
|
`package foo
|
|
func Main() []byte {
|
|
var z []byte
|
|
z = append(z, 0x78, 0x79, 0x80, 0x81, 0xFF)
|
|
return z
|
|
}`,
|
|
[]byte{0x78, 0x79, 0x80, 0x81, 0xFF},
|
|
},
|
|
{
|
|
"append bytes bigger than 0x79, not nil",
|
|
`package foo
|
|
func Main() []byte {
|
|
z := []byte{0x78}
|
|
z = append(z, 0x79, 0x80, 0x81, 0xFF)
|
|
return z
|
|
}`,
|
|
[]byte{0x78, 0x79, 0x80, 0x81, 0xFF},
|
|
},
|
|
{
|
|
"append bytes bigger than 0x79, function return",
|
|
`package foo
|
|
func getByte() byte { return 0x80 }
|
|
func Main() []byte {
|
|
var z []byte
|
|
z = append(z, 0x78, 0x79, getByte(), 0x81, 0xFF)
|
|
return z
|
|
}`,
|
|
[]byte{0x78, 0x79, 0x80, 0x81, 0xFF},
|
|
},
|
|
{
|
|
"append ints bigger than 0x79, function return byte",
|
|
`package foo
|
|
func getByte() byte { return 0x80 }
|
|
func Main() []int {
|
|
var z []int
|
|
z = append(z, 0x78, int(getByte()))
|
|
return z
|
|
}`,
|
|
[]stackitem.Item{stackitem.Make(0x78), stackitem.Make(0x80)},
|
|
},
|
|
{
|
|
"slice literal, bytes bigger than 0x79, function return",
|
|
`package foo
|
|
func getByte() byte { return 0x80 }
|
|
func Main() []byte {
|
|
z := []byte{0x79, getByte(), 0x81}
|
|
return z
|
|
}`,
|
|
[]byte{0x79, 0x80, 0x81},
|
|
},
|
|
{
|
|
"compare bytes as integers",
|
|
`package foo
|
|
func getByte1() byte { return 0x79 }
|
|
func getByte2() byte { return 0x80 }
|
|
func Main() bool {
|
|
return getByte1() < getByte2()
|
|
}`,
|
|
true,
|
|
},
|
|
}
|
|
runTestCases(t, testCases)
|
|
}
|
|
|
|
func TestSubsliceCompound(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []int {
|
|
a := []int{0, 1, 2, 3}
|
|
b := a[1:3]
|
|
return b
|
|
}`
|
|
_, err := compiler.Compile("", strings.NewReader(src))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestSubsliceFromStructField(t *testing.T) {
|
|
src := `package foo
|
|
type pair struct { key, value []byte }
|
|
func Main() []byte {
|
|
p := pair{ []byte{1}, []byte{4, 8, 15, 16, 23, 42} }
|
|
return p.value[2:4]
|
|
}`
|
|
eval(t, src, []byte{15, 16})
|
|
}
|
|
|
|
func TestRemove(t *testing.T) {
|
|
t.Run("Valid", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
func Main() int {
|
|
a := []int{11, 22, 33}
|
|
util.Remove(a, 1)
|
|
return len(a) + a[0] + a[1]
|
|
}`
|
|
eval(t, src, big.NewInt(46))
|
|
})
|
|
t.Run("ByteSlice", func(t *testing.T) {
|
|
// This test checks that `Remove` has correct arguments.
|
|
// After `Remove` became an opcode it is harder to do such checks.
|
|
// Skip the test for now.
|
|
t.Skip()
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
func Main() int {
|
|
a := []byte{11, 22, 33}
|
|
util.Remove(a, 1)
|
|
return len(a)
|
|
}`
|
|
_, err := compiler.Compile("", strings.NewReader(src))
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestJumps(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() []byte {
|
|
buf := []byte{0x62, 0x01, 0x00}
|
|
return buf
|
|
}
|
|
`
|
|
eval(t, src, []byte{0x62, 0x01, 0x00})
|
|
}
|
|
|
|
func TestMake(t *testing.T) {
|
|
t.Run("Map", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
a := make(map[int]int)
|
|
a[1] = 10
|
|
a[2] = 20
|
|
return a[1]
|
|
}`
|
|
eval(t, src, big.NewInt(10))
|
|
})
|
|
t.Run("IntSlice", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
a := make([]int, 10)
|
|
return len(a) + a[0]
|
|
}`
|
|
eval(t, src, big.NewInt(10))
|
|
})
|
|
t.Run("ByteSlice", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
a := make([]byte, 10)
|
|
return len(a) + int(a[0])
|
|
}`
|
|
eval(t, src, big.NewInt(10))
|
|
})
|
|
t.Run("CapacityError", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
a := make([]int, 1, 2)
|
|
return a[0]
|
|
}`
|
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestCopy(t *testing.T) {
|
|
t.Run("Invalid", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []int {
|
|
src := []int{3, 2, 1}
|
|
dst := make([]int, 2)
|
|
copy(dst, src)
|
|
return dst
|
|
}`
|
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
|
require.Error(t, err)
|
|
})
|
|
t.Run("Simple", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []byte {
|
|
src := []byte{3, 2, 1}
|
|
dst := make([]byte, 2)
|
|
copy(dst, src)
|
|
return dst
|
|
}`
|
|
eval(t, src, []byte{3, 2})
|
|
})
|
|
t.Run("LowSrcIndex", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []byte {
|
|
src := []byte{3, 2, 1}
|
|
dst := make([]byte, 2)
|
|
copy(dst, src[1:])
|
|
return dst
|
|
}`
|
|
eval(t, src, []byte{2, 1})
|
|
})
|
|
t.Run("LowDstIndex", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []byte {
|
|
src := []byte{3, 2, 1}
|
|
dst := make([]byte, 2)
|
|
copy(dst[1:], src[1:])
|
|
return dst
|
|
}`
|
|
eval(t, src, []byte{0, 2})
|
|
})
|
|
t.Run("BothIndices", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []byte {
|
|
src := []byte{4, 3, 2, 1}
|
|
dst := make([]byte, 4)
|
|
copy(dst[1:], src[1:3])
|
|
return dst
|
|
}`
|
|
eval(t, src, []byte{0, 3, 2, 0})
|
|
})
|
|
t.Run("EmptySliceExpr", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() []byte {
|
|
src := []byte{3, 2, 1}
|
|
dst := make([]byte, 2)
|
|
copy(dst[1:], src[:])
|
|
return dst
|
|
}`
|
|
eval(t, src, []byte{0, 3})
|
|
})
|
|
t.Run("AssignToVariable", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
src := []byte{3, 2, 1}
|
|
dst := make([]byte, 2)
|
|
n := copy(dst, src)
|
|
return n
|
|
}`
|
|
eval(t, src, big.NewInt(2))
|
|
})
|
|
}
|