2021-02-04 12:41:00 +00:00
|
|
|
package compiler
|
|
|
|
|
|
|
|
import (
|
2021-06-02 08:46:54 +00:00
|
|
|
"fmt"
|
2021-02-04 12:41:00 +00:00
|
|
|
"go/ast"
|
2021-06-02 08:46:08 +00:00
|
|
|
"go/constant"
|
2021-02-04 12:41:00 +00:00
|
|
|
"go/types"
|
|
|
|
|
2021-06-02 08:46:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
2021-06-24 15:36:40 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2021-02-04 12:41:00 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
|
|
)
|
|
|
|
|
|
|
|
// inlineCall inlines call of n for function represented by f.
|
|
|
|
// Call `f(a,b)` for definition `func f(x,y int)` is translated to block:
|
2021-05-12 20:17:03 +00:00
|
|
|
// {
|
|
|
|
// x := a
|
|
|
|
// y := b
|
|
|
|
// <inline body of f directly>
|
|
|
|
// }
|
2021-02-04 12:41:00 +00:00
|
|
|
func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
2021-02-26 14:58:08 +00:00
|
|
|
labelSz := len(c.labelList)
|
|
|
|
offSz := len(c.inlineLabelOffsets)
|
|
|
|
c.inlineLabelOffsets = append(c.inlineLabelOffsets, labelSz)
|
|
|
|
defer func() {
|
|
|
|
c.inlineLabelOffsets = c.inlineLabelOffsets[:offSz]
|
|
|
|
c.labelList = c.labelList[:labelSz]
|
|
|
|
}()
|
|
|
|
|
2021-12-02 14:44:53 +00:00
|
|
|
pkg := c.packageCache[f.pkg.Path()]
|
2021-02-04 12:41:00 +00:00
|
|
|
sig := c.typeOf(n.Fun).(*types.Signature)
|
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
c.processStdlibCall(f, n.Args)
|
2021-06-02 08:46:08 +00:00
|
|
|
|
2021-02-25 12:12:16 +00:00
|
|
|
// When inlined call is used during global initialization
|
|
|
|
// there is no func scope, thus this if.
|
|
|
|
if c.scope == nil {
|
|
|
|
c.scope = &funcScope{}
|
|
|
|
c.scope.vars.newScope()
|
2021-04-28 13:37:31 +00:00
|
|
|
defer func() {
|
|
|
|
if cnt := c.scope.vars.localsCnt; cnt > c.globalInlineCount {
|
|
|
|
c.globalInlineCount = cnt
|
|
|
|
}
|
|
|
|
c.scope = nil
|
|
|
|
}()
|
2021-02-25 12:12:16 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 12:41:00 +00:00
|
|
|
// Arguments need to be walked with the current scope,
|
|
|
|
// while stored in the new.
|
|
|
|
oldScope := c.scope.vars.locals
|
|
|
|
c.scope.vars.newScope()
|
2021-02-26 15:02:54 +00:00
|
|
|
newScope := make([]map[string]varInfo, len(c.scope.vars.locals))
|
|
|
|
copy(newScope, c.scope.vars.locals)
|
2021-02-04 12:41:00 +00:00
|
|
|
defer c.scope.vars.dropScope()
|
2021-02-08 10:51:25 +00:00
|
|
|
|
2022-07-06 14:56:53 +00:00
|
|
|
if f.decl.Recv != nil {
|
|
|
|
c.scope.vars.locals = newScope
|
|
|
|
name := f.decl.Recv.List[0].Names[0].Name
|
|
|
|
c.scope.vars.addAlias(name, -1, unspecifiedVarIndex, &varContext{
|
|
|
|
importMap: c.importMap,
|
|
|
|
expr: f.selector,
|
|
|
|
scope: oldScope,
|
|
|
|
})
|
|
|
|
}
|
2021-02-08 10:51:25 +00:00
|
|
|
hasVarArgs := !n.Ellipsis.IsValid()
|
|
|
|
needPack := sig.Variadic() && hasVarArgs
|
2021-02-04 12:41:00 +00:00
|
|
|
for i := range n.Args {
|
|
|
|
c.scope.vars.locals = oldScope
|
2021-02-08 10:51:25 +00:00
|
|
|
// true if normal arg or var arg is `slice...`
|
|
|
|
needStore := i < sig.Params().Len()-1 || !sig.Variadic() || !hasVarArgs
|
|
|
|
if !needStore {
|
|
|
|
break
|
|
|
|
}
|
2021-02-04 13:26:33 +00:00
|
|
|
name := sig.Params().At(i).Name()
|
2021-05-25 13:02:28 +00:00
|
|
|
if !c.hasCalls(n.Args[i]) {
|
|
|
|
// If argument contains no calls, we save context and traverse the expression
|
|
|
|
// when argument is emitted.
|
2021-02-05 13:15:26 +00:00
|
|
|
c.scope.vars.locals = newScope
|
2021-05-25 13:02:28 +00:00
|
|
|
c.scope.vars.addAlias(name, -1, unspecifiedVarIndex, &varContext{
|
|
|
|
importMap: c.importMap,
|
|
|
|
expr: n.Args[i],
|
|
|
|
scope: oldScope,
|
|
|
|
})
|
2021-02-05 13:15:26 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-05-25 13:02:28 +00:00
|
|
|
|
2021-02-04 12:41:00 +00:00
|
|
|
ast.Walk(c, n.Args[i])
|
|
|
|
c.scope.vars.locals = newScope
|
|
|
|
c.scope.newLocal(name)
|
|
|
|
c.emitStoreVar("", name)
|
|
|
|
}
|
|
|
|
|
2021-02-08 10:51:25 +00:00
|
|
|
if needPack {
|
|
|
|
// traverse variadic args and pack them
|
|
|
|
// if they are provided directly i.e. without `...`
|
|
|
|
c.scope.vars.locals = oldScope
|
|
|
|
for i := sig.Params().Len() - 1; i < len(n.Args); i++ {
|
|
|
|
ast.Walk(c, n.Args[i])
|
|
|
|
}
|
|
|
|
c.scope.vars.locals = newScope
|
|
|
|
c.packVarArgs(n, sig)
|
|
|
|
name := sig.Params().At(sig.Params().Len() - 1).Name()
|
|
|
|
c.scope.newLocal(name)
|
|
|
|
c.emitStoreVar("", name)
|
|
|
|
}
|
|
|
|
|
2021-02-04 12:41:00 +00:00
|
|
|
c.pkgInfoInline = append(c.pkgInfoInline, pkg)
|
|
|
|
oldMap := c.importMap
|
2021-11-30 15:03:21 +00:00
|
|
|
oldDefers := c.scope.deferStack
|
|
|
|
c.scope.deferStack = nil
|
2021-12-02 14:44:53 +00:00
|
|
|
c.fillImportMap(f.file, pkg)
|
2021-02-04 12:41:00 +00:00
|
|
|
ast.Inspect(f.decl, c.scope.analyzeVoidCalls)
|
|
|
|
ast.Walk(c, f.decl.Body)
|
|
|
|
if c.scope.voidCalls[n] {
|
|
|
|
for i := 0; i < f.decl.Type.Results.NumFields(); i++ {
|
|
|
|
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
|
|
|
|
}
|
|
|
|
}
|
2021-11-30 15:03:21 +00:00
|
|
|
c.processDefers()
|
|
|
|
c.scope.deferStack = oldDefers
|
2021-02-04 12:41:00 +00:00
|
|
|
c.importMap = oldMap
|
|
|
|
c.pkgInfoInline = c.pkgInfoInline[:len(c.pkgInfoInline)-1]
|
|
|
|
}
|
2021-06-02 08:46:08 +00:00
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) {
|
|
|
|
if f == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.pkg.Path() == interopPrefix+"/runtime" && (f.name == "Notify" || f.name == "Log") {
|
|
|
|
c.processNotify(f, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.pkg.Path() == interopPrefix+"/contract" && f.name == "Call" {
|
|
|
|
c.processContractCall(f, args)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 08:46:08 +00:00
|
|
|
func (c *codegen) processNotify(f *funcScope, args []ast.Expr) {
|
2021-06-24 15:36:40 +00:00
|
|
|
if c.scope != nil && c.isVerifyFunc(c.scope.decl) &&
|
compiler: fix panic in notification check
Options is a pointer, so it can be nil:
--- FAIL: TestCompiler (0.23s)
--- FAIL: TestCompiler/TestCompile (0.21s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x960374]
goroutine 8861 [running]:
testing.tRunner.func1.2({0xa604c0, 0x112c230})
/usr/lib64/go/1.18/src/testing/testing.go:1389 +0x24e
testing.tRunner.func1()
/usr/lib64/go/1.18/src/testing/testing.go:1392 +0x39f
panic({0xa604c0, 0x112c230})
/usr/lib64/go/1.18/src/runtime/panic.go:838 +0x207
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).processNotify(0xc0000eba40, 0xc000233ae0?, {0xc00044ae90, 0x1, 0x1})
/home/rik/dev/neo-go/pkg/compiler/inline.go:134 +0xd4
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).processStdlibCall(0xc0000eba40?, 0xc000233ae0, {0xc00044ae90, 0x1, 0x1})
/home/rik/dev/neo-go/pkg/compiler/inline.go:124 +0xda
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).inlineCall(0xc0000eba40, 0xc000233ae0, 0xc0001fe5c0)
/home/rik/dev/neo-go/pkg/compiler/inline.go:35 +0x1fa
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0000eba40, {0xd36bf8?, 0xc0001fe5c0?})
/home/rik/dev/neo-go/pkg/compiler/codegen.go:932 +0x152c
go/ast.Walk({0xd348e0?, 0xc0000eba40?}, {0xd36bf8?, 0xc0001fe5c0?})
/usr/lib64/go/1.18/src/go/ast/walk.go:52 +0x62
go/ast.Walk({0xd348e0?, 0xc0000eba40?}, {0xd36db0?, 0xc00044aeb0?})
/usr/lib64/go/1.18/src/go/ast/walk.go:207 +0x1154
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0000eba40, {0xd36ba8?, 0xc0001cce70?})
/home/rik/dev/neo-go/pkg/compiler/codegen.go:1155 +0x54cd
go/ast.Walk({0xd348e0?, 0xc0000eba40?}, {0xd36ba8?, 0xc0001cce70?})
/usr/lib64/go/1.18/src/go/ast/walk.go:52 +0x62
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0000eba40, {0xd36f68?, 0xc0001fe600?})
/home/rik/dev/neo-go/pkg/compiler/codegen.go:733 +0x2e30
go/ast.Walk({0xd348e0?, 0xc0000eba40?}, {0xd36f68?, 0xc0001fe600?})
/usr/lib64/go/1.18/src/go/ast/walk.go:52 +0x62
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0000eba40, {0xd36ba8?, 0xc0001ccea0?})
/home/rik/dev/neo-go/pkg/compiler/codegen.go:1155 +0x54cd
go/ast.Walk({0xd348e0?, 0xc0000eba40?}, {0xd36ba8?, 0xc0001ccea0?})
/usr/lib64/go/1.18/src/go/ast/walk.go:52 +0x62
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertFuncDecl(0xc0000eba40, {0xd36e28, 0xc000256700}, 0xc0001cced0, 0xc00041b68c?)
/home/rik/dev/neo-go/pkg/compiler/codegen.go:502 +0x97d
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile.func2(0xc000256700, 0xc0001c64b0)
/home/rik/dev/neo-go/pkg/compiler/codegen.go:2129 +0x1f9
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachFile.func1(0xc000254a00)
/home/rik/dev/neo-go/pkg/compiler/compiler.go:102 +0x96
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachPackage(0xc0000eba40, 0xc0002f3c68)
/home/rik/dev/neo-go/pkg/compiler/compiler.go:93 +0xdb
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachFile(0xa1f700?, 0xc000448f90?)
/home/rik/dev/neo-go/pkg/compiler/compiler.go:99 +0x4d
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile(0xc0000eba40, 0xc00022c390, 0x1?)
/home/rik/dev/neo-go/pkg/compiler/codegen.go:2116 +0x3d6
github.com/nspcc-dev/neo-go/pkg/compiler.codeGen(0xc00022c390)
/home/rik/dev/neo-go/pkg/compiler/codegen.go:2167 +0x373
github.com/nspcc-dev/neo-go/pkg/compiler.CompileWithOptions({0xc000c525a0?, 0x16?}, {0x0?, 0x0?}, 0x0)
/home/rik/dev/neo-go/pkg/compiler/compiler.go:218 +0x7d
github.com/nspcc-dev/neo-go/pkg/compiler.Compile({0xc000c525a0?, 0xc000304340?}, {0x0?, 0x0?})
/home/rik/dev/neo-go/pkg/compiler/compiler.go:203 +0x34
github.com/nspcc-dev/neo-go/pkg/compiler_test.compileFile(...)
/home/rik/dev/neo-go/pkg/compiler/compiler_test.go:89
github.com/nspcc-dev/neo-go/pkg/compiler_test.TestCompiler.func2(0x6a2a89?)
/home/rik/dev/neo-go/pkg/compiler/compiler_test.go:61 +0x133
testing.tRunner(0xc000304340, 0xc7f5b0)
/usr/lib64/go/1.18/src/testing/testing.go:1439 +0x102
created by testing.(*T).Run
/usr/lib64/go/1.18/src/testing/testing.go:1486 +0x35f
2022-03-24 13:34:18 +00:00
|
|
|
c.scope.pkg == c.mainPkg.Types && (c.buildInfo.options == nil || !c.buildInfo.options.NoEventsCheck) {
|
2021-06-24 15:36:40 +00:00
|
|
|
c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name)
|
|
|
|
return
|
|
|
|
}
|
2021-06-04 08:47:52 +00:00
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
if f.name == "Log" {
|
|
|
|
return
|
|
|
|
}
|
2021-06-04 08:47:52 +00:00
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
// Sometimes event name is stored in a var.
|
|
|
|
// Skip in this case.
|
|
|
|
tv := c.typeAndValueOf(args[0])
|
|
|
|
if tv.Value == nil {
|
|
|
|
return
|
|
|
|
}
|
2021-06-04 08:47:52 +00:00
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
params := make([]string, 0, len(args[1:]))
|
|
|
|
for _, p := range args[1:] {
|
2022-02-11 12:16:15 +00:00
|
|
|
st, _, _ := c.scAndVMTypeFromExpr(p)
|
2021-06-24 15:36:40 +00:00
|
|
|
params = append(params, st.String())
|
|
|
|
}
|
2021-06-02 08:46:08 +00:00
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
name := constant.StringVal(tv.Value)
|
|
|
|
if len(name) > runtime.MaxEventNameLen {
|
|
|
|
c.prog.Err = fmt.Errorf("event name '%s' should be less than %d",
|
|
|
|
name, runtime.MaxEventNameLen)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.emittedEvents[name] = append(c.emittedEvents[name], params)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *codegen) processContractCall(f *funcScope, args []ast.Expr) {
|
2021-06-25 07:54:00 +00:00
|
|
|
var u util.Uint160
|
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
// For stdlib calls it is `interop.Hash160(constHash)`
|
|
|
|
// so we can determine hash at compile-time.
|
|
|
|
ce, ok := args[0].(*ast.CallExpr)
|
2021-06-25 07:54:00 +00:00
|
|
|
if ok && len(ce.Args) == 1 {
|
|
|
|
// Ensure this is a type conversion, not a simple invoke.
|
|
|
|
se, ok := ce.Fun.(*ast.SelectorExpr)
|
|
|
|
if ok {
|
|
|
|
name, _ := c.getFuncNameFromSelector(se)
|
|
|
|
if _, ok := c.funcs[name]; !ok {
|
|
|
|
value := c.typeAndValueOf(ce.Args[0]).Value
|
|
|
|
if value != nil {
|
|
|
|
s := constant.StringVal(value)
|
|
|
|
copy(u[:], s) // constant must be big-endian
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 15:36:40 +00:00
|
|
|
}
|
|
|
|
|
2021-06-25 07:54:00 +00:00
|
|
|
value := c.typeAndValueOf(args[1]).Value
|
2021-06-24 15:36:40 +00:00
|
|
|
if value == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
method := constant.StringVal(value)
|
|
|
|
|
|
|
|
value = c.typeAndValueOf(args[2]).Value
|
|
|
|
if value == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
flag, _ := constant.Uint64Val(value)
|
2021-12-08 19:33:03 +00:00
|
|
|
c.appendInvokedContract(u, method, flag)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *codegen) appendInvokedContract(u util.Uint160, method string, flag uint64) {
|
|
|
|
currLst := c.invokedContracts[u]
|
|
|
|
for _, m := range currLst {
|
|
|
|
if m == method {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:36:40 +00:00
|
|
|
if flag&uint64(callflag.WriteStates|callflag.AllowNotify) != 0 {
|
|
|
|
c.invokedContracts[u] = append(currLst, method)
|
2021-06-02 08:46:08 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-25 13:02:28 +00:00
|
|
|
|
|
|
|
// hasCalls returns true if expression contains any calls.
|
|
|
|
// We uses this as a rough heuristic to determine if expression calculation
|
|
|
|
// has any side-effects.
|
|
|
|
func (c *codegen) hasCalls(expr ast.Expr) bool {
|
|
|
|
var has bool
|
|
|
|
ast.Inspect(expr, func(n ast.Node) bool {
|
2021-05-25 13:17:40 +00:00
|
|
|
ce, ok := n.(*ast.CallExpr)
|
|
|
|
if !has && ok {
|
|
|
|
isFunc := true
|
|
|
|
fun, ok := ce.Fun.(*ast.Ident)
|
|
|
|
if ok {
|
|
|
|
_, isFunc = c.getFuncFromIdent(fun)
|
|
|
|
} else {
|
|
|
|
var sel *ast.SelectorExpr
|
|
|
|
sel, ok = ce.Fun.(*ast.SelectorExpr)
|
|
|
|
if ok {
|
|
|
|
name, _ := c.getFuncNameFromSelector(sel)
|
|
|
|
_, isFunc = c.funcs[name]
|
|
|
|
fun = sel.Sel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
has = isFunc || fun.Obj != nil && (fun.Obj.Kind == ast.Var || fun.Obj.Kind == ast.Fun)
|
2021-05-25 13:02:28 +00:00
|
|
|
}
|
|
|
|
return !has
|
|
|
|
})
|
|
|
|
return has
|
|
|
|
}
|