Skip to content

Commit e352770

Browse files
grepsuzettethehowl
andauthored
fix(amino): panic when registering types with the same name (gnolang#2325)
This adds some doc and tests to `tm2/pkg/amino` to address the following in amino_test.go: fixed gnolang#2326 `// XXX Test registering duplicate names or concrete types not in a package.` - **chore(docs): document frequent functions in tm2/pkg/amino** - **chore(amino): add some tests** ## To run tests ``` cd tm2/pkg/amino go test -v --run=WithPanic\$ ``` Tests have uncovered a potential bug however in `TestDupNamesMustPanic`. Opening an issue now to document this, with a possible fix. ```go // The following does NOT panic, but it should. // assert.Panics(t, func() { // myPkg.WithTypes( // tests.EmptyStruct{}, "A", // tests.PrimitivesStruct{}, "B", // tests.ShortArraysStruct{}, "A", // Same name // ) // }) ``` <details><summary>Contributors' checklist...</summary> - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [x] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details> --------- Co-authored-by: grepsuzette <[email protected]> Co-authored-by: Morgan Bazalgette <[email protected]>
1 parent f4324fb commit e352770

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

tm2/pkg/amino/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ var Package = amino.RegisterPackage(
8484
)
8585
```
8686

87-
You can still override global registrations with local \*amino.Codec state.
88-
This is used by genproto.P3Context, which may help development while writing
87+
You can still override global registrations with local `*amino.Codec` state.
88+
This is used by `genproto.P3Context`, which may help development while writing
8989
migration scripts. Feedback welcome in the issues section.
9090

9191
## Unsupported types

tm2/pkg/amino/amino.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -888,17 +888,25 @@ func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byt
888888
// ----------------------------------------
889889
// Other
890890

891+
// Given amino package `pi`, register it with the global codec.
891892
// NOTE: do not modify the result.
892893
func RegisterPackage(pi *pkg.Package) *Package {
893894
gcdc.RegisterPackage(pi)
894895
return pi
895896
}
896897

898+
// Create an unregistered amino package with args:
899+
// - (gopkg string) The Go package path, e.g. "github.com/gnolang/gno/tm2/pkg/std"
900+
// - (p3pkg string) The (shorter) Proto3 package path (no slashes), e.g. "std"
901+
// - (dirname string) Package directory this is called from. Typical is to use `amino.GetCallersDirname()`
897902
func NewPackage(gopkg string, p3pkg string, dirname string) *Package {
898903
return pkg.NewPackage(gopkg, p3pkg, dirname)
899904
}
900905

901-
// NOTE: duplicated in pkg/pkg.go
906+
// Get caller's package directory.
907+
// Implementation uses `filepath.Dir(runtime.Caller(1))`.
908+
// NOTE: duplicated in pkg/pkg.go; given what it does and how,
909+
// both are probably needed.
902910
func GetCallersDirname() string {
903911
dirname := "" // derive from caller.
904912
_, filename, _, ok := runtime.Caller(1)

tm2/pkg/amino/codec_test.go

+67-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package amino_test
33
import (
44
"bytes"
55
"encoding/binary"
6+
"reflect"
67
"strings"
78
"testing"
89
"time"
@@ -236,4 +237,69 @@ func TestCodecSeal(t *testing.T) {
236237
assert.Panics(t, func() { cdc.RegisterPackage(tests.Package) })
237238
}
238239

239-
// XXX Test registering duplicate names or concrete types not in a package.
240+
func TestDupTypesMustPanic(t *testing.T) {
241+
// duplicate types must panic
242+
t.Parallel()
243+
244+
pkg := amino.NewPackage(
245+
reflect.TypeOf(SimpleStruct{}).PkgPath(),
246+
"amino_test",
247+
amino.GetCallersDirname(),
248+
)
249+
assert.PanicsWithError(
250+
t,
251+
"type amino_test.SimpleStruct already registered with package",
252+
func() {
253+
pkg.WithTypes(
254+
SimpleStruct{},
255+
SimpleStruct{},
256+
)
257+
})
258+
}
259+
260+
func TestTypesOutsidePackageMustPanic(t *testing.T) {
261+
// adding concrete types from within a different package must panic
262+
// (use dependency instead)
263+
t.Parallel()
264+
265+
makepkg := func() *amino.Package {
266+
return amino.NewPackage(
267+
reflect.TypeOf(tests.EmptyStruct{}).PkgPath(),
268+
"amino_test",
269+
amino.GetCallersDirname(),
270+
)
271+
}
272+
273+
makepkg().WithTypes(tests.PrimitivesStruct{}) // from same package ✓
274+
275+
assert.Panics(t, func() {
276+
makepkg().WithTypes(
277+
SimpleStruct{}, // from another package ✗
278+
)
279+
})
280+
}
281+
282+
func TestDupNamesMustPanic(t *testing.T) {
283+
// adding types with the same names must panic
284+
t.Parallel()
285+
286+
makepkg := func() *amino.Package {
287+
return amino.NewPackage(
288+
reflect.TypeOf(tests.EmptyStruct{}).PkgPath(),
289+
"amino_test",
290+
amino.GetCallersDirname(),
291+
)
292+
}
293+
makepkg().WithTypes(
294+
tests.EmptyStruct{}, "A",
295+
tests.PrimitivesStruct{}, "B",
296+
tests.ShortArraysStruct{}, "C",
297+
)
298+
assert.Panics(t, func() {
299+
makepkg().WithTypes(
300+
tests.EmptyStruct{}, "A",
301+
tests.PrimitivesStruct{}, "B",
302+
tests.ShortArraysStruct{}, "A", // Same name!
303+
)
304+
})
305+
}

tm2/pkg/amino/pkg/pkg.go

+24
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,24 @@ func (pkg *Package) WithGoPkgName(name string) *Package {
118118
return pkg
119119
}
120120

121+
// Package dependencies need to be declared (for now).
122+
// If a package has no dependency, it is conventional to
123+
// use `.WithDependencies()` with no arguments.
121124
func (pkg *Package) WithDependencies(deps ...*Package) *Package {
122125
pkg.Dependencies = append(pkg.Dependencies, deps...)
123126
return pkg
124127
}
125128

129+
// WithType specifies which types are encoded and decoded by the package.
130+
// You must provide a list of instantiated objects in the arguments.
131+
// Each type declaration may be optionally followed by a string which is then
132+
// used as its name.
133+
//
134+
// pkg.WithTypes(
135+
// StructA{},
136+
// &StructB{}, // If pointer receivers are preferred when decoding to interfaces.
137+
// NoInputsError{}, "NoInputsError", // Named
138+
// )
126139
func (pkg *Package) WithTypes(objs ...interface{}) *Package {
127140
var lastType *Type = nil
128141
for _, obj := range objs {
@@ -183,6 +196,17 @@ func (pkg *Package) WithTypes(objs ...interface{}) *Package {
183196
}
184197
pkg.Types = append(pkg.Types, lastType)
185198
}
199+
seen := make(map[string]struct{})
200+
for _, tp := range pkg.Types {
201+
// tp.Name is "" for cases like nativePkg, containing go native types.
202+
if tp.Name == "" {
203+
continue
204+
}
205+
if _, ok := seen[tp.Name]; ok {
206+
panic("duplicate type name " + tp.Name)
207+
}
208+
seen[tp.Name] = struct{}{}
209+
}
186210
return pkg
187211
}
188212

0 commit comments

Comments
 (0)