Merge pull request #1024 from nspcc-dev/fix/interop

compiler: process interop together with package
This commit is contained in:
Roman Khimov 2020-06-09 14:37:22 +03:00 committed by GitHub
commit 7ddcc35daf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 22 deletions

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"go/ast" "go/ast"
"go/types" "go/types"
"strings"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -11,12 +12,12 @@ import (
) )
var ( var (
// Go language builtin functions and custom builtin utility functions. // Go language builtin functions.
builtinFuncs = []string{ goBuiltins = []string{"len", "append", "panic"}
"len", "append", "SHA256", // Custom builtin utility functions.
"AppCall", customBuiltins = []string{
"SHA256", "AppCall",
"FromAddress", "Equals", "FromAddress", "Equals",
"panic",
"ToBool", "ToByteArray", "ToInteger", "ToBool", "ToByteArray", "ToInteger",
} }
) )
@ -134,20 +135,21 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
return usage return usage
} }
func isBuiltin(expr ast.Expr) bool { func isGoBuiltin(name string) bool {
var name string for i := range goBuiltins {
if name == goBuiltins[i] {
return true
}
}
return false
}
switch t := expr.(type) { func isCustomBuiltin(f *funcScope) bool {
case *ast.Ident: if !isInteropPath(f.pkg.Path()) {
name = t.Name
case *ast.SelectorExpr:
name = t.Sel.Name
default:
return false return false
} }
for _, n := range customBuiltins {
for _, n := range builtinFuncs { if f.name == n {
if name == n {
return true return true
} }
} }
@ -155,9 +157,13 @@ func isBuiltin(expr ast.Expr) bool {
} }
func isSyscall(fun *funcScope) bool { func isSyscall(fun *funcScope) bool {
if fun.selector == nil { if fun.selector == nil || fun.pkg == nil || !isInteropPath(fun.pkg.Path()) {
return false return false
} }
_, ok := syscalls[fun.selector.Name][fun.name] _, ok := syscalls[fun.selector.Name][fun.name]
return ok 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] f, ok = c.funcs[decl.Name.Name]
if ok { if ok {
// If this function is a syscall we will not convert it to bytecode. // If this function is a syscall or builtin we will not convert it to bytecode.
if isSyscall(f) { if isSyscall(f) || isCustomBuiltin(f) {
return return
} }
c.setLabel(f.label) c.setLabel(f.label)
@ -691,12 +691,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ok bool ok bool
name string name string
numArgs = len(n.Args) numArgs = len(n.Args)
isBuiltin = isBuiltin(n.Fun) isBuiltin bool
) )
switch fun := n.Fun.(type) { switch fun := n.Fun.(type) {
case *ast.Ident: case *ast.Ident:
f, ok = c.funcs[fun.Name] f, ok = c.funcs[fun.Name]
isBuiltin = isGoBuiltin(fun.Name)
if !ok && !isBuiltin { if !ok && !isBuiltin {
name = fun.Name 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) c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Sel.Name)
return nil return nil
} }
isBuiltin = isCustomBuiltin(f)
case *ast.ArrayType: case *ast.ArrayType:
// For now we will assume that there are only byte slice conversions. // For now we will assume that there are only byte slice conversions.
// E.g. []byte("foobar") or []byte(scriptHash). // 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. // Bring all imported functions into scope.
for _, pkg := range info.program.AllPackages { for _, pkg := range info.program.AllPackages {
for _, f := range pkg.Files { 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 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 { for _, decl := range f.Decls {
switch n := decl.(type) { switch n := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if n.Name.Name != mainIdent { if n.Name.Name != mainIdent {
c.newFunc(n) c.newFunc(n)
c.funcs[n.Name.Name].pkg = pkg
} }
} }
} }

View file

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

View file

@ -2,6 +2,7 @@ package compiler_test
import ( import (
"fmt" "fmt"
"math/big"
"strings" "strings"
"testing" "testing"
@ -15,6 +16,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "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/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "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
}