neo-go/pkg/compiler/inline.go

318 lines
9.5 KiB
Go
Raw Normal View History

2021-02-04 12:41:00 +00:00
package compiler
import (
"fmt"
2021-02-04 12:41:00 +00:00
"go/ast"
"go/constant"
2021-02-04 12:41:00 +00:00
"go/types"
"slices"
2021-02-04 12:41:00 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
2021-02-04 12:41:00 +00:00
)
// 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:
//
// {
// 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) {
offSz := len(c.inlineContext)
c.inlineContext = append(c.inlineContext, inlineContextSingle{
labelOffset: len(c.labelList),
returnLabel: c.newLabel(),
})
defer func() {
c.labelList = c.labelList[:c.inlineContext[offSz].labelOffset]
c.inlineContext = c.inlineContext[:offSz]
}()
pkg := c.packageCache[f.pkg.Path()]
2021-02-04 12:41:00 +00:00
sig := c.typeOf(n.Fun).(*types.Signature)
hasVarArgs := !n.Ellipsis.IsValid()
eventParams := c.processStdlibCall(f, n.Args, !hasVarArgs)
// 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()
defer func() {
c.globalInlineCount = max(c.globalInlineCount, c.scope.vars.localsCnt)
c.scope = nil
}()
}
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()
newScope := slices.Clone(c.scope.vars.locals)
2021-02-04 12:41:00 +00:00
defer c.scope.vars.dropScope()
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,
})
}
needPack := sig.Variadic() && hasVarArgs
2021-02-04 12:41:00 +00:00
for i := range n.Args {
c.scope.vars.locals = oldScope
// true if normal arg or var arg is `slice...`
needStore := i < sig.Params().Len()-1 || !sig.Variadic() || !hasVarArgs
if !needStore {
break
}
name := sig.Params().At(i).Name()
if !c.hasCalls(n.Args[i]) {
// If argument contains no calls, we save context and traverse the expression
// when argument is emitted.
c.scope.vars.locals = newScope
c.scope.vars.addAlias(name, -1, unspecifiedVarIndex, &varContext{
importMap: c.importMap,
expr: n.Args[i],
scope: oldScope,
})
continue
}
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)
}
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])
// In case of runtime.Notify, its arguments need to be converted to proper type.
// i's initial value is 1 (variadic args start).
if eventParams != nil && eventParams[i-1] != nil {
c.emitConvert(*eventParams[i-1])
}
}
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
oldDefers := c.scope.deferStack
c.scope.deferStack = nil
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)
c.setLabel(c.inlineContext[offSz].returnLabel)
2021-02-04 12:41:00 +00:00
if c.scope.voidCalls[n] {
for range f.decl.Type.Results.NumFields() {
2021-02-04 12:41:00 +00:00
emit.Opcodes(c.prog.BinWriter, opcode.DROP)
}
}
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]
}
func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr, hasEllipsis bool) []*stackitem.Type {
if f == nil {
return nil
}
var eventParams []*stackitem.Type
if f.pkg.Path() == interopPrefix+"/runtime" && (f.name == "Notify" || f.name == "Log") {
eventParams = c.processNotify(f, args, hasEllipsis)
}
if f.pkg.Path() == interopPrefix+"/contract" && f.name == "Call" {
c.processContractCall(f, args)
}
return eventParams
}
// processNotify checks whether notification emitting rules are met and returns expected
// notification signature if found.
func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool) []*stackitem.Type {
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) {
c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name)
return nil
}
if f.name == "Log" {
return nil
}
// Sometimes event name is stored in a var. Or sometimes event args are provided
// via ellipses (`slice...`).
// Skip in this case. Also, don't enforce runtime.Notify parameters conversion.
tv := c.typeAndValueOf(args[0])
if tv.Value == nil || hasEllipsis {
return nil
}
params := make([]DebugParam, 0, len(args[1:]))
vParams := make([]*stackitem.Type, 0, len(args[1:]))
// extMap holds the extended parameter types used for the given event call.
// It will be unified with the common extMap later during bindings config
// generation.
extMap := make(map[string]binding.ExtendedType)
for _, p := range args[1:] {
st, vt, over, extT := c.scAndVMTypeFromExpr(p, extMap)
params = append(params, DebugParam{
Name: "", // Parameter name will be filled in several lines below if the corresponding event exists in the buildinfo.options.
Type: vt.String(),
RealType: over,
ExtendedType: extT,
TypeSC: st,
})
vParams = append(vParams, &vt)
}
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 nil
}
var eventFound bool
if c.buildInfo.options != nil && c.buildInfo.options.ContractEvents != nil {
for _, e := range c.buildInfo.options.ContractEvents {
if e.Name == name && len(e.Parameters) == len(vParams) {
eventFound = true
for i, scParam := range e.Parameters {
params[i].Name = scParam.Name
if !c.buildInfo.options.NoEventsCheck {
expectedType := scParam.Type.ConvertToStackitemType()
// No need to cast if the desired type is unknown.
if expectedType == stackitem.AnyT ||
// Do not cast if desired type is Interop, the actual type is likely to be Any, leave the resolving to runtime.Notify.
expectedType == stackitem.InteropT ||
// No need to cast if actual parameter type matches the desired one.
*vParams[i] == expectedType ||
// expectedType doesn't contain Buffer anyway, but if actual variable type is Buffer,
// then runtime.Notify will convert it to ByteArray automatically, thus no need to emit conversion code.
(*vParams[i] == stackitem.BufferT && expectedType == stackitem.ByteArrayT) {
vParams[i] = nil
} else {
// For other cases the conversion code will be emitted using vParams...
vParams[i] = &expectedType
// ...thus, update emitted notification info in advance.
params[i].Type = scParam.Type.String()
params[i].TypeSC = scParam.Type
}
}
}
}
}
}
c.emittedEvents[name] = append(c.emittedEvents[name], EmittedEventInfo{
ExtTypes: extMap,
Params: params,
})
// Do not enforce perfect expected/actual events match on this step, the final
// check wil be performed after compilation if --no-events option is off.
if eventFound && !c.buildInfo.options.NoEventsCheck {
return vParams
}
return nil
}
func (c *codegen) processContractCall(f *funcScope, args []ast.Expr) {
var u util.Uint160
// For stdlib calls it is `interop.Hash160(constHash)`
// so we can determine hash at compile-time.
ce, ok := args[0].(*ast.CallExpr)
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
}
}
}
}
value := c.typeAndValueOf(args[1]).Value
if value == nil {
return
}
method := constant.StringVal(value)
value = c.typeAndValueOf(args[2]).Value
if value == nil {
return
}
flag, _ := constant.Uint64Val(value)
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
}
}
if flag&uint64(callflag.WriteStates|callflag.AllowNotify) != 0 {
c.invokedContracts[u] = append(currLst, method)
}
}
// 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 {
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)
}
return !has
})
return has
}