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") }