From a781d299e097b6efe36576de41d2b8498a049e5d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 29 Jul 2020 17:20:00 +0300 Subject: [PATCH] compiler: use fully-qualified names for tracking functions Function name now consists of 3 parts: 1) full package path 2) method receiver type (if any) 3) function name itself . Fix #1150. Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 7 +++--- pkg/compiler/codegen.go | 37 ++++++++++++++++++++++---------- pkg/compiler/compiler.go | 1 + pkg/compiler/debug.go | 2 ++ pkg/compiler/func_scope.go | 11 +++++++++- pkg/compiler/global_test.go | 30 ++++++++++++++++++++++++++ pkg/compiler/testdata/foo/foo.go | 13 +++++++++++ pkg/compiler/vm_test.go | 1 + 8 files changed, 87 insertions(+), 15 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 4a0821267..1c1dab8bd 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -113,14 +113,15 @@ func (c *codegen) analyzeFuncUsage() funcUsage { case *ast.CallExpr: switch t := n.Fun.(type) { case *ast.Ident: - usage[t.Name] = true + usage[c.getIdentName("", t.Name)] = true case *ast.SelectorExpr: - usage[t.Sel.Name] = true + name, _ := c.getFuncNameFromSelector(t) + usage[name] = true } case *ast.FuncDecl: // exported functions are always assumed to be used if isMain && n.Name.IsExported() { - usage[n.Name.Name] = true + usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true } } return true diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 49d9250e1..fc34b7b04 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -68,6 +68,9 @@ type codegen struct { // constMap contains constants from foreign packages. constMap map[string]types.TypeAndValue + // currPkg is current package being processed. + currPkg *types.Package + // mainPkg is a main package metadata. mainPkg *loader.PackageInfo @@ -278,19 +281,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types. ok, isLambda bool ) - f, ok = c.funcs[decl.Name.Name] + f, ok = c.funcs[c.getFuncNameFromDecl("", decl)] if ok { // 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) - } else if f, ok = c.lambda[decl.Name.Name]; ok { + } else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok { isLambda = ok c.setLabel(f.label) } else { f = c.newFunc(decl) - f.pkg = pkg } f.rng.Start = uint16(c.prog.Len()) @@ -738,7 +740,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch fun := n.Fun.(type) { case *ast.Ident: - f, ok = c.funcs[fun.Name] + f, ok = c.funcs[c.getIdentName("", fun.Name)] isBuiltin = isGoBuiltin(fun.Name) if !ok && !isBuiltin { name = fun.Name @@ -751,13 +753,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // If this is a method call we need to walk the AST to load the struct locally. // Otherwise this is a function call from a imported package and we can call it // directly. - if c.typeInfo.Selections[fun] != nil { + name, isMethod := c.getFuncNameFromSelector(fun) + if isMethod { ast.Walk(c, fun.X) // Dont forget to add 1 extra argument when its a method. numArgs++ } - f, ok = c.funcs[fun.Sel.Name] + f, ok = c.funcs[name] // @FIXME this could cause runtime errors. f.selector = fun.X.(*ast.Ident) if !ok { @@ -1431,18 +1434,30 @@ func (c *codegen) convertToken(tok token.Token) { } func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { - f := newFuncScope(decl, c.newLabel()) - c.funcs[f.name] = f + f := c.newFuncScope(decl, c.newLabel()) + c.funcs[c.getFuncNameFromDecl("", decl)] = f return f } +// getFuncNameFromSelector returns fully-qualified function name from the selector expression. +// Second return value is true iff this was a method call, not foreign package call. +func (c *codegen) getFuncNameFromSelector(e *ast.SelectorExpr) (string, bool) { + ident := e.X.(*ast.Ident) + if c.typeInfo.Selections[e] != nil { + typ := c.typeInfo.Types[ident].Type.String() + return c.getIdentName(typ, e.Sel.Name), true + } + return c.getIdentName(ident.Name, e.Sel.Name), false +} + func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) { name := fmt.Sprintf("lambda@%d", u) - c.lambda[name] = newFuncScope(&ast.FuncDecl{ + f := c.newFuncScope(&ast.FuncDecl{ Name: ast.NewIdent(name), Type: lit.Type, Body: lit.Body, }, u) + c.lambda[c.getFuncNameFromDecl("", f.decl)] = f } func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { @@ -1472,7 +1487,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { case *ast.FuncDecl: // Don't convert the function if it's not used. This will save a lot // of bytecode space. - if funUsage.funcUsed(n.Name.Name) { + name := c.getFuncNameFromDecl(pkg.Path(), n) + if funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) { c.convertFuncDecl(f, n, pkg) } } @@ -1519,7 +1535,6 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) { switch n := decl.(type) { case *ast.FuncDecl: c.newFunc(n) - c.funcs[n.Name.Name].pkg = pkg } } } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 0a19a0f5a..af37f0250 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -48,6 +48,7 @@ type buildInfo struct { func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { for _, pkg := range c.buildInfo.program.AllPackages { c.typeInfo = &pkg.Info + c.currPkg = pkg.Pkg for _, f := range pkg.Files { c.fillImportMap(f, pkg.Pkg) fn(f, pkg.Pkg) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 01b1aed64..95bb7677b 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -152,6 +152,8 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu }) } } + ss := strings.Split(name, ".") + name = ss[len(ss)-1] return &MethodDebugInfo{ ID: name, Name: DebugMethodName{ diff --git a/pkg/compiler/func_scope.go b/pkg/compiler/func_scope.go index 2202c120d..01ed1d175 100644 --- a/pkg/compiler/func_scope.go +++ b/pkg/compiler/func_scope.go @@ -45,7 +45,7 @@ type funcScope struct { i int } -func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { +func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { var name string if decl.Name != nil { name = decl.Name.Name @@ -54,6 +54,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { name: name, decl: decl, label: label, + pkg: c.currPkg, vars: newVarScope(), voidCalls: map[*ast.CallExpr]bool{}, variables: []string{}, @@ -61,6 +62,14 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { } } +func (c *codegen) getFuncNameFromDecl(pkgPath string, decl *ast.FuncDecl) string { + name := decl.Name.Name + if decl.Recv != nil { + name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name + } + return c.getIdentName(pkgPath, name) +} + // analyzeVoidCalls checks for functions that are not assigned // and therefore we need to cleanup the return value from the stack. func (c *funcScope) analyzeVoidCalls(node ast.Node) bool { diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index 1b46ff986..934972fec 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -196,3 +196,33 @@ func TestExportedConst(t *testing.T) { }` eval(t, src, big.NewInt(42)) } + +func TestMultipleFuncSameName(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi" + func Main() int { + return multi.Sum() + Sum() + } + func Sum() int { + return 11 + }` + eval(t, src, big.NewInt(53)) + }) + t.Run("WithMethod", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/foo" + type Foo struct{} + func (f Foo) Bar() int { return 11 } + func Bar() int { return 22 } + func Main() int { + var a Foo + var b foo.Foo + return a.Bar() + // 11 + foo.Bar() + // 1 + b.Bar() + // 8 + Bar() // 22 + }` + eval(t, src, big.NewInt(42)) + }) +} diff --git a/pkg/compiler/testdata/foo/foo.go b/pkg/compiler/testdata/foo/foo.go index 731c66bb9..ca98105ac 100644 --- a/pkg/compiler/testdata/foo/foo.go +++ b/pkg/compiler/testdata/foo/foo.go @@ -4,3 +4,16 @@ package foo func NewBar() int { return 10 } + +// Foo is a type. +type Foo struct{} + +// Bar is a function. +func Bar() int { + return 1 +} + +// Bar is a method. +func (f Foo) Bar() int { + return 8 +} diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index 237ee8c12..7456eedc4 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -74,6 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) { b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) require.NoError(t, err) + invokeMethod(t, testMainIdent, b, vm, di) return vm, storePlugin }