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 }