forked from TrueCloudLab/neoneo-go
Merge pull request #1024 from nspcc-dev/fix/interop
compiler: process interop together with package
This commit is contained in:
commit
7ddcc35daf
6 changed files with 98 additions and 22 deletions
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
9
pkg/compiler/testdata/block/block.go
vendored
Normal 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
6
pkg/compiler/testdata/util/equals.go
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
// Equals is a mirror of `Equals` builtin.
|
||||||
|
func Equals(a, b interface{}) bool {
|
||||||
|
return true
|
||||||
|
}
|
Loading…
Reference in a new issue