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 <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2020-07-29 17:20:00 +03:00
parent 528c184f00
commit a781d299e0
8 changed files with 87 additions and 15 deletions

View file

@ -113,14 +113,15 @@ func (c *codegen) analyzeFuncUsage() funcUsage {
case *ast.CallExpr: case *ast.CallExpr:
switch t := n.Fun.(type) { switch t := n.Fun.(type) {
case *ast.Ident: case *ast.Ident:
usage[t.Name] = true usage[c.getIdentName("", t.Name)] = true
case *ast.SelectorExpr: case *ast.SelectorExpr:
usage[t.Sel.Name] = true name, _ := c.getFuncNameFromSelector(t)
usage[name] = true
} }
case *ast.FuncDecl: case *ast.FuncDecl:
// exported functions are always assumed to be used // exported functions are always assumed to be used
if isMain && n.Name.IsExported() { if isMain && n.Name.IsExported() {
usage[n.Name.Name] = true usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true
} }
} }
return true return true

View file

@ -68,6 +68,9 @@ type codegen struct {
// constMap contains constants from foreign packages. // constMap contains constants from foreign packages.
constMap map[string]types.TypeAndValue constMap map[string]types.TypeAndValue
// currPkg is current package being processed.
currPkg *types.Package
// mainPkg is a main package metadata. // mainPkg is a main package metadata.
mainPkg *loader.PackageInfo mainPkg *loader.PackageInfo
@ -278,19 +281,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
ok, isLambda bool ok, isLambda bool
) )
f, ok = c.funcs[decl.Name.Name] f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
if ok { if ok {
// If this function is a syscall or builtin 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) || isCustomBuiltin(f) { if isSyscall(f) || isCustomBuiltin(f) {
return return
} }
c.setLabel(f.label) 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 isLambda = ok
c.setLabel(f.label) c.setLabel(f.label)
} else { } else {
f = c.newFunc(decl) f = c.newFunc(decl)
f.pkg = pkg
} }
f.rng.Start = uint16(c.prog.Len()) 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) { switch fun := n.Fun.(type) {
case *ast.Ident: case *ast.Ident:
f, ok = c.funcs[fun.Name] f, ok = c.funcs[c.getIdentName("", fun.Name)]
isBuiltin = isGoBuiltin(fun.Name) isBuiltin = isGoBuiltin(fun.Name)
if !ok && !isBuiltin { if !ok && !isBuiltin {
name = fun.Name 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. // 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 // Otherwise this is a function call from a imported package and we can call it
// directly. // directly.
if c.typeInfo.Selections[fun] != nil { name, isMethod := c.getFuncNameFromSelector(fun)
if isMethod {
ast.Walk(c, fun.X) ast.Walk(c, fun.X)
// Dont forget to add 1 extra argument when its a method. // Dont forget to add 1 extra argument when its a method.
numArgs++ numArgs++
} }
f, ok = c.funcs[fun.Sel.Name] f, ok = c.funcs[name]
// @FIXME this could cause runtime errors. // @FIXME this could cause runtime errors.
f.selector = fun.X.(*ast.Ident) f.selector = fun.X.(*ast.Ident)
if !ok { if !ok {
@ -1431,18 +1434,30 @@ func (c *codegen) convertToken(tok token.Token) {
} }
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
f := newFuncScope(decl, c.newLabel()) f := c.newFuncScope(decl, c.newLabel())
c.funcs[f.name] = f c.funcs[c.getFuncNameFromDecl("", decl)] = f
return 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) { func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
name := fmt.Sprintf("lambda@%d", u) name := fmt.Sprintf("lambda@%d", u)
c.lambda[name] = newFuncScope(&ast.FuncDecl{ f := c.newFuncScope(&ast.FuncDecl{
Name: ast.NewIdent(name), Name: ast.NewIdent(name),
Type: lit.Type, Type: lit.Type,
Body: lit.Body, Body: lit.Body,
}, u) }, u)
c.lambda[c.getFuncNameFromDecl("", f.decl)] = f
} }
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { 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: case *ast.FuncDecl:
// Don't convert the function if it's not used. This will save a lot // Don't convert the function if it's not used. This will save a lot
// of bytecode space. // 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) c.convertFuncDecl(f, n, pkg)
} }
} }
@ -1519,7 +1535,6 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
switch n := decl.(type) { switch n := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
c.newFunc(n) c.newFunc(n)
c.funcs[n.Name.Name].pkg = pkg
} }
} }
} }

View file

@ -48,6 +48,7 @@ type buildInfo struct {
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
for _, pkg := range c.buildInfo.program.AllPackages { for _, pkg := range c.buildInfo.program.AllPackages {
c.typeInfo = &pkg.Info c.typeInfo = &pkg.Info
c.currPkg = pkg.Pkg
for _, f := range pkg.Files { for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg) c.fillImportMap(f, pkg.Pkg)
fn(f, pkg.Pkg) fn(f, pkg.Pkg)

View file

@ -152,6 +152,8 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
}) })
} }
} }
ss := strings.Split(name, ".")
name = ss[len(ss)-1]
return &MethodDebugInfo{ return &MethodDebugInfo{
ID: name, ID: name,
Name: DebugMethodName{ Name: DebugMethodName{

View file

@ -45,7 +45,7 @@ type funcScope struct {
i int i int
} }
func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
var name string var name string
if decl.Name != nil { if decl.Name != nil {
name = decl.Name.Name name = decl.Name.Name
@ -54,6 +54,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
name: name, name: name,
decl: decl, decl: decl,
label: label, label: label,
pkg: c.currPkg,
vars: newVarScope(), vars: newVarScope(),
voidCalls: map[*ast.CallExpr]bool{}, voidCalls: map[*ast.CallExpr]bool{},
variables: []string{}, 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 // analyzeVoidCalls checks for functions that are not assigned
// and therefore we need to cleanup the return value from the stack. // and therefore we need to cleanup the return value from the stack.
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool { func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {

View file

@ -196,3 +196,33 @@ func TestExportedConst(t *testing.T) {
}` }`
eval(t, src, big.NewInt(42)) 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))
})
}

View file

@ -4,3 +4,16 @@ package foo
func NewBar() int { func NewBar() int {
return 10 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
}

View file

@ -74,6 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) {
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
require.NoError(t, err) require.NoError(t, err)
invokeMethod(t, testMainIdent, b, vm, di) invokeMethod(t, testMainIdent, b, vm, di)
return vm, storePlugin return vm, storePlugin
} }