neo-go/pkg/compiler/analysis.go
Evgenii Stratonikov 3cbd138b67 compiler: allow to declare variables of struct type
Previously, struct variables were initialize with VM's nil value
which is of primitive type. Thus SETITEM used for struct's field
updating wasn't working.
2020-03-27 13:50:09 +03:00

248 lines
5.4 KiB
Go

package compiler
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"golang.org/x/tools/go/loader"
)
var (
// Go language builtin functions and custom builtin utility functions.
builtinFuncs = []string{
"len", "append", "SHA256",
"SHA1", "Hash256", "Hash160",
"VerifySignature", "AppCall",
"FromAddress", "Equals",
"panic",
}
)
// typeAndValueForField returns a zero initialized typeAndValue for the given type.Var.
func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) {
switch t := fld.Type().(type) {
case *types.Basic:
switch t.Kind() {
case types.Int:
return types.TypeAndValue{
Type: t,
Value: constant.MakeInt64(0),
}, nil
case types.String:
return types.TypeAndValue{
Type: t,
Value: constant.MakeString(""),
}, nil
case types.Bool, types.UntypedBool:
return types.TypeAndValue{
Type: t,
Value: constant.MakeBool(false),
}, nil
default:
return types.TypeAndValue{}, fmt.Errorf("could not initialize struct field %s to zero, type: %s", fld.Name(), t)
}
}
return types.TypeAndValue{}, nil
}
// countGlobals counts the global variables in the program to add
// them with the stack size of the function.
func countGlobals(f ast.Node) (i int64) {
ast.Inspect(f, func(node ast.Node) bool {
switch 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++
}
return true
})
return
}
// isIdentBool looks if the given ident is a boolean.
func isIdentBool(ident *ast.Ident) bool {
return ident.Name == "true" || ident.Name == "false"
}
// 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"
}
// makeBoolFromIdent creates a bool type from an *ast.Ident.
func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) (types.TypeAndValue, error) {
var b bool
switch ident.Name {
case "true":
b = true
case "false":
b = false
default:
return types.TypeAndValue{}, fmt.Errorf("givent identifier cannot be converted to a boolean => %s", ident.Name)
}
return types.TypeAndValue{
Type: tinfo.ObjectOf(ident).Type(),
Value: constant.MakeBool(b),
}, nil
}
// resolveEntryPoint returns the function declaration of the entrypoint and the corresponding file.
func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *ast.File) {
var (
main *ast.FuncDecl
file *ast.File
)
for _, f := range pkg.Files {
ast.Inspect(f, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.Name == entry {
main = t
file = f
return false
}
}
return true
})
}
return main, file
}
// 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
}
// hasReturnStmt looks if the given FuncDecl has a return statement.
func hasReturnStmt(decl ast.Node) (b bool) {
ast.Inspect(decl, func(node ast.Node) bool {
if _, ok := node.(*ast.ReturnStmt); ok {
b = true
return false
}
return true
})
return
}
func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
usage := funcUsage{}
for _, pkg := range pkgs {
for _, f := range pkg.Files {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
switch t := n.Fun.(type) {
case *ast.Ident:
usage[t.Name] = true
case *ast.SelectorExpr:
usage[t.Sel.Name] = true
}
}
return true
})
}
}
return usage
}
func isBuiltin(expr ast.Expr) bool {
var name string
switch t := expr.(type) {
case *ast.Ident:
name = t.Name
case *ast.SelectorExpr:
name = t.Sel.Name
default:
return false
}
for _, n := range builtinFuncs {
if name == n {
return true
}
}
return false
}
func (c *codegen) isCompoundArrayType(t ast.Expr) bool {
switch s := t.(type) {
case *ast.ArrayType:
return true
case *ast.Ident:
arr, ok := c.typeInfo.Types[s].Type.Underlying().(*types.Slice)
return ok && !isByte(arr.Elem())
}
return false
}
func isByte(t types.Type) bool {
e, ok := t.(*types.Basic)
return ok && e.Kind() == types.Byte
}
func (c *codegen) isStructType(t ast.Expr) (int, bool) {
switch s := t.(type) {
case *ast.StructType:
return s.Fields.NumFields(), true
case *ast.Ident:
st, ok := c.typeInfo.Types[s].Type.Underlying().(*types.Struct)
if ok {
return st.NumFields(), true
}
}
return 0, false
}
func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool {
if len(lit.Elts) == 0 {
if typ, ok := lit.Type.(*ast.ArrayType); ok {
if name, ok := typ.Elt.(*ast.Ident); ok {
return name.Name == "byte" || name.Name == "uint8"
}
}
return false
}
typ := tInfo.Types[lit.Elts[0]].Type.Underlying()
return isByte(typ)
}
func isSyscall(fun *funcScope) bool {
if fun.selector == nil {
return false
}
_, ok := syscalls[fun.selector.Name][fun.name]
return ok
}
func isByteArrayType(t types.Type) bool {
return t.String() == "[]byte"
}
func isStringType(t types.Type) bool {
return t.String() == "string"
}