From 80f71a4e6efbcc9030a562cb856d6c400590ca24 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 29 Sep 2022 15:41:53 +0300 Subject: [PATCH] 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{}. --- pkg/compiler/compiler_test.go | 19 +++++++++++++++++++ pkg/compiler/inline.go | 15 ++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index 7fc6db6a6..142c33110 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -230,6 +230,25 @@ func TestEventWarnings(t *testing.T) { 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) { diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index 3fa502b0f..56884830a 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -36,7 +36,8 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) { pkg := c.packageCache[f.pkg.Path()] 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 // there is no func scope, thus this if. @@ -68,7 +69,6 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) { scope: oldScope, }) } - hasVarArgs := !n.Ellipsis.IsValid() needPack := sig.Variadic() && hasVarArgs for i := range n.Args { 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] } -func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr) { +func (c *codegen) processStdlibCall(f *funcScope, args []ast.Expr, hasEllipsis bool) { if f == nil { return } 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" { @@ -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) && 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) @@ -154,10 +154,11 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { 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. tv := c.typeAndValueOf(args[0]) - if tv.Value == nil { + if tv.Value == nil || hasEllipsis { return }