neo-go/pkg/compiler/function_call_test.go
Evgeniy Stratonikov 0bc81aecf4 compiler: do not emit code for unused imported functions
Our current algorithm marks function as used if it is called
at least ones, even if the callee function is itself unused.
This commit implements more clever traversal to collect usage
information more precisely.

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-10-13 15:56:07 +03:00

353 lines
6.7 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 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.CompileWithDebugInfo("", strings.NewReader(src))
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[mi.Range.Start], byte(opcode.INITSLOT))
}
require.Equal(t, b[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", 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", strings.NewReader(src))
require.NoError(t, err)
eval(t, src, big.NewInt(65))
})
}