diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index e0eaee510..317838ece 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -2,6 +2,7 @@ package compiler import ( "errors" + "fmt" "go/ast" "go/token" "go/types" @@ -12,6 +13,9 @@ import ( "golang.org/x/tools/go/packages" ) +// ErrMissingExportedParamName is returned when exported contract method has unnamed parameter. +var ErrMissingExportedParamName = errors.New("exported method is not allowed to have unnamed parameter") + var ( // Go language builtin functions. goBuiltins = []string{"len", "append", "panic", "make", "copy", "recover", "delete"} @@ -284,12 +288,31 @@ func (c *codegen) analyzeFuncUsage() funcUsage { if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) { diff[name] = true } + if isMain && n.Name.IsExported() { + if n.Type.Params.List != nil { + for i, param := range n.Type.Params.List { + if param.Names == nil { + c.prog.Err = fmt.Errorf("%w: %s", ErrMissingExportedParamName, n.Name) + return false // Program is invalid. + } + for _, name := range param.Names { + if name == nil || name.Name == "_" { + c.prog.Err = fmt.Errorf("%w: %s/%d", ErrMissingExportedParamName, n.Name, i) + return false // Program is invalid. + } + } + } + } + } nodeCache[name] = declPair{n, c.importMap, pkgPath} return false // will be processed in the next stage } return true }) }) + if c.prog.Err != nil { + return nil + } usage := funcUsage{} for len(diff) != 0 { diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 33cec05f3..a554dae80 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -2106,6 +2106,9 @@ func (c *codegen) compile(info *buildInfo, pkg *packages.Package) error { c.analyzePkgOrder() c.fillDocumentInfo() funUsage := c.analyzeFuncUsage() + if c.prog.Err != nil { + return c.prog.Err + } // Bring all imported functions into scope. c.ForEachFile(c.resolveFuncDecls) diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index 7dcd59786..2e04251e4 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -343,3 +343,73 @@ func TestInvokedContractsPermissons(t *testing.T) { }) }) } + +func TestUnnamedParameterCheck(t *testing.T) { + t.Run("single argument", func(t *testing.T) { + src := ` + package testcase + func Main(_ int) int { + x := 10 + return x + } + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.Error(t, err) + require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) + }) + t.Run("several arguments", func(t *testing.T) { + src := ` + package testcase + func Main(a int, b string, _ int) int { + x := 10 + return x + } + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.Error(t, err) + require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) + }) + t.Run("interface", func(t *testing.T) { + src := ` + package testcase + func OnNEP17Payment(h string, i int, _ interface{}){} + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.Error(t, err) + require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) + }) + t.Run("a set of unnamed params", func(t *testing.T) { + src := ` + package testcase + func OnNEP17Payment(_ string, _ int, _ interface{}){} + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.Error(t, err) + require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) + }) + t.Run("mixed named and unnamed params", func(t *testing.T) { + src := ` + package testcase + func OnNEP17Payment(s0, _, s2 string){} + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.Error(t, err) + require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) + }) + t.Run("empty args", func(t *testing.T) { + src := ` + package testcase + func OnNEP17Payment(){} + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.NoError(t, err) + }) + t.Run("good", func(t *testing.T) { + src := ` + package testcase + func OnNEP17Payment(s string, i int, iface interface{}){} + ` + _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) + require.NoError(t, err) + }) +}