mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-11 15:30:07 +00:00
16dbb35bd8
An attempt to compile the following code leads to runtime panic: ``` package foo type CustomInt int func Main() int { var i CustomInt i = 5 return i.Do(2) } func (CustomInt) Do(arg int) int { return arg } ``` The panic: ``` panic: runtime error: index out of range [0] with length 0 [recovered] panic: runtime error: index out of range [0] with length 0 goroutine 22 [running]: testing.tRunner.func1.2({0xa341c0, 0xc0001606d8}) /usr/local/go/src/testing/testing.go:1209 +0x24e testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1212 +0x218 panic({0xa341c0, 0xc0001606d8}) /usr/local/go/src/runtime/panic.go:1038 +0x215 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertFuncDecl(0xc00015e3c0, {0xc753b8, 0xc000152c80}, 0xc000266300, 0x30) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:497 +0x10b3 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile.func2(0xc000152c80, 0xc00023c410) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2153 +0x3f8 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachFile.func1(0xc000229b80) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:102 +0x82 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachPackage(0xc00015e3c0, 0xc000189bb0) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:93 +0xc6 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachFile(0x999a20, 0xc000130d80) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:99 +0x45 github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile(0xc00015e3c0, 0xc0002669f0, 0x1) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2140 +0x445 github.com/nspcc-dev/neo-go/pkg/compiler.codeGen(0xc0002669f0) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2191 +0x353 github.com/nspcc-dev/neo-go/pkg/compiler.CompileWithOptions({0xa6f39a, 0x50b6b3}, {0xc6d1a0, 0xc0002421e0}, 0x0) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:218 +0x65 github.com/nspcc-dev/neo-go/pkg/compiler_test.vmAndCompileInterop(0x5648df, {0xa9bf23, 0x94}) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:75 +0x113 github.com/nspcc-dev/neo-go/pkg/compiler_test.eval(0xc0002421c0, {0xa9bf23, 0x61be8c7}, {0xa68880, 0xc0002421c0}) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:36 +0x2d github.com/nspcc-dev/neo-go/pkg/compiler_test.TestUnnamedMethodReceiver(0x4079f9) /home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/function_call_test.go:400 +0x4f testing.tRunner(0xc000204b60, 0xbcebb0) /usr/local/go/src/testing/testing.go:1259 +0x102 created by testing.(*T).Run /usr/local/go/src/testing/testing.go:1306 +0x35a ``` The solution is to use the same approach as for unnamed function parameters handling introduced in #2204. (c *funcScope).newVariable is able to properly handle "_" receiver.
401 lines
7.9 KiB
Go
401 lines
7.9 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 TestReturnValueReceiver(t *testing.T) {
|
|
t.Run("regular", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/method"
|
|
|
|
func Main() int {
|
|
return method.NewX().GetA()
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("inline", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
|
|
|
|
func Main() int {
|
|
return inline.NewT().GetN()
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
}
|
|
|
|
func TestSimpleFunctionCall(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := 10
|
|
y := getSomeInteger()
|
|
return x + y
|
|
}
|
|
|
|
func getSomeInteger() int {
|
|
x := 10
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(20))
|
|
}
|
|
|
|
func TestNotAssignedFunctionCall(t *testing.T) {
|
|
t.Run("Simple", func(t *testing.T) {
|
|
src := `package testcase
|
|
func Main() int {
|
|
getSomeInteger()
|
|
getSomeInteger()
|
|
return 0
|
|
}
|
|
|
|
func getSomeInteger() int {
|
|
return 0
|
|
}`
|
|
eval(t, src, big.NewInt(0))
|
|
})
|
|
t.Run("If", func(t *testing.T) {
|
|
src := `package testcase
|
|
func f() bool { return true }
|
|
func Main() int {
|
|
if f() {
|
|
return 42
|
|
}
|
|
return 0
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("Switch", func(t *testing.T) {
|
|
src := `package testcase
|
|
func f() bool { return true }
|
|
func Main() int {
|
|
switch true {
|
|
case f():
|
|
return 42
|
|
default:
|
|
return 0
|
|
}
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("Builtin", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
func Main() int {
|
|
util.FromAddress("NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8")
|
|
util.FromAddress("NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8")
|
|
return 1
|
|
}`
|
|
eval(t, src, big.NewInt(1))
|
|
})
|
|
t.Run("Lambda", func(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
f := func() (int, int) { return 1, 2 }
|
|
f()
|
|
f()
|
|
return 42
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("VarDecl", func(t *testing.T) {
|
|
src := `package foo
|
|
func foo() []int { return []int{1} }
|
|
func Main() int {
|
|
var x = foo()
|
|
return len(x)
|
|
}`
|
|
eval(t, src, big.NewInt(1))
|
|
})
|
|
}
|
|
|
|
func TestMultipleFunctionCalls(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := 10
|
|
y := getSomeInteger()
|
|
return x + y
|
|
}
|
|
|
|
func getSomeInteger() int {
|
|
x := 10
|
|
y := getSomeOtherInt()
|
|
return x + y
|
|
}
|
|
|
|
func getSomeOtherInt() int {
|
|
x := 8
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(28))
|
|
}
|
|
|
|
func TestFunctionCallWithArgs(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := 10
|
|
y := getSomeInteger(x)
|
|
return y
|
|
}
|
|
|
|
func getSomeInteger(x int) int {
|
|
y := 8
|
|
return x + y
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(18))
|
|
}
|
|
|
|
func TestFunctionCallWithInterfaceType(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() interface{} {
|
|
x := getSomeInteger(10)
|
|
return x
|
|
}
|
|
|
|
func getSomeInteger(x interface{}) interface{} {
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(10))
|
|
}
|
|
|
|
func TestFunctionCallMultiArg(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := addIntegers(2, 4)
|
|
return x
|
|
}
|
|
|
|
func addIntegers(x int, y int) int {
|
|
return x + y
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(6))
|
|
}
|
|
|
|
func TestFunctionWithVoidReturn(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := 2
|
|
getSomeInteger()
|
|
y := 4
|
|
return x + y
|
|
}
|
|
|
|
func getSomeInteger() { %s }
|
|
`
|
|
t.Run("EmptyBody", func(t *testing.T) {
|
|
src := fmt.Sprintf(src, "")
|
|
eval(t, src, big.NewInt(6))
|
|
})
|
|
t.Run("SingleReturn", func(t *testing.T) {
|
|
src := fmt.Sprintf(src, "return")
|
|
eval(t, src, big.NewInt(6))
|
|
})
|
|
}
|
|
|
|
func TestFunctionWithVoidReturnBranch(t *testing.T) {
|
|
src := `
|
|
package testcase
|
|
func Main() int {
|
|
x := %t
|
|
f(x)
|
|
return 2
|
|
}
|
|
|
|
func f(x bool) {
|
|
if x {
|
|
return
|
|
}
|
|
}
|
|
`
|
|
t.Run("ReturnBranch", func(t *testing.T) {
|
|
src := fmt.Sprintf(src, true)
|
|
eval(t, src, big.NewInt(2))
|
|
})
|
|
t.Run("NoReturn", func(t *testing.T) {
|
|
src := fmt.Sprintf(src, false)
|
|
eval(t, src, big.NewInt(2))
|
|
})
|
|
}
|
|
|
|
func TestFunctionWithMultipleArgumentNames(t *testing.T) {
|
|
src := `package foo
|
|
func Main() int {
|
|
return add(1, 2)
|
|
}
|
|
func add(a, b int) int {
|
|
return a + b
|
|
}`
|
|
eval(t, src, big.NewInt(3))
|
|
}
|
|
|
|
func TestLocalsCount(t *testing.T) {
|
|
src := `package foo
|
|
func f(a, b, c int) int {
|
|
sum := a
|
|
for i := 0; i < c; i++ {
|
|
sum += b
|
|
}
|
|
return sum
|
|
}
|
|
func Main() int {
|
|
return f(1, 2, 3)
|
|
}`
|
|
eval(t, src, big.NewInt(7))
|
|
}
|
|
|
|
func TestVariadic(t *testing.T) {
|
|
srcTmpl := `package foo
|
|
func someFunc(a int, b ...int) int {
|
|
sum := a
|
|
for i := range b {
|
|
sum = sum - b[i]
|
|
}
|
|
return sum
|
|
}
|
|
func Main() int {
|
|
%s
|
|
return someFunc(10, %s)
|
|
}`
|
|
t.Run("Elements", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, "", "1, 2, 3")
|
|
eval(t, src, big.NewInt(4))
|
|
})
|
|
t.Run("Slice", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, "a := []int{1, 2, 3}", "a...")
|
|
eval(t, src, big.NewInt(4))
|
|
})
|
|
t.Run("Literal", func(t *testing.T) {
|
|
src := fmt.Sprintf(srcTmpl, "", "[]int{1, 2, 3}...")
|
|
eval(t, src, big.NewInt(4))
|
|
})
|
|
}
|
|
|
|
func TestVariadicMethod(t *testing.T) {
|
|
src := `package foo
|
|
type myInt int
|
|
func (x myInt) someFunc(a int, b ...int) int {
|
|
sum := int(x) + a
|
|
for i := range b {
|
|
sum = sum - b[i]
|
|
}
|
|
return sum
|
|
}
|
|
func Main() int {
|
|
x := myInt(38)
|
|
return x.someFunc(10, 1, 2, 3)
|
|
}`
|
|
eval(t, src, big.NewInt(42))
|
|
}
|
|
|
|
func TestJumpOptimize(t *testing.T) {
|
|
src := `package foo
|
|
func init() {
|
|
if true {} else {}
|
|
var a int
|
|
_ = a
|
|
}
|
|
func _deploy(_ interface{}, upd bool) {
|
|
if true {} else {}
|
|
t := upd
|
|
_ = t
|
|
}
|
|
func Get1() int { return 1 }
|
|
func Get2() int { Get1(); Get1(); Get1(); Get1(); return Get1() }
|
|
func Get3() int { return Get2() }
|
|
func Main() int {
|
|
return Get3()
|
|
}`
|
|
b, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(src), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 6, len(di.Methods))
|
|
for _, mi := range di.Methods {
|
|
// only _deploy and init have locals here
|
|
if mi.Name.Name == "_deploy" || mi.Name.Name == "init" {
|
|
require.Equal(t, b.Script[mi.Range.Start], byte(opcode.INITSLOT))
|
|
}
|
|
require.Equal(t, b.Script[mi.Range.End], byte(opcode.RET))
|
|
}
|
|
}
|
|
|
|
func TestFunctionUnusedParameters(t *testing.T) {
|
|
src := `package foo
|
|
func add13(a int, _ int, _1 int, _ int) int {
|
|
return a + _1
|
|
}
|
|
func Main() int {
|
|
return add13(1, 10, 100, 1000)
|
|
}`
|
|
eval(t, src, big.NewInt(101))
|
|
}
|
|
|
|
func TestUnusedFunctions(t *testing.T) {
|
|
t.Run("only variable", func(t *testing.T) {
|
|
src := `package foo
|
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall"
|
|
func Main() int {
|
|
return nestedcall.X
|
|
}`
|
|
|
|
b, err := compiler.Compile("foo.go", strings.NewReader(src))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, len(b)) // PUSHINT8 (42) + RET
|
|
eval(t, src, big.NewInt(42))
|
|
})
|
|
t.Run("imported function", func(t *testing.T) {
|
|
// Check that import map is set correctly during package traversal.
|
|
src := `package foo
|
|
import inner "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall"
|
|
func Main() int {
|
|
return inner.N()
|
|
}`
|
|
|
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
|
require.NoError(t, err)
|
|
eval(t, src, big.NewInt(65))
|
|
})
|
|
t.Run("method inside of an imported package", func(t *testing.T) {
|
|
// Check that import map is set correctly during package traversal.
|
|
src := `package foo
|
|
import inner "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall"
|
|
func Main() int {
|
|
var t inner.Token
|
|
return t.Method()
|
|
}`
|
|
|
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
|
require.NoError(t, err)
|
|
eval(t, src, big.NewInt(2231))
|
|
})
|
|
}
|
|
|
|
func TestUnnamedMethodReceiver(t *testing.T) {
|
|
src := `package foo
|
|
type CustomInt int
|
|
func Main() int {
|
|
var i CustomInt
|
|
i = 5
|
|
return i.Do(2)
|
|
}
|
|
func (CustomInt) Do(arg int) int {
|
|
return arg
|
|
}`
|
|
eval(t, src, big.NewInt(2))
|
|
}
|