github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/analysis.go (about)

     1  package compiler
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/token"
     8  	"go/types"
     9  	"strings"
    10  
    11  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    13  	"golang.org/x/tools/go/packages"
    14  )
    15  
    16  // Various exported functions usage errors.
    17  var (
    18  	// ErrMissingExportedParamName is returned when exported contract method has unnamed parameter.
    19  	ErrMissingExportedParamName = errors.New("exported method is not allowed to have unnamed parameter")
    20  	// ErrInvalidExportedRetCount is returned when exported contract method has invalid return values count.
    21  	ErrInvalidExportedRetCount = errors.New("exported method is not allowed to have more than one return value")
    22  	// ErrGenericsUnsuppored is returned when generics-related tokens are encountered.
    23  	ErrGenericsUnsuppored = errors.New("generics are currently unsupported, please, see the https://github.com/nspcc-dev/neo-go/issues/2376")
    24  )
    25  
    26  var (
    27  	// Go language builtin functions.
    28  	goBuiltins = []string{"len", "append", "panic", "make", "copy", "recover", "delete"}
    29  	// Custom builtin utility functions that contain some meaningful code inside and
    30  	// require code generation using standard rules, but sometimes (depending on
    31  	// the expression usage condition) may be optimized at compile time.
    32  	potentialCustomBuiltins = map[string]func(f ast.Expr) bool{
    33  		"ToHash160": func(f ast.Expr) bool {
    34  			c, ok := f.(*ast.CallExpr)
    35  			if !ok {
    36  				return false
    37  			}
    38  			if len(c.Args) != 1 {
    39  				return false
    40  			}
    41  			switch c.Args[0].(type) {
    42  			case *ast.BasicLit:
    43  				return true
    44  			default:
    45  				return false
    46  			}
    47  		},
    48  	}
    49  )
    50  
    51  // newGlobal creates a new global variable.
    52  func (c *codegen) newGlobal(pkg string, name string) {
    53  	name = c.getIdentName(pkg, name)
    54  	c.globals[name] = len(c.globals)
    55  }
    56  
    57  // getIdentName returns a fully-qualified name for a variable.
    58  func (c *codegen) getIdentName(pkg string, name string) string {
    59  	if fullName, ok := c.importMap[pkg]; ok {
    60  		pkg = fullName
    61  	}
    62  	return pkg + "." + name
    63  }
    64  
    65  // traverseGlobals visits and initializes global variables.
    66  // It returns `true` if contract has `_deploy` function.
    67  func (c *codegen) traverseGlobals() bool {
    68  	var hasDefer bool
    69  	var n, nConst int
    70  	var hasUnusedCall bool
    71  	var hasDeploy bool
    72  	c.ForEachFile(func(f *ast.File, pkg *types.Package) {
    73  		nv, nc, huc := countGlobals(f, !hasUnusedCall)
    74  		n += nv
    75  		nConst += nc
    76  		if huc {
    77  			hasUnusedCall = true
    78  		}
    79  		if !hasDeploy || !hasDefer {
    80  			ast.Inspect(f, func(node ast.Node) bool {
    81  				switch n := node.(type) {
    82  				case *ast.FuncDecl:
    83  					hasDeploy = hasDeploy || isDeployFunc(n)
    84  				case *ast.DeferStmt:
    85  					hasDefer = true
    86  					return false
    87  				}
    88  				return true
    89  			})
    90  		}
    91  	})
    92  	if hasDefer {
    93  		n++
    94  	}
    95  
    96  	if n > 255 {
    97  		c.prog.BinWriter.Err = errors.New("too many global variables")
    98  		return hasDeploy
    99  	}
   100  
   101  	if n != 0 {
   102  		emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
   103  	}
   104  
   105  	initOffset := c.prog.Len()
   106  	emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{0, 0})
   107  
   108  	lastCnt, maxCnt := -1, -1
   109  	c.ForEachPackage(func(pkg *packages.Package) {
   110  		if n+nConst > 0 || hasUnusedCall {
   111  			for _, f := range pkg.Syntax {
   112  				c.fillImportMap(f, pkg)
   113  				c.convertGlobals(f)
   114  			}
   115  		}
   116  		for _, f := range pkg.Syntax {
   117  			c.fillImportMap(f, pkg)
   118  
   119  			var currMax int
   120  			lastCnt, currMax = c.convertInitFuncs(f, pkg.Types, lastCnt)
   121  			if currMax > maxCnt {
   122  				maxCnt = currMax
   123  			}
   124  		}
   125  		// because we reuse `convertFuncDecl` for init funcs,
   126  		// we need to clear scope, so that global variables
   127  		// encountered after will be recognized as globals.
   128  		c.scope = nil
   129  	})
   130  
   131  	if c.globalInlineCount > maxCnt {
   132  		maxCnt = c.globalInlineCount
   133  	}
   134  
   135  	// Here we remove `INITSLOT` if no code was emitted for `init` function.
   136  	// Note that the `INITSSLOT` must stay in place.
   137  	hasNoInit := initOffset+3 == c.prog.Len()
   138  	if hasNoInit {
   139  		buf := c.prog.Bytes()
   140  		c.prog.Reset()
   141  		c.prog.WriteBytes(buf[:initOffset])
   142  	}
   143  
   144  	if initOffset != 0 || !hasNoInit { // if there are some globals or `init()`.
   145  		c.initEndOffset = c.prog.Len()
   146  		emit.Opcodes(c.prog.BinWriter, opcode.RET)
   147  
   148  		if maxCnt >= 0 {
   149  			c.reverseOffsetMap[initOffset] = nameWithLocals{
   150  				name:  "init",
   151  				count: maxCnt,
   152  			}
   153  		}
   154  	}
   155  
   156  	// store auxiliary variables after all others.
   157  	if hasDefer {
   158  		c.exceptionIndex = len(c.globals)
   159  		c.globals[exceptionVarName] = c.exceptionIndex
   160  	}
   161  
   162  	return hasDeploy
   163  }
   164  
   165  // countGlobals counts the global variables in the program to add
   166  // them with the stack size of the function.
   167  // Second returned argument contains the amount of global constants.
   168  // If checkUnusedCalls set to true then unnamed global variables containing call
   169  // will be searched for and their presence is returned as the last argument.
   170  func countGlobals(f ast.Node, checkUnusedCalls bool) (int, int, bool) {
   171  	var numVar, numConst int
   172  	var hasUnusedCall bool
   173  	ast.Inspect(f, func(node ast.Node) bool {
   174  		switch n := node.(type) {
   175  		// Skip all function declarations if we have already encountered `defer`.
   176  		case *ast.FuncDecl:
   177  			return false
   178  		// After skipping all funcDecls, we are sure that each value spec
   179  		// is a globally declared variable or constant.
   180  		case *ast.GenDecl:
   181  			isVar := n.Tok == token.VAR
   182  			if isVar || n.Tok == token.CONST {
   183  				for _, s := range n.Specs {
   184  					valueSpec := s.(*ast.ValueSpec)
   185  					multiRet := len(valueSpec.Values) != 0 && len(valueSpec.Names) != len(valueSpec.Values) // e.g. var A, B = f() where func f() (int, int)
   186  					for j, id := range valueSpec.Names {
   187  						if id.Name != "_" { // If variable has name, then it's treated as used - that's countGlobals' caller responsibility to guarantee that.
   188  							if isVar {
   189  								numVar++
   190  							} else {
   191  								numConst++
   192  							}
   193  						} else if isVar && len(valueSpec.Values) != 0 && checkUnusedCalls && !hasUnusedCall {
   194  							indexToCheck := j
   195  							if multiRet {
   196  								indexToCheck = 0
   197  							}
   198  							hasUnusedCall = containsCall(valueSpec.Values[indexToCheck])
   199  						}
   200  					}
   201  				}
   202  			}
   203  			return false
   204  		}
   205  		return true
   206  	})
   207  	return numVar, numConst, hasUnusedCall
   208  }
   209  
   210  // containsCall traverses node and looks if it contains a function or method call.
   211  func containsCall(n ast.Node) bool {
   212  	var hasCall bool
   213  	ast.Inspect(n, func(node ast.Node) bool {
   214  		switch node.(type) {
   215  		case *ast.CallExpr:
   216  			hasCall = true
   217  		case *ast.Ident:
   218  			// Can safely skip idents immediately, we're interested at function calls only.
   219  			return false
   220  		}
   221  		return !hasCall
   222  	})
   223  	return hasCall
   224  }
   225  
   226  // isExprNil looks if the given expression is a `nil`.
   227  func isExprNil(e ast.Expr) bool {
   228  	v, ok := e.(*ast.Ident)
   229  	return ok && v.Name == "nil"
   230  }
   231  
   232  // indexOfStruct returns the index of the given field inside that struct.
   233  // If the struct does not contain that field, it will return -1.
   234  func indexOfStruct(strct *types.Struct, fldName string) int {
   235  	for i := 0; i < strct.NumFields(); i++ {
   236  		if strct.Field(i).Name() == fldName {
   237  			return i
   238  		}
   239  	}
   240  	return -1
   241  }
   242  
   243  type funcUsage map[string]bool
   244  
   245  func (f funcUsage) funcUsed(name string) bool {
   246  	_, ok := f[name]
   247  	return ok
   248  }
   249  
   250  // lastStmtIsReturn checks if the last statement of the declaration was return statement.
   251  func lastStmtIsReturn(body *ast.BlockStmt) (b bool) {
   252  	if l := len(body.List); l != 0 {
   253  		switch inner := body.List[l-1].(type) {
   254  		case *ast.BlockStmt:
   255  			return lastStmtIsReturn(inner)
   256  		case *ast.ReturnStmt:
   257  			return true
   258  		default:
   259  			return false
   260  		}
   261  	}
   262  	return false
   263  }
   264  
   265  // analyzePkgOrder sets the order in which packages should be processed.
   266  // From Go spec:
   267  //
   268  //	A package with no imports is initialized by assigning initial values to all its package-level variables
   269  //	followed by calling all init functions in the order they appear in the source, possibly in multiple files,
   270  //	as presented to the compiler. If a package has imports, the imported packages are initialized before
   271  //	initializing the package itself. If multiple packages import a package, the imported package
   272  //	will be initialized only once. The importing of packages, by construction, guarantees
   273  //	that there can be no cyclic initialization dependencies.
   274  func (c *codegen) analyzePkgOrder() {
   275  	seen := make(map[string]bool)
   276  	info := c.buildInfo.program[0]
   277  	c.visitPkg(info, seen)
   278  }
   279  
   280  func (c *codegen) visitPkg(pkg *packages.Package, seen map[string]bool) {
   281  	if seen[pkg.PkgPath] {
   282  		return
   283  	}
   284  	for _, imp := range pkg.Types.Imports() {
   285  		var subpkg = pkg.Imports[imp.Path()]
   286  		if subpkg == nil {
   287  			if c.prog.Err == nil {
   288  				c.prog.Err = fmt.Errorf("failed to load %q package from %q, import cycle?", imp.Path(), pkg.PkgPath)
   289  			}
   290  			return
   291  		}
   292  		c.visitPkg(subpkg, seen)
   293  	}
   294  	seen[pkg.PkgPath] = true
   295  	c.packages = append(c.packages, pkg.PkgPath)
   296  	c.packageCache[pkg.PkgPath] = pkg
   297  }
   298  
   299  func (c *codegen) fillDocumentInfo() {
   300  	fset := c.buildInfo.config.Fset
   301  	fset.Iterate(func(f *token.File) bool {
   302  		filePath := f.Position(f.Pos(0)).Filename
   303  		c.docIndex[filePath] = len(c.documents)
   304  		c.documents = append(c.documents, filePath)
   305  		return true
   306  	})
   307  }
   308  
   309  // analyzeFuncAndGlobalVarUsage traverses all code and returns a map with functions
   310  // which should be present in the emitted code.
   311  // This is done using BFS starting from exported functions or
   312  // the function used in variable declarations (graph edge corresponds to
   313  // the function being called in declaration). It also analyzes global variables
   314  // usage preserving the same traversal strategy and rules. Unused global variables
   315  // are renamed to "_" in the end. Global variable is treated as "used" iff:
   316  // 1. It belongs either to main or to exported package AND is used directly from the exported (or _init\_deploy) method of the main package.
   317  // 2. It belongs either to main or to exported package AND is used non-directly from the exported (or _init\_deploy) method of the main package
   318  // (e.g. via series of function calls or in some expression that is "used").
   319  // 3. It belongs either to main or to exported package AND contains function call inside its value definition.
   320  func (c *codegen) analyzeFuncAndGlobalVarUsage() funcUsage {
   321  	type declPair struct {
   322  		decl      *ast.FuncDecl
   323  		importMap map[string]string
   324  		path      string
   325  	}
   326  	// globalVar represents a global variable declaration node with the corresponding package context.
   327  	type globalVar struct {
   328  		decl      *ast.GenDecl // decl contains global variables declaration node (there can be multiple declarations in a single node).
   329  		specIdx   int          // specIdx is the index of variable specification in the list of GenDecl specifications.
   330  		varIdx    int          // varIdx is the index of variable name in the specification names.
   331  		ident     *ast.Ident   // ident is a named global variable identifier got from the specified node.
   332  		importMap map[string]string
   333  		path      string
   334  	}
   335  	// nodeCache contains top-level function declarations.
   336  	nodeCache := make(map[string]declPair)
   337  	// globalVarsCache contains both used and unused declared named global vars.
   338  	globalVarsCache := make(map[string]globalVar)
   339  	// diff contains used functions that are not yet marked as "used" and those definition
   340  	// requires traversal in the subsequent stages.
   341  	diff := funcUsage{}
   342  	// globalVarsDiff contains used named global variables that are not yet marked as "used"
   343  	// and those declaration requires traversal in the subsequent stages.
   344  	globalVarsDiff := funcUsage{}
   345  	// usedExpressions contains a set of ast.Nodes that are used in the program and need to be evaluated
   346  	// (either they are used from the used functions OR belong to global variable declaration and surrounded by a function call)
   347  	var usedExpressions []nodeContext
   348  	c.ForEachFile(func(f *ast.File, pkg *types.Package) {
   349  		var pkgPath string
   350  		isMain := pkg == c.mainPkg.Types
   351  		if !isMain {
   352  			pkgPath = pkg.Path()
   353  		}
   354  
   355  		ast.Inspect(f, func(node ast.Node) bool {
   356  			switch n := node.(type) {
   357  			case *ast.CallExpr:
   358  				// functions invoked in variable declarations in imported packages
   359  				// are marked as used.
   360  				var name string
   361  				switch t := n.Fun.(type) {
   362  				case *ast.Ident:
   363  					name = c.getIdentName(pkgPath, t.Name)
   364  				case *ast.SelectorExpr:
   365  					name, _ = c.getFuncNameFromSelector(t)
   366  				default:
   367  					return true
   368  				}
   369  				diff[name] = true
   370  			case *ast.FuncDecl:
   371  				name := c.getFuncNameFromDecl(pkgPath, n)
   372  
   373  				// filter out generic functions
   374  				err := c.checkGenericsFuncDecl(n, name)
   375  				if err != nil {
   376  					c.prog.Err = err
   377  					return false // Program is invalid.
   378  				}
   379  
   380  				// exported functions and methods are always assumed to be used
   381  				if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) {
   382  					diff[name] = true
   383  				}
   384  				// exported functions are not allowed to have unnamed parameters  or multiple return values
   385  				if isMain && n.Name.IsExported() && n.Recv == nil {
   386  					if n.Type.Params.List != nil {
   387  						for i, param := range n.Type.Params.List {
   388  							if param.Names == nil {
   389  								c.prog.Err = fmt.Errorf("%w: %s", ErrMissingExportedParamName, n.Name)
   390  								return false // Program is invalid.
   391  							}
   392  							for _, name := range param.Names {
   393  								if name == nil || name.Name == "_" {
   394  									c.prog.Err = fmt.Errorf("%w: %s/%d", ErrMissingExportedParamName, n.Name, i)
   395  									return false // Program is invalid.
   396  								}
   397  							}
   398  						}
   399  					}
   400  					if retCnt := n.Type.Results.NumFields(); retCnt > 1 {
   401  						c.prog.Err = fmt.Errorf("%w: %s/%d return values", ErrInvalidExportedRetCount, n.Name, retCnt)
   402  					}
   403  				}
   404  				nodeCache[name] = declPair{n, c.importMap, pkgPath}
   405  				return false // will be processed in the next stage
   406  			case *ast.GenDecl:
   407  				// Filter out generics usage.
   408  				err := c.checkGenericsGenDecl(n, pkgPath)
   409  				if err != nil {
   410  					c.prog.Err = err
   411  					return false // Program is invalid.
   412  				}
   413  
   414  				// After skipping all funcDecls, we are sure that each value spec
   415  				// is a globally declared variable or constant. We need to gather global
   416  				// vars from both main and imported packages.
   417  				if n.Tok == token.VAR {
   418  					for i, s := range n.Specs {
   419  						valSpec := s.(*ast.ValueSpec)
   420  						for j, id := range valSpec.Names {
   421  							if id.Name != "_" {
   422  								name := c.getIdentName(pkgPath, id.Name)
   423  								globalVarsCache[name] = globalVar{
   424  									decl:      n,
   425  									specIdx:   i,
   426  									varIdx:    j,
   427  									ident:     id,
   428  									importMap: c.importMap,
   429  									path:      pkgPath,
   430  								}
   431  							}
   432  							// Traverse both named/unnamed global variables, check whether function/method call
   433  							// is present inside variable value and if so, mark all its children as "used" for
   434  							// further traversal and evaluation.
   435  							if len(valSpec.Values) == 0 {
   436  								continue
   437  							}
   438  							multiRet := len(valSpec.Values) != len(valSpec.Names)
   439  							if (j == 0 || !multiRet) && containsCall(valSpec.Values[j]) {
   440  								usedExpressions = append(usedExpressions, nodeContext{
   441  									node:      valSpec.Values[j],
   442  									path:      pkgPath,
   443  									importMap: c.importMap,
   444  									typeInfo:  c.typeInfo,
   445  									currPkg:   c.currPkg,
   446  								})
   447  							}
   448  						}
   449  					}
   450  				}
   451  			}
   452  			return true
   453  		})
   454  	})
   455  	if c.prog.Err != nil {
   456  		return nil
   457  	}
   458  
   459  	// Handle nodes that contain (or surrounded by) function calls and are a part
   460  	// of global variable declaration.
   461  	c.pickVarsFromNodes(usedExpressions, func(name string) {
   462  		if _, gOK := globalVarsCache[name]; gOK {
   463  			globalVarsDiff[name] = true
   464  		}
   465  	})
   466  
   467  	// Traverse the set of upper-layered used functions and construct the functions' usage map.
   468  	// At the same time, go through the whole set of used functions and mark global vars used
   469  	// from these functions as "used". Also mark the global variables from the previous step
   470  	// and their children as "used".
   471  	usage := funcUsage{}
   472  	globalVarsUsage := funcUsage{}
   473  	for len(diff) != 0 || len(globalVarsDiff) != 0 {
   474  		nextDiff := funcUsage{}
   475  		nextGlobalVarsDiff := funcUsage{}
   476  		usedExpressions = usedExpressions[:0]
   477  		for name := range diff {
   478  			fd, ok := nodeCache[name]
   479  			if !ok || usage[name] {
   480  				continue
   481  			}
   482  			usage[name] = true
   483  
   484  			pkg := c.mainPkg
   485  			if fd.path != "" {
   486  				pkg = c.packageCache[fd.path]
   487  			}
   488  			c.typeInfo = pkg.TypesInfo
   489  			c.currPkg = pkg
   490  			c.importMap = fd.importMap
   491  			ast.Inspect(fd.decl, func(node ast.Node) bool {
   492  				switch n := node.(type) {
   493  				case *ast.CallExpr:
   494  					switch t := n.Fun.(type) {
   495  					case *ast.Ident:
   496  						nextDiff[c.getIdentName(fd.path, t.Name)] = true
   497  					case *ast.SelectorExpr:
   498  						name, _ := c.getFuncNameFromSelector(t)
   499  						nextDiff[name] = true
   500  					}
   501  				}
   502  				return true
   503  			})
   504  			usedExpressions = append(usedExpressions, nodeContext{
   505  				node:      fd.decl.Body,
   506  				path:      fd.path,
   507  				importMap: c.importMap,
   508  				typeInfo:  c.typeInfo,
   509  				currPkg:   c.currPkg,
   510  			})
   511  		}
   512  
   513  		// Traverse used global vars in a separate cycle so that we're sure there's no other unrelated vars.
   514  		// Mark their children as "used".
   515  		for name := range globalVarsDiff {
   516  			fd, ok := globalVarsCache[name]
   517  			if !ok || globalVarsUsage[name] {
   518  				continue
   519  			}
   520  			globalVarsUsage[name] = true
   521  			pkg := c.mainPkg
   522  			if fd.path != "" {
   523  				pkg = c.packageCache[fd.path]
   524  			}
   525  			valSpec := fd.decl.Specs[fd.specIdx].(*ast.ValueSpec)
   526  			if len(valSpec.Values) == 0 {
   527  				continue
   528  			}
   529  			multiRet := len(valSpec.Values) != len(valSpec.Names)
   530  			if fd.varIdx == 0 || !multiRet {
   531  				usedExpressions = append(usedExpressions, nodeContext{
   532  					node:      valSpec.Values[fd.varIdx],
   533  					path:      fd.path,
   534  					importMap: fd.importMap,
   535  					typeInfo:  pkg.TypesInfo,
   536  					currPkg:   pkg,
   537  				})
   538  			}
   539  		}
   540  		c.pickVarsFromNodes(usedExpressions, func(name string) {
   541  			if _, gOK := globalVarsCache[name]; gOK {
   542  				nextGlobalVarsDiff[name] = true
   543  			}
   544  		})
   545  		diff = nextDiff
   546  		globalVarsDiff = nextGlobalVarsDiff
   547  	}
   548  
   549  	// Tiny hack: rename all remaining unused global vars. After that these unused
   550  	// vars will be handled as any other unnamed unused variables, i.e.
   551  	// c.traverseGlobals() won't take them into account during static slot creation
   552  	// and the code won't be emitted for them.
   553  	for name, node := range globalVarsCache {
   554  		if _, ok := globalVarsUsage[name]; !ok {
   555  			node.ident.Name = "_"
   556  		}
   557  	}
   558  	return usage
   559  }
   560  
   561  // checkGenericFuncDecl checks whether provided ast.FuncDecl has generic code.
   562  func (c *codegen) checkGenericsFuncDecl(n *ast.FuncDecl, funcName string) error {
   563  	var errGenerics error
   564  
   565  	// Generic function receiver.
   566  	if n.Recv != nil {
   567  		switch t := n.Recv.List[0].Type.(type) {
   568  		case *ast.StarExpr:
   569  			switch t.X.(type) {
   570  			case *ast.IndexExpr:
   571  				// func (x *Pointer[T]) Load() *T
   572  				errGenerics = errors.New("generic pointer function receiver")
   573  			}
   574  		case *ast.IndexExpr:
   575  			// func (x Structure[T]) Load() *T
   576  			errGenerics = errors.New("generic function receiver")
   577  		}
   578  	}
   579  
   580  	// Generic function parameters type: func SumInts[V int64 | int32](vals []V) V
   581  	if n.Type.TypeParams != nil {
   582  		errGenerics = errors.New("function type parameters")
   583  	}
   584  
   585  	if errGenerics != nil {
   586  		return fmt.Errorf("%w: %s has %s", ErrGenericsUnsuppored, funcName, errGenerics.Error())
   587  	}
   588  
   589  	return nil
   590  }
   591  
   592  // checkGenericsGenDecl checks whether provided ast.GenDecl has generic code.
   593  func (c *codegen) checkGenericsGenDecl(n *ast.GenDecl, pkgPath string) error {
   594  	// Generic type declaration:
   595  	// 	type List[T any] struct
   596  	// 	type List[T any] interface
   597  	if n.Tok == token.TYPE {
   598  		for _, s := range n.Specs {
   599  			typeSpec := s.(*ast.TypeSpec)
   600  			if typeSpec.TypeParams != nil {
   601  				return fmt.Errorf("%w: type %s is generic", ErrGenericsUnsuppored, c.getIdentName(pkgPath, typeSpec.Name.Name))
   602  			}
   603  		}
   604  	}
   605  
   606  	return nil
   607  }
   608  
   609  // nodeContext contains ast node with the corresponding import map, type info and package information
   610  // required to retrieve fully qualified node name (if so).
   611  type nodeContext struct {
   612  	node      ast.Node
   613  	path      string
   614  	importMap map[string]string
   615  	typeInfo  *types.Info
   616  	currPkg   *packages.Package
   617  }
   618  
   619  // derive returns provided node with the parent's context.
   620  func (c nodeContext) derive(n ast.Node) nodeContext {
   621  	return nodeContext{
   622  		node:      n,
   623  		path:      c.path,
   624  		importMap: c.importMap,
   625  		typeInfo:  c.typeInfo,
   626  		currPkg:   c.currPkg,
   627  	}
   628  }
   629  
   630  // pickVarsFromNodes searches for variables used in the given set of nodes
   631  // calling markAsUsed for each variable. Be careful while using codegen after
   632  // pickVarsFromNodes, it changes importMap, currPkg and typeInfo.
   633  func (c *codegen) pickVarsFromNodes(nodes []nodeContext, markAsUsed func(name string)) {
   634  	for len(nodes) != 0 {
   635  		var nextExprToCheck []nodeContext
   636  		for _, val := range nodes {
   637  			// Set variable context for proper name extraction.
   638  			c.importMap = val.importMap
   639  			c.currPkg = val.currPkg
   640  			c.typeInfo = val.typeInfo
   641  			ast.Inspect(val.node, func(node ast.Node) bool {
   642  				switch n := node.(type) {
   643  				case *ast.KeyValueExpr: // var _ = f() + CustomInt{Int: Unused}.Int + 3 => mark Unused as "used".
   644  					nextExprToCheck = append(nextExprToCheck, val.derive(n.Value))
   645  					return false
   646  				case *ast.CallExpr:
   647  					switch t := n.Fun.(type) {
   648  					case *ast.Ident:
   649  						// Do nothing, used functions are handled in a separate cycle.
   650  					case *ast.SelectorExpr:
   651  						nextExprToCheck = append(nextExprToCheck, val.derive(t))
   652  					}
   653  					for _, arg := range n.Args {
   654  						switch arg.(type) {
   655  						case *ast.BasicLit:
   656  						default:
   657  							nextExprToCheck = append(nextExprToCheck, val.derive(arg))
   658  						}
   659  					}
   660  					return false
   661  				case *ast.SelectorExpr:
   662  					if c.typeInfo.Selections[n] != nil {
   663  						switch t := n.X.(type) {
   664  						case *ast.Ident:
   665  							nextExprToCheck = append(nextExprToCheck, val.derive(t))
   666  						case *ast.CompositeLit:
   667  							nextExprToCheck = append(nextExprToCheck, val.derive(t))
   668  						case *ast.SelectorExpr: // imp_pkg.Anna.GetAge() => mark Anna (exported global struct) as used.
   669  							nextExprToCheck = append(nextExprToCheck, val.derive(t))
   670  						}
   671  					} else {
   672  						ident := n.X.(*ast.Ident)
   673  						name := c.getIdentName(ident.Name, n.Sel.Name)
   674  						markAsUsed(name)
   675  					}
   676  					return false
   677  				case *ast.CompositeLit: // var _ = f(1) + []int{1, Unused, 3}[1] => mark Unused as "used".
   678  					for _, e := range n.Elts {
   679  						switch e.(type) {
   680  						case *ast.BasicLit:
   681  						default:
   682  							nextExprToCheck = append(nextExprToCheck, val.derive(e))
   683  						}
   684  					}
   685  					return false
   686  				case *ast.Ident:
   687  					name := c.getIdentName(val.path, n.Name)
   688  					markAsUsed(name)
   689  					return false
   690  				case *ast.DeferStmt:
   691  					nextExprToCheck = append(nextExprToCheck, val.derive(n.Call.Fun))
   692  					return false
   693  				case *ast.BasicLit:
   694  					return false
   695  				}
   696  				return true
   697  			})
   698  		}
   699  		nodes = nextExprToCheck
   700  	}
   701  }
   702  
   703  func isGoBuiltin(name string) bool {
   704  	for i := range goBuiltins {
   705  		if name == goBuiltins[i] {
   706  			return true
   707  		}
   708  	}
   709  	return false
   710  }
   711  
   712  func isPotentialCustomBuiltin(f *funcScope, expr ast.Expr) bool {
   713  	if !isInteropPath(f.pkg.Path()) {
   714  		return false
   715  	}
   716  	for name, isBuiltin := range potentialCustomBuiltins {
   717  		if f.name == name && isBuiltin(expr) {
   718  			return true
   719  		}
   720  	}
   721  	return false
   722  }
   723  
   724  func isSyscall(fun *funcScope) bool {
   725  	if fun.selector == nil || fun.pkg == nil || !isInteropPath(fun.pkg.Path()) {
   726  		return false
   727  	}
   728  	return fun.pkg.Name() == "neogointernal" && (strings.HasPrefix(fun.name, "Syscall") ||
   729  		strings.HasPrefix(fun.name, "Opcode") || strings.HasPrefix(fun.name, "CallWithToken"))
   730  }
   731  
   732  const interopPrefix = "github.com/nspcc-dev/neo-go/pkg/interop"
   733  
   734  func isInteropPath(s string) bool {
   735  	return strings.HasPrefix(s, interopPrefix)
   736  }
   737  
   738  // canConvert returns true if type doesn't need to be converted on type assertion.
   739  func canConvert(s string) bool {
   740  	if len(s) != 0 && s[0] == '*' {
   741  		s = s[1:]
   742  	}
   743  	if isInteropPath(s) {
   744  		s = s[len(interopPrefix):]
   745  		return s != "/iterator.Iterator" && s != "/storage.Context" &&
   746  			s != "/native/ledger.Block" && s != "/native/ledger.Transaction" &&
   747  			s != "/native/management.Contract" && s != "/native/neo.AccountState" &&
   748  			s != "/native/ledger.BlockSR"
   749  	}
   750  	return true
   751  }
   752  
   753  // canInline returns true if the function is to be inlined.
   754  // The list of functions that can be inlined is not static, it depends on the function usages.
   755  // isBuiltin denotes whether code generation for dynamic builtin function will be performed
   756  // manually.
   757  func canInline(s string, name string, isBuiltin bool) bool {
   758  	if strings.HasPrefix(s, "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline") {
   759  		return true
   760  	}
   761  	if !isInteropPath(s) {
   762  		return false
   763  	}
   764  	return !strings.HasPrefix(s[len(interopPrefix):], "/neogointernal") &&
   765  		!(strings.HasPrefix(s[len(interopPrefix):], "/util") && name == "FromAddress") &&
   766  		!(strings.HasPrefix(s[len(interopPrefix):], "/lib/address") && name == "ToHash160" && isBuiltin)
   767  }