Skip to content

Commit

Permalink
fix(amino): panic when registering types with the same name (gnolang#…
Browse files Browse the repository at this point in the history
…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 <grepsuzette@users.noreply.github.com>
Co-authored-by: Morgan Bazalgette <morgan@morganbaz.com>
  • Loading branch information
3 people authored Feb 28, 2025
1 parent f4324fb commit e352770
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 4 deletions.
4 changes: 2 additions & 2 deletions tm2/pkg/amino/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ var Package = amino.RegisterPackage(
)
```

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

## Unsupported types
Expand Down
10 changes: 9 additions & 1 deletion tm2/pkg/amino/amino.go
Original file line number Diff line number Diff line change
Expand Up @@ -888,17 +888,25 @@ func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byt
// ----------------------------------------
// Other

// Given amino package `pi`, register it with the global codec.
// NOTE: do not modify the result.
func RegisterPackage(pi *pkg.Package) *Package {
gcdc.RegisterPackage(pi)
return pi
}

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

// NOTE: duplicated in pkg/pkg.go
// Get caller's package directory.
// Implementation uses `filepath.Dir(runtime.Caller(1))`.
// NOTE: duplicated in pkg/pkg.go; given what it does and how,
// both are probably needed.
func GetCallersDirname() string {
dirname := "" // derive from caller.
_, filename, _, ok := runtime.Caller(1)
Expand Down
68 changes: 67 additions & 1 deletion tm2/pkg/amino/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package amino_test
import (
"bytes"
"encoding/binary"
"reflect"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -236,4 +237,69 @@ func TestCodecSeal(t *testing.T) {
assert.Panics(t, func() { cdc.RegisterPackage(tests.Package) })
}

// XXX Test registering duplicate names or concrete types not in a package.
func TestDupTypesMustPanic(t *testing.T) {
// duplicate types must panic
t.Parallel()

pkg := amino.NewPackage(
reflect.TypeOf(SimpleStruct{}).PkgPath(),
"amino_test",
amino.GetCallersDirname(),
)
assert.PanicsWithError(
t,
"type amino_test.SimpleStruct already registered with package",
func() {
pkg.WithTypes(
SimpleStruct{},
SimpleStruct{},
)
})
}

func TestTypesOutsidePackageMustPanic(t *testing.T) {
// adding concrete types from within a different package must panic
// (use dependency instead)
t.Parallel()

makepkg := func() *amino.Package {
return amino.NewPackage(
reflect.TypeOf(tests.EmptyStruct{}).PkgPath(),
"amino_test",
amino.GetCallersDirname(),
)
}

makepkg().WithTypes(tests.PrimitivesStruct{}) // from same package ✓

assert.Panics(t, func() {
makepkg().WithTypes(
SimpleStruct{}, // from another package ✗
)
})
}

func TestDupNamesMustPanic(t *testing.T) {
// adding types with the same names must panic
t.Parallel()

makepkg := func() *amino.Package {
return amino.NewPackage(
reflect.TypeOf(tests.EmptyStruct{}).PkgPath(),
"amino_test",
amino.GetCallersDirname(),
)
}
makepkg().WithTypes(
tests.EmptyStruct{}, "A",
tests.PrimitivesStruct{}, "B",
tests.ShortArraysStruct{}, "C",
)
assert.Panics(t, func() {
makepkg().WithTypes(
tests.EmptyStruct{}, "A",
tests.PrimitivesStruct{}, "B",
tests.ShortArraysStruct{}, "A", // Same name!
)
})
}
24 changes: 24 additions & 0 deletions tm2/pkg/amino/pkg/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,24 @@ func (pkg *Package) WithGoPkgName(name string) *Package {
return pkg
}

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

// WithType specifies which types are encoded and decoded by the package.
// You must provide a list of instantiated objects in the arguments.
// Each type declaration may be optionally followed by a string which is then
// used as its name.
//
// pkg.WithTypes(
// StructA{},
// &StructB{}, // If pointer receivers are preferred when decoding to interfaces.
// NoInputsError{}, "NoInputsError", // Named
// )
func (pkg *Package) WithTypes(objs ...interface{}) *Package {
var lastType *Type = nil
for _, obj := range objs {
Expand Down Expand Up @@ -183,6 +196,17 @@ func (pkg *Package) WithTypes(objs ...interface{}) *Package {
}
pkg.Types = append(pkg.Types, lastType)
}
seen := make(map[string]struct{})
for _, tp := range pkg.Types {
// tp.Name is "" for cases like nativePkg, containing go native types.
if tp.Name == "" {
continue
}
if _, ok := seen[tp.Name]; ok {
panic("duplicate type name " + tp.Name)
}
seen[tp.Name] = struct{}{}
}
return pkg
}

Expand Down

0 comments on commit e352770

Please sign in to comment.