Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(amino): panic when registering types with the same name #2325

Merged
merged 9 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading