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 <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-08-11 16:59:31 +03:00
parent b4c0fcfaad
commit 16d1d1e5eb
8 changed files with 139 additions and 0 deletions

View file

@ -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`") 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`")
})
})
}

View file

@ -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})
}

View file

@ -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

View file

@ -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})
}

View file

@ -0,0 +1,6 @@
name: Test duplicating autogenerated event fields
events:
- name: SomeEvent
parameters:
- name: p1
type: Struct

View file

@ -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
}

View file

@ -0,0 +1 @@
name: Test duplicating struct fields

View file

@ -430,6 +430,28 @@ func Generate(cfg binding.Config) error {
ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo) ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo)
ctr.NamedTypes = cfg.NamedTypes 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{ var srcTemplate = template.Must(template.New("generate").Funcs(template.FuncMap{
"addIndent": addIndent, "addIndent": addIndent,
"etTypeConverter": etTypeConverter, "etTypeConverter": etTypeConverter,