From 16d1d1e5eb1f1c500ea93870c14163874b163ffd Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 11 Aug 2023 16:59:31 +0300 Subject: [PATCH] rpcbinding: check duplicating struct fields before binding generation RPC binding config may be malformed or the source .go contract may contain structures like this: ``` type Str struct { Field int field int } ``` We need to recognise these cases and return error. otherwise the resulting binding can't be compiled. Signed-off-by: Anna Shaleva --- cli/smartcontract/generate_test.go | 48 +++++++++++++++++++ .../testdata/rpcbindings/invalid6/invalid.go | 14 ++++++ .../testdata/rpcbindings/invalid6/invalid.yml | 18 +++++++ .../testdata/rpcbindings/invalid7/invalid.go | 14 ++++++ .../testdata/rpcbindings/invalid7/invalid.yml | 6 +++ .../testdata/rpcbindings/invalid8/invalid.go | 16 +++++++ .../testdata/rpcbindings/invalid8/invalid.yml | 1 + pkg/smartcontract/rpcbinding/binding.go | 22 +++++++++ 8 files changed, 139 insertions(+) create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go create mode 100644 cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index 7f46e826a..d5674e5b0 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -562,3 +562,51 @@ func TestCompile_GuessEventTypes(t *testing.T) { check(t, filepath.Join("testdata", "rpcbindings", "invalid5"), "configured declared named type intersects with the contract's one: `invalid5.NamedStruct`") }) } + +func TestGenerateRPCBindings_Errors(t *testing.T) { + app := cli.NewApp() + app.Commands = NewCommands() + app.ExitErrHandler = func(*cli.Context, error) {} + + t.Run("duplicating resulting fields", func(t *testing.T) { + check := func(t *testing.T, packageName string, autogen bool, expectedError string) { + tmpDir := t.TempDir() + source := filepath.Join("testdata", "rpcbindings", packageName) + configFile := filepath.Join(source, "invalid.yml") + out := filepath.Join(tmpDir, "rpcbindings.out") + manifestF := filepath.Join(tmpDir, "manifest.json") + bindingF := filepath.Join(tmpDir, "binding.yml") + nefF := filepath.Join(tmpDir, "out.nef") + cmd := []string{"", "contract", "compile", + "--in", source, + "--config", configFile, + "--manifest", manifestF, + "--bindings", bindingF, + "--out", nefF, + } + if autogen { + cmd = append(cmd, "--guess-eventtypes") + } + require.NoError(t, app.Run(cmd)) + + cmds := []string{"", "contract", "generate-rpcwrapper", + "--config", bindingF, + "--manifest", manifestF, + "--out", out, + } + err := app.Run(cmds) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), expectedError), err.Error()) + } + + t.Run("event", func(t *testing.T) { + check(t, "invalid6", false, "error during generation: named type `SomeStruct` has two fields with identical resulting binding name `Field`") + }) + t.Run("autogen event", func(t *testing.T) { + check(t, "invalid7", true, "error during generation: named type `invalid7.SomeStruct` has two fields with identical resulting binding name `Field`") + }) + t.Run("struct", func(t *testing.T) { + check(t, "invalid8", false, "error during generation: named type `invalid8.SomeStruct` has two fields with identical resulting binding name `Field`") + }) + }) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go new file mode 100644 index 000000000..2b210e733 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.go @@ -0,0 +1,14 @@ +package invalid6 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() { + runtime.Notify("SomeEvent", SomeStruct{Field: 123, field: 123}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml new file mode 100644 index 000000000..c9fe64b23 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml @@ -0,0 +1,18 @@ +name: Test duplicating event fields +events: + - name: SomeEvent + parameters: + - name: p1 + type: Struct + extendedtype: + base: Struct + name: SomeStruct +namedtypes: + SomeStruct: + base: Struct + name: SomeStruct + fields: + - field: Field + base: Integer + - field: field + base: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go new file mode 100644 index 000000000..6cb9e6a68 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.go @@ -0,0 +1,14 @@ +package invalid7 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() { + runtime.Notify("SomeEvent", SomeStruct{Field: 123, field: 123}) +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml new file mode 100644 index 000000000..cbc8c5674 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid7/invalid.yml @@ -0,0 +1,6 @@ +name: Test duplicating autogenerated event fields +events: + - name: SomeEvent + parameters: + - name: p1 + type: Struct diff --git a/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go new file mode 100644 index 000000000..db9652324 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.go @@ -0,0 +1,16 @@ +package invalid8 + +type SomeStruct struct { + Field int + // RPC binding generator will convert this field into exported, which matches + // exactly the existing Field. + field int +} + +func Main() SomeStruct { + s := SomeStruct{ + Field: 1, + field: 2, + } + return s +} diff --git a/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml new file mode 100644 index 000000000..2b308a573 --- /dev/null +++ b/cli/smartcontract/testdata/rpcbindings/invalid8/invalid.yml @@ -0,0 +1 @@ +name: Test duplicating struct fields diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 3c2c85db2..942791d9a 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -430,6 +430,28 @@ func Generate(cfg binding.Config) error { ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo) ctr.NamedTypes = cfg.NamedTypes + // Check resulting named types and events don't have duplicating field names. + for _, t := range ctr.NamedTypes { + fDict := make(map[string]struct{}) + for _, n := range t.Fields { + name := upperFirst(n.Field) + if _, ok := fDict[name]; ok { + return fmt.Errorf("named type `%s` has two fields with identical resulting binding name `%s`", t.Name, name) + } + fDict[name] = struct{}{} + } + } + for _, e := range ctr.CustomEvents { + fDict := make(map[string]struct{}) + for _, n := range e.Parameters { + name := upperFirst(n.Name) + if _, ok := fDict[name]; ok { + return fmt.Errorf("event `%s` has two fields with identical resulting binding name `%s`", e.Name, name) + } + fDict[name] = struct{}{} + } + } + var srcTemplate = template.Must(template.New("generate").Funcs(template.FuncMap{ "addIndent": addIndent, "etTypeConverter": etTypeConverter,