compiler: process interop together with package

Closes #913.
Provide package info in the funcScope to check if function is defined
insided an interop package. As a good side-effect bytecode for builtins from `util`
is no longer emitted.

Related #941.
This commit is contained in:
Evgenii Stratonikov 2020-06-08 14:16:41 +03:00
parent 795523f5cd
commit ed45ff98e3
6 changed files with 98 additions and 22 deletions

View file

@ -4,6 +4,7 @@ import (
"errors"
"go/ast"
"go/types"
"strings"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -11,12 +12,12 @@ import (
)
var (
// Go language builtin functions and custom builtin utility functions.
builtinFuncs = []string{
"len", "append", "SHA256",
"AppCall",
// Go language builtin functions.
goBuiltins = []string{"len", "append", "panic"}
// Custom builtin utility functions.
customBuiltins = []string{
"SHA256", "AppCall",
"FromAddress", "Equals",
"panic",
"ToBool", "ToByteArray", "ToInteger",
}
)
@ -134,20 +135,21 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
return usage
}
func isBuiltin(expr ast.Expr) bool {
var name string
func isGoBuiltin(name string) bool {
for i := range goBuiltins {
if name == goBuiltins[i] {
return true
}
}
return false
}
switch t := expr.(type) {
case *ast.Ident:
name = t.Name
case *ast.SelectorExpr:
name = t.Sel.Name
default:
func isCustomBuiltin(f *funcScope) bool {
if !isInteropPath(f.pkg.Path()) {
return false
}
for _, n := range builtinFuncs {
if name == n {
for _, n := range customBuiltins {
if f.name == n {
return true
}
}
@ -155,9 +157,13 @@ func isBuiltin(expr ast.Expr) bool {
}
func isSyscall(fun *funcScope) bool {
if fun.selector == nil {
if fun.selector == nil || fun.pkg == nil || !isInteropPath(fun.pkg.Path()) {
return false
}
_, ok := syscalls[fun.selector.Name][fun.name]
return ok
}
func isInteropPath(s string) bool {
return strings.HasPrefix(s, "github.com/nspcc-dev/neo-go/pkg/interop")
}

View file

@ -281,8 +281,8 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
f, ok = c.funcs[decl.Name.Name]
if ok {
// If this function is a syscall we will not convert it to bytecode.
if isSyscall(f) {
// If this function is a syscall or builtin we will not convert it to bytecode.
if isSyscall(f) || isCustomBuiltin(f) {
return
}
c.setLabel(f.label)
@ -691,12 +691,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ok bool
name string
numArgs = len(n.Args)
isBuiltin = isBuiltin(n.Fun)
isBuiltin bool
)
switch fun := n.Fun.(type) {
case *ast.Ident:
f, ok = c.funcs[fun.Name]
isBuiltin = isGoBuiltin(fun.Name)
if !ok && !isBuiltin {
name = fun.Name
}
@ -717,6 +718,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Sel.Name)
return nil
}
isBuiltin = isCustomBuiltin(f)
case *ast.ArrayType:
// For now we will assume that there are only byte slice conversions.
// E.g. []byte("foobar") or []byte(scriptHash).
@ -1309,7 +1311,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
// Bring all imported functions into scope.
for _, pkg := range info.program.AllPackages {
for _, f := range pkg.Files {
c.resolveFuncDecls(f)
c.resolveFuncDecls(f, pkg.Pkg)
}
}
@ -1378,12 +1380,13 @@ func CodeGen(info *buildInfo) ([]byte, *DebugInfo, error) {
return buf, c.emitDebugInfo(), nil
}
func (c *codegen) resolveFuncDecls(f *ast.File) {
func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
for _, decl := range f.Decls {
switch n := decl.(type) {
case *ast.FuncDecl:
if n.Name.Name != mainIdent {
c.newFunc(n)
c.funcs[n.Name.Name].pkg = pkg
}
}
}

View file

@ -3,6 +3,7 @@ package compiler
import (
"go/ast"
"go/token"
"go/types"
)
// A funcScope represents the scope within the function context.
@ -18,6 +19,9 @@ type funcScope struct {
// The declaration of the function in the AST. Nil if this scope is not a function.
decl *ast.FuncDecl
// Package where the function is defined.
pkg *types.Package
// Program label of the scope
label uint16

View file

@ -2,6 +2,7 @@ package compiler_test
import (
"fmt"
"math/big"
"strings"
"testing"
@ -15,6 +16,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
@ -137,3 +139,49 @@ func getAppCallScript(h string) string {
}
`
}
func TestBuiltinDoesNotCompile(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() bool {
a := 1
b := 2
return util.Equals(a, b)
}`
v := vmAndCompile(t, src)
ctx := v.Context()
retCount := 0
for op, _, err := ctx.Next(); err == nil; op, _, err = ctx.Next() {
if ctx.IP() > len(ctx.Program()) {
break
}
if op == opcode.RET {
retCount++
}
}
require.Equal(t, 1, retCount)
}
func TestInteropPackage(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/block"
func Main() int {
b := block.Block{}
a := block.GetTransactionCount(b)
return a
}`
eval(t, src, big.NewInt(42))
}
func TestBuiltinPackage(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/util"
func Main() int {
if util.Equals(1, 2) { // always returns true
return 1
}
return 2
}`
eval(t, src, big.NewInt(1))
}

9
pkg/compiler/testdata/block/block.go vendored Normal file
View file

@ -0,0 +1,9 @@
package block
// Block is opaque type.
type Block struct{}
// GetTransactionCount is a mirror of `GetTransactionCount` interop.
func GetTransactionCount(b Block) int {
return 42
}

6
pkg/compiler/testdata/util/equals.go vendored Normal file
View file

@ -0,0 +1,6 @@
package util
// Equals is a mirror of `Equals` builtin.
func Equals(a, b interface{}) bool {
return true
}