neo-go/pkg/compiler/analysis.go
Evgenii Stratonikov a781d299e0 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>
2020-07-31 12:07:06 +03:00

164 lines
3.8 KiB
Go

package compiler
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"
)
var (
// Go language builtin functions.
goBuiltins = []string{"len", "append", "panic"}
// Custom builtin utility functions.
customBuiltins = []string{
"FromAddress", "Equals",
"ToBool", "ToByteArray", "ToInteger",
}
)
// newGlobal creates new global variable.
func (c *codegen) newGlobal(pkg string, name string) {
name = c.getIdentName(pkg, name)
c.globals[name] = len(c.globals)
}
// getIdentName returns fully-qualified name for a variable.
func (c *codegen) getIdentName(pkg string, name string) string {
if fullName, ok := c.importMap[pkg]; ok {
pkg = fullName
}
return pkg + "." + name
}
// traverseGlobals visits and initializes global variables.
// and returns number of variables initialized.
func (c *codegen) traverseGlobals() int {
var n int
c.ForEachFile(func(f *ast.File, _ *types.Package) {
n += countGlobals(f)
})
if n != 0 {
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return 0
}
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
c.ForEachFile(c.convertGlobals)
}
return n
}
// countGlobals counts the global variables in the program to add
// them with the stack size of the function.
func countGlobals(f ast.Node) (i int) {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
// Skip all function declarations.
case *ast.FuncDecl:
return false
// After skipping all funcDecls we are sure that each value spec
// is a global declared variable or constant.
case *ast.ValueSpec:
i += len(n.Names)
}
return true
})
return
}
// isExprNil looks if the given expression is a `nil`.
func isExprNil(e ast.Expr) bool {
v, ok := e.(*ast.Ident)
return ok && v.Name == "nil"
}
// indexOfStruct returns the index of the given field inside that struct.
// If the struct does not contain that field it will return -1.
func indexOfStruct(strct *types.Struct, fldName string) int {
for i := 0; i < strct.NumFields(); i++ {
if strct.Field(i).Name() == fldName {
return i
}
}
return -1
}
type funcUsage map[string]bool
func (f funcUsage) funcUsed(name string) bool {
_, ok := f[name]
return ok
}
// lastStmtIsReturn checks if last statement of the declaration was return statement..
func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) {
if l := len(decl.Body.List); l != 0 {
_, ok := decl.Body.List[l-1].(*ast.ReturnStmt)
return ok
}
return false
}
func (c *codegen) analyzeFuncUsage() funcUsage {
usage := funcUsage{}
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
isMain := pkg == c.mainPkg.Pkg
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
switch t := n.Fun.(type) {
case *ast.Ident:
usage[c.getIdentName("", t.Name)] = true
case *ast.SelectorExpr:
name, _ := c.getFuncNameFromSelector(t)
usage[name] = true
}
case *ast.FuncDecl:
// exported functions are always assumed to be used
if isMain && n.Name.IsExported() {
usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true
}
}
return true
})
})
return usage
}
func isGoBuiltin(name string) bool {
for i := range goBuiltins {
if name == goBuiltins[i] {
return true
}
}
return false
}
func isCustomBuiltin(f *funcScope) bool {
if !isInteropPath(f.pkg.Path()) {
return false
}
for _, n := range customBuiltins {
if f.name == n {
return true
}
}
return false
}
func isSyscall(fun *funcScope) bool {
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")
}