neo-go/pkg/compiler/compiler_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

344 lines
11 KiB
Go

package compiler_test
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
const examplePath = "../../examples"
const exampleCompilePath = "testdata/compile"
const exampleSavePath = exampleCompilePath + "/save"
type compilerTestCase struct {
name string
function func(*testing.T)
}
func TestCompiler(t *testing.T) {
// CompileAndSave use config.Version for proper .nef generation.
config.Version = "0.90.0-test"
testCases := []compilerTestCase{
{
name: "TestCompileDirectory",
function: func(t *testing.T) {
const multiMainDir = "testdata/multi"
_, di, err := compiler.CompileWithDebugInfo(multiMainDir, nil)
require.NoError(t, err)
m := map[string]bool{}
for i := range di.Methods {
m[di.Methods[i].ID] = true
}
require.Contains(t, m, "Func1")
require.Contains(t, m, "Func2")
},
},
{
name: "TestCompile",
function: func(t *testing.T) {
infos, err := ioutil.ReadDir(examplePath)
require.NoError(t, err)
for _, info := range infos {
if !info.IsDir() {
// example smart contracts are located in the `examplePath` subdirectories, but
// there are also a couple of files inside the `examplePath` which doesn't need to be compiled
continue
}
targetPath := path.Join(examplePath, info.Name())
require.NoError(t, compileFile(targetPath))
}
},
},
{
name: "TestCompileAndSave",
function: func(t *testing.T) {
infos, err := ioutil.ReadDir(exampleCompilePath)
require.NoError(t, err)
err = os.MkdirAll(exampleSavePath, os.ModePerm)
require.NoError(t, err)
t.Cleanup(func() {
err := os.RemoveAll(exampleSavePath)
require.NoError(t, err)
})
outfile := exampleSavePath + "/test.nef"
_, err = compiler.CompileAndSave(exampleCompilePath+"/"+infos[0].Name(), &compiler.Options{Outfile: outfile})
require.NoError(t, err)
},
},
}
for _, tcase := range testCases {
t.Run(tcase.name, tcase.function)
}
}
func compileFile(src string) error {
_, err := compiler.Compile(src, nil)
return err
}
func TestOnPayableChecks(t *testing.T) {
compileAndCheck := func(t *testing.T, src string) error {
_, di, err := compiler.CompileWithDebugInfo("payable", strings.NewReader(src))
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{})
return err
}
t.Run("NEP-11, good", func(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP11Payment(from interop.Hash160, amount int, tokenID []byte, data interface{}) {}`
require.NoError(t, compileAndCheck(t, src))
})
t.Run("NEP-11, bad", func(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP11Payment(from interop.Hash160, amount int, oldParam string, tokenID []byte, data interface{}) {}`
require.Error(t, compileAndCheck(t, src))
})
t.Run("NEP-17, good", func(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {}`
require.NoError(t, compileAndCheck(t, src))
})
t.Run("NEP-17, bad", func(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}, extra int) {}`
require.Error(t, compileAndCheck(t, src))
})
}
func TestSafeMethodWarnings(t *testing.T) {
src := `package payable
func Main() int { return 1 }`
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{SafeMethods: []string{"main"}})
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{SafeMethods: []string{"main", "mississippi"}})
require.Error(t, err)
}
func TestEventWarnings(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main() { runtime.Notify("Event", 1) }`
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
require.NoError(t, err)
t.Run("event it missing from config", func(t *testing.T) {
_, err = compiler.CreateManifest(di, &compiler.Options{})
require.Error(t, err)
t.Run("suppress", func(t *testing.T) {
_, err = compiler.CreateManifest(di, &compiler.Options{NoEventsCheck: true})
require.NoError(t, err)
})
})
t.Run("wrong parameter number", func(t *testing.T) {
_, err = compiler.CreateManifest(di, &compiler.Options{
ContractEvents: []manifest.Event{{Name: "Event"}},
})
require.Error(t, err)
})
t.Run("wrong parameter type", func(t *testing.T) {
_, err = compiler.CreateManifest(di, &compiler.Options{
ContractEvents: []manifest.Event{{
Name: "Event",
Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.StringType)},
}},
})
require.Error(t, err)
})
t.Run("good", func(t *testing.T) {
_, err = compiler.CreateManifest(di, &compiler.Options{
ContractEvents: []manifest.Event{{
Name: "Event",
Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)},
}},
})
require.NoError(t, err)
})
t.Run("event in imported package", func(t *testing.T) {
t.Run("unused", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify"
func Main() int {
return notify.Value
}`
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{NoEventsCheck: true})
require.NoError(t, err)
})
t.Run("used", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify"
func Main() int {
notify.EmitEvent()
return 42
}`
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{})
require.Error(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{
ContractEvents: []manifest.Event{{Name: "Event"}},
})
require.NoError(t, err)
})
})
}
func TestNotifyInVerify(t *testing.T) {
srcTmpl := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Verify() bool { runtime.%s("Event"); return true }`
for _, name := range []string{"Notify", "Log"} {
t.Run(name, func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, name)
_, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src),
&compiler.Options{ContractEvents: []manifest.Event{{Name: "Event"}}})
require.Error(t, err)
t.Run("suppress", func(t *testing.T) {
_, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src),
&compiler.Options{NoEventsCheck: true})
require.NoError(t, err)
})
})
}
}
func TestInvokedContractsPermissons(t *testing.T) {
testCompile := func(t *testing.T, di *compiler.DebugInfo, disable bool, ps ...manifest.Permission) error {
o := &compiler.Options{
NoPermissionsCheck: disable,
Permissions: ps,
}
_, err := compiler.CreateManifest(di, o)
return err
}
t.Run("native", func(t *testing.T) {
src := `package test
import "github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
import "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
func Main() int {
neo.Transfer(nil, nil, 10, nil)
management.GetContract(nil) // skip read-only
return 0
}`
_, di, err := compiler.CompileWithOptions("permissionTest", strings.NewReader(src), &compiler.Options{})
require.NoError(t, err)
var nh util.Uint160
p := manifest.NewPermission(manifest.PermissionHash, nh)
require.Error(t, testCompile(t, di, false, *p))
require.NoError(t, testCompile(t, di, true, *p))
copy(nh[:], neo.Hash)
p.Contract.Value = nh
require.NoError(t, testCompile(t, di, false, *p))
p.Methods.Restrict()
require.Error(t, testCompile(t, di, false, *p))
require.NoError(t, testCompile(t, di, true, *p))
})
t.Run("custom", func(t *testing.T) {
hashStr := "aaaaaaaaaaaaaaaaaaaa"
src := fmt.Sprintf(`package test
import "github.com/nspcc-dev/neo-go/pkg/interop/contract"
import "github.com/nspcc-dev/neo-go/pkg/interop"
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/runh"
const hash = "%s"
var runtimeHash interop.Hash160
var runtimeMethod string
func invoke(h string) interop.Hash160 { return nil }
func Main() {
contract.Call(interop.Hash160(hash), "method1", contract.All)
contract.Call(interop.Hash160(hash), "method2", contract.All)
contract.Call(interop.Hash160(hash), "method2", contract.All)
// skip read-only
contract.Call(interop.Hash160(hash), "method3", contract.ReadStates)
// skip this
contract.Call(interop.Hash160(hash), runtimeMethod, contract.All)
contract.Call(runtimeHash, "someMethod", contract.All)
contract.Call(interop.Hash160(runtimeHash), "someMethod", contract.All)
contract.Call(runh.RuntimeHash(), "method4", contract.All)
}`, hashStr)
_, di, err := compiler.CompileWithOptions("permissionTest", strings.NewReader(src), &compiler.Options{})
require.NoError(t, err)
var h util.Uint160
copy(h[:], hashStr)
p := manifest.NewPermission(manifest.PermissionHash, h)
require.NoError(t, testCompile(t, di, false, *p))
p.Methods.Add("method1")
require.Error(t, testCompile(t, di, false, *p))
require.NoError(t, testCompile(t, di, true, *p))
pr := manifest.NewPermission(manifest.PermissionHash, random.Uint160())
pr.Methods.Add("someMethod")
pr.Methods.Add("method4")
t.Run("wildcard", func(t *testing.T) {
pw := manifest.NewPermission(manifest.PermissionWildcard)
require.NoError(t, testCompile(t, di, false, *p, *pw))
pw.Methods.Add("method2")
require.Error(t, testCompile(t, di, false, *p, *pw))
require.NoError(t, testCompile(t, di, false, *p, *pw, *pr))
})
t.Run("group", func(t *testing.T) {
priv, _ := keys.NewPrivateKey()
pw := manifest.NewPermission(manifest.PermissionGroup, priv.PublicKey())
require.NoError(t, testCompile(t, di, false, *p, *pw))
pw.Methods.Add("invalid")
require.Error(t, testCompile(t, di, false, *p, *pw, *pr))
pw.Methods.Add("method2")
require.Error(t, testCompile(t, di, false, *p, *pw))
require.NoError(t, testCompile(t, di, false, *p, *pw, *pr))
})
})
}