compiler: do not enforce variadic event args check on ellipsis usage

In case of ellipsis usage compiler defines argument type as ArrayT
(which is correct, because it's a natural representation of the last
argument, it represents the array of interface{}).
Here goes the problem:
```
=== RUN   TestEventWarnings/variadic_event_args_via_ellipsis
    compiler_test.go:251:
        	Error Trace:	compiler_test.go:251
        	Error:      	Received unexpected error:
        	            	event 'Event' should have 'Integer' as type of 1 parameter, got: Array
        	Test:       	TestEventWarnings/variadic_event_args_via_ellipsis
```

Parsing the last argument in this case is a separate complicated problem
due to the fact that we need to grab types of elements of []interface{} inside the
fully qualified ast node which may looks like:
```
runtime.Notify("Event", (append([]interface{}{1, 2}, (([]interface{}{someVar, 4}))...))...)
```

Temporary solution is to exclude such notifications from analysis until we're
able to properly resolve element types of []interface{}.
This commit is contained in:
Anna Shaleva 2022-09-29 15:41:53 +03:00
parent 08427f23b6
commit 80f71a4e6e
2 changed files with 27 additions and 7 deletions

View file

@ -230,6 +230,25 @@ func TestEventWarnings(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
}) })
t.Run("variadic event args via ellipsis", func(t *testing.T) {
src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main() {
runtime.Notify("Event", []interface{}{1}...)
}`
_, di, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), nil)
require.NoError(t, err)
_, err = compiler.CreateManifest(di, &compiler.Options{
Name: "eventTest",
ContractEvents: []manifest.Event{{
Name: "Event",
Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)},
}},
})
require.NoError(t, err)
})
} }
func TestNotifyInVerify(t *testing.T) { func TestNotifyInVerify(t *testing.T) {

View file

@ -36,7 +36,8 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
pkg := c.packageCache[f.pkg.Path()] pkg := c.packageCache[f.pkg.Path()]
sig := c.typeOf(n.Fun).(*types.Signature) sig := c.typeOf(n.Fun).(*types.Signature)
c.processStdlibCall(f, n.Args) hasVarArgs := !n.Ellipsis.IsValid()
c.processStdlibCall(f, n.Args, !hasVarArgs)
// When inlined call is used during global initialization // When inlined call is used during global initialization
// there is no func scope, thus this if. // there is no func scope, thus this if.
@ -68,7 +69,6 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
scope: oldScope, scope: oldScope,
}) })
} }
hasVarArgs := !n.Ellipsis.IsValid()
needPack := sig.Variadic() && hasVarArgs needPack := sig.Variadic() && hasVarArgs
for i := range n.Args { for i := range n.Args {
c.scope.vars.locals = oldScope c.scope.vars.locals = oldScope
@ -129,13 +129,13 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
c.pkgInfoInline = c.pkgInfoInline[:len(c.pkgInfoInline)-1] c.pkgInfoInline = c.pkgInfoInline[:len(c.pkgInfoInline)-1]
} }
func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) { func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr, hasEllipsis bool) {
if f == nil { if f == nil {
return return
} }
if f.pkg.Path() == interopPrefix+"/runtime" && (f.name == "Notify" || f.name == "Log") { if f.pkg.Path() == interopPrefix+"/runtime" && (f.name == "Notify" || f.name == "Log") {
c.processNotify(f, args) c.processNotify(f, args, hasEllipsis)
} }
if f.pkg.Path() == interopPrefix+"/contract" && f.name == "Call" { if f.pkg.Path() == interopPrefix+"/contract" && f.name == "Call" {
@ -143,7 +143,7 @@ func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) {
} }
} }
func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool) {
if c.scope != nil && c.isVerifyFunc(c.scope.decl) && if c.scope != nil && c.isVerifyFunc(c.scope.decl) &&
c.scope.pkg == c.mainPkg.Types && (c.buildInfo.options == nil || !c.buildInfo.options.NoEventsCheck) { 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) c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name)
@ -154,10 +154,11 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr) {
return return
} }
// Sometimes event name is stored in a var. // Sometimes event name is stored in a var. Or sometimes event args are provided
// via ellipses (`slice...`).
// Skip in this case. // Skip in this case.
tv := c.typeAndValueOf(args[0]) tv := c.typeAndValueOf(args[0])
if tv.Value == nil { if tv.Value == nil || hasEllipsis {
return return
} }