github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/codec.go (about)

     1  package amino
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/gnolang/gno/tm2/pkg/amino/pkg"
    12  )
    13  
    14  // Useful for debugging.
    15  const printLog = false
    16  
    17  // ----------------------------------------
    18  // Codec internals
    19  
    20  type TypeInfo struct {
    21  	Type      reflect.Type // never a pointer kind.
    22  	Package   *Package     // package associated with Type.
    23  	PtrToType reflect.Type
    24  	ZeroValue reflect.Value
    25  	InterfaceInfo
    26  	ConcreteInfo
    27  	StructInfo
    28  }
    29  
    30  type InterfaceInfo struct{}
    31  
    32  type ConcreteInfo struct {
    33  	Registered            bool      // Registered with Register*().
    34  	Name                  string    // Registered name which may override default reflection name.
    35  	PointerPreferred      bool      // Deserialize to pointer type if possible.
    36  	TypeURL               string    // <domain and path>/<p3 package no slashes>.<Name>
    37  	IsAminoMarshaler      bool      // Implements MarshalAmino() (<ReprObject>, error) and UnmarshalAmino(<ReprObject>) (error).
    38  	ReprType              *TypeInfo // <ReprType> if IsAminoMarshaler, that, or by default the identity Type.
    39  	IsJSONValueType       bool      // If true, the Any representation uses the "value" field (instead of embedding @type).
    40  	IsBinaryWellKnownType bool      // If true, use built-in functions to encode/decode.
    41  	IsJSONWellKnownType   bool      // If true, use built-in functions to encode/decode.
    42  	IsJSONAnyValueType    bool      // If true, the interface/Any representation uses the "value" field.
    43  	Elem                  *TypeInfo // Set if Type.Kind() is Slice or Array.
    44  	ElemIsPtr             bool      // Set true iff Type.Elem().Kind() is Pointer.
    45  }
    46  
    47  type StructInfo struct {
    48  	Fields []FieldInfo // If a struct.
    49  }
    50  
    51  type FieldInfo struct {
    52  	Type         reflect.Type  // Struct field reflect.Type.
    53  	TypeInfo     *TypeInfo     // Dereferenced struct field TypeInfo
    54  	Name         string        // Struct field name
    55  	Index        int           // Struct field index
    56  	ZeroValue    reflect.Value // Could be nil pointer unlike TypeInfo.ZeroValue.
    57  	UnpackedList bool          // True iff this field should be encoded as an unpacked list.
    58  	FieldOptions               // Encoding options
    59  }
    60  
    61  type FieldOptions struct {
    62  	JSONName      string // (JSON) field name
    63  	JSONOmitEmpty bool   // (JSON) omitempty
    64  	BinFixed64    bool   // (Binary) Encode as fixed64
    65  	BinFixed32    bool   // (Binary) Encode as fixed32
    66  	BinFieldNum   uint32 // (Binary) max 1<<29-1
    67  
    68  	Unsafe         bool // e.g. if this field is a float.
    69  	WriteEmpty     bool // write empty structs and lists (default false except for pointers)
    70  	NilElements    bool // Empty list elements are decoded as nil iff set, otherwise are never nil.
    71  	UseGoogleTypes bool // If true, decodes Any timestamp and duration to google types.
    72  }
    73  
    74  // ----------------------------------------
    75  // TypeInfo convenience
    76  
    77  func (info *TypeInfo) GetTyp3(fopts FieldOptions) Typ3 {
    78  	return typeToTyp3(info.ReprType.Type, fopts)
    79  }
    80  
    81  // Used to determine whether to create an implicit struct or not.  Notice that
    82  // the binary encoding of a list to be unpacked is indistinguishable from a
    83  // struct that contains that list.
    84  // NOTE: we expect info.Elem to be prepopulated, constructed within the scope
    85  // of a Codec.
    86  func (info *TypeInfo) IsStructOrUnpacked(fopt FieldOptions) bool {
    87  	rinfo := info.ReprType
    88  	if rinfo.Type.Kind() == reflect.Struct || rinfo.Type.Kind() == reflect.Interface {
    89  		return true
    90  	}
    91  	// We can't just look at the kind and info.Type.Elem(),
    92  	// as for example, a []time.Duration should not be packed,
    93  	// but should be represented as a slice of structs.
    94  	// For these cases, we should expect info.Elem to be prepopulated.
    95  	if rinfo.Type.Kind() == reflect.Array || rinfo.Type.Kind() == reflect.Slice {
    96  		return rinfo.Elem.GetTyp3(fopt) == Typ3ByteLength
    97  	}
    98  	return false
    99  }
   100  
   101  // If this is a slice or array, get .Elem.ReprType until no longer slice or
   102  // array.
   103  func (info *TypeInfo) GetUltimateElem() *TypeInfo {
   104  	if info.Elem != nil {
   105  		return info.Elem.ReprType.GetUltimateElem()
   106  	}
   107  	return info
   108  }
   109  
   110  func (info *TypeInfo) String() string {
   111  	if info.Type == nil {
   112  		// since we set it on the codec map
   113  		// before it's fully populated.
   114  		return "<new TypeInfo>"
   115  	}
   116  	buf := new(bytes.Buffer)
   117  	buf.Write([]byte("TypeInfo{"))
   118  	buf.Write([]byte(fmt.Sprintf("Type:%v,", info.Type)))
   119  	if info.ConcreteInfo.Registered {
   120  		buf.Write([]byte("Registered:true,"))
   121  		buf.Write([]byte(fmt.Sprintf("PointerPreferred:%v,", info.PointerPreferred)))
   122  		buf.Write([]byte(fmt.Sprintf("TypeURL:\"%v\",", info.TypeURL)))
   123  	} else {
   124  		buf.Write([]byte("Registered:false,"))
   125  	}
   126  	if info.ReprType == info {
   127  		buf.Write([]byte(fmt.Sprintf("ReprType:<self>,")))
   128  	} else {
   129  		buf.Write([]byte(fmt.Sprintf("ReprType:\"%v\",", info.ReprType)))
   130  	}
   131  	if info.Type.Kind() == reflect.Struct {
   132  		buf.Write([]byte(fmt.Sprintf("Fields:%v,", info.Fields)))
   133  	}
   134  	buf.Write([]byte("}"))
   135  	return buf.String()
   136  }
   137  
   138  // ----------------------------------------
   139  // FieldInfo convenience
   140  
   141  func (finfo *FieldInfo) IsPtr() bool {
   142  	return finfo.Type.Kind() == reflect.Ptr
   143  }
   144  
   145  func (finfo *FieldInfo) ValidateBasic() {
   146  	if finfo.BinFixed32 {
   147  		switch finfo.TypeInfo.GetUltimateElem().Type.Kind() {
   148  		case reflect.Int32, reflect.Uint32:
   149  			// ok
   150  		case reflect.Int, reflect.Uint:
   151  			// TODO error upon overflow/underflow during conversion.
   152  			panic("\"fixed32\" not yet supported for int/uint")
   153  		default:
   154  			panic("unexpected tag \"fixed32\" for non-32bit type")
   155  		}
   156  	}
   157  	if finfo.BinFixed64 {
   158  		switch finfo.TypeInfo.GetUltimateElem().Type.Kind() {
   159  		case reflect.Int64, reflect.Uint64, reflect.Int, reflect.Uint:
   160  			// ok
   161  		default:
   162  			panic("unexpected tag \"fixed64\" for non-64bit type")
   163  		}
   164  	}
   165  	if !finfo.Unsafe {
   166  		switch finfo.TypeInfo.Type.Kind() {
   167  		case reflect.Float32, reflect.Float64:
   168  			panic("floating point types are unsafe for go-amino")
   169  		}
   170  		switch finfo.TypeInfo.GetUltimateElem().Type.Kind() {
   171  		case reflect.Float32, reflect.Float64:
   172  			panic("floating point types are unsafe for go-amino, even for repr types")
   173  		}
   174  	}
   175  }
   176  
   177  // ----------------------------------------
   178  // Codec
   179  
   180  type Codec struct {
   181  	mtx       sync.RWMutex
   182  	sealed    bool
   183  	autoseal  bool
   184  	typeInfos map[reflect.Type]*TypeInfo
   185  	// proto3 name of format "<pkg path no slashes>.<MessageName>"
   186  	// which follows the TypeURL's last (and required) slash.
   187  	// only registered types have names.
   188  	fullnameToTypeInfo map[string]*TypeInfo
   189  	packages           pkg.PackageSet
   190  	usePBBindings      bool
   191  }
   192  
   193  func NewCodec() *Codec {
   194  	cdc := &Codec{
   195  		sealed:             false,
   196  		autoseal:           false,
   197  		typeInfos:          make(map[reflect.Type]*TypeInfo),
   198  		fullnameToTypeInfo: make(map[string]*TypeInfo),
   199  		packages:           pkg.NewPackageSet(),
   200  		usePBBindings:      false,
   201  	}
   202  	cdc.registerWellKnownTypes()
   203  	return cdc
   204  }
   205  
   206  // Returns a new codec that is optimized w/ pbbindings.
   207  // The returned codec is sealed, but may be affected by
   208  // modifications to the underlying codec.
   209  func (cdc *Codec) WithPBBindings() *Codec {
   210  	return &Codec{
   211  		sealed:             cdc.sealed,
   212  		autoseal:           cdc.autoseal,
   213  		typeInfos:          cdc.typeInfos,
   214  		fullnameToTypeInfo: cdc.fullnameToTypeInfo,
   215  		packages:           cdc.packages,
   216  		usePBBindings:      true,
   217  	}
   218  }
   219  
   220  // The package isn't (yet) necessary besides to get the full name of concrete
   221  // types.  Registers all dependencies of pkg recursively.  This operation is
   222  // idempotent -- pkgs already registered may be registered again.
   223  func (cdc *Codec) RegisterPackage(pkg *Package) {
   224  	cdc.assertNotSealed()
   225  
   226  	// Register dependencies if needed.
   227  	for _, dep := range pkg.Dependencies {
   228  		cdc.RegisterPackage(dep)
   229  	}
   230  
   231  	// Register types for package.
   232  	for _, t := range pkg.Types {
   233  		cdc.RegisterTypeFrom(t.Type, pkg)
   234  	}
   235  }
   236  
   237  // This function should be used to register concrete types that will appear in
   238  // interface fields/elements to be encoded/decoded by go-amino.
   239  // You may want to use RegisterPackage() instead which registers everything in
   240  // a package.
   241  // Usage:
   242  // `amino.RegisterTypeFrom(MyStruct1{}, "/tm.cryp.MyStruct1")`
   243  func (cdc *Codec) RegisterTypeFrom(rt reflect.Type, pkg *Package) {
   244  	cdc.assertNotSealed()
   245  
   246  	// Get p3 full name.
   247  	t, ok := pkg.GetType(rt)
   248  	if !ok {
   249  		panic(fmt.Errorf("type %v not registered on package %v", rt, pkg))
   250  	}
   251  
   252  	// Get type_url
   253  	typeURL := pkg.TypeURLForType(rt)
   254  	pointerPreferred := t.PointerPreferred
   255  	cdc.registerType(pkg, rt, typeURL, pointerPreferred, true)
   256  }
   257  
   258  // This function exists so that typeURL etc can be overridden.
   259  func (cdc *Codec) registerType(pkg *Package, rt reflect.Type, typeURL string, pointerPreferred bool, primary bool) {
   260  	cdc.assertNotSealed()
   261  
   262  	// Add package to packages if new.
   263  	cdc.packages.Add(pkg)
   264  
   265  	if rt.Kind() == reflect.Interface ||
   266  		rt.Kind() == reflect.Ptr {
   267  		panic(fmt.Sprintf("expected non-interface non-pointer concrete type, got %v", rt))
   268  	}
   269  
   270  	// Construct TypeInfo if one doesn't already exist.
   271  	info, ok := cdc.typeInfos[rt]
   272  	if ok {
   273  		if info.Registered {
   274  			// If idempotent operation, ignore silently.
   275  			// Otherwise, panic.
   276  			if info.Package != pkg {
   277  				panic(fmt.Sprintf("type %v already registered with different package %v", rt, info.Package))
   278  			}
   279  			if info.ConcreteInfo.PointerPreferred != pointerPreferred {
   280  				panic(fmt.Sprintf("type %v already registered with different pointer preference", rt))
   281  			}
   282  			if info.ConcreteInfo.TypeURL != typeURL {
   283  				panic(fmt.Sprintf("type %v already registered with different type URL %v", rt, info.TypeURL))
   284  			}
   285  			return // silently
   286  		} else {
   287  			// we will be filling in an existing type.
   288  		}
   289  	} else {
   290  		// construct a new one.
   291  		info = cdc.newTypeInfoUnregisteredWLock(rt)
   292  	}
   293  
   294  	// Fill info for registered types.
   295  	info.Package = pkg
   296  	info.ConcreteInfo.Registered = true
   297  	info.ConcreteInfo.PointerPreferred = pointerPreferred
   298  	info.ConcreteInfo.Name = typeURLtoShortname(typeURL)
   299  	info.ConcreteInfo.TypeURL = typeURL
   300  
   301  	// Separate locking instance,
   302  	// do the registration
   303  	func() { // So it unlocks after scope.
   304  		cdc.mtx.Lock()
   305  		defer cdc.mtx.Unlock()
   306  		cdc.registerTypeInfoWLocked(info, primary)
   307  	}()
   308  
   309  	func() { // And do it again...
   310  		cdc.mtx.Lock()
   311  		defer cdc.mtx.Unlock()
   312  		// Cuz why not.
   313  	}()
   314  }
   315  
   316  func (cdc *Codec) Seal() *Codec {
   317  	cdc.mtx.Lock()
   318  	defer cdc.mtx.Unlock()
   319  
   320  	cdc.sealed = true
   321  	return cdc
   322  }
   323  
   324  func (cdc *Codec) Autoseal() *Codec {
   325  	cdc.mtx.Lock()
   326  	defer cdc.mtx.Unlock()
   327  
   328  	if cdc.sealed {
   329  		panic("already sealed")
   330  	}
   331  	cdc.autoseal = true
   332  	return cdc
   333  }
   334  
   335  // PrintTypes writes all registered types in a markdown-style table.
   336  // The table's header is:
   337  //
   338  // | Type  | TypeURL | Notes |
   339  //
   340  // Where Type is the golang type name and TypeURL is the type_url the type was registered with.
   341  func (cdc *Codec) PrintTypes(out io.Writer) error {
   342  	cdc.mtx.RLock()
   343  	defer cdc.mtx.RUnlock()
   344  	// print header
   345  	if _, err := io.WriteString(out, "| Type | TypeURL | Length | Notes |\n"); err != nil {
   346  		return err
   347  	}
   348  	if _, err := io.WriteString(out, "| ---- | ------- | ------ | ----- |\n"); err != nil {
   349  		return err
   350  	}
   351  	// only print concrete types for now (if we want everything, we can iterate over the typeInfos map instead)
   352  	for _, i := range cdc.typeInfos {
   353  		if _, err := io.WriteString(out, "| "); err != nil {
   354  			return err
   355  		}
   356  		// TODO(ismail): optionally create a link to code on github:
   357  		if _, err := io.WriteString(out, i.Type.Name()); err != nil {
   358  			return err
   359  		}
   360  		if _, err := io.WriteString(out, " | "); err != nil {
   361  			return err
   362  		}
   363  		if _, err := io.WriteString(out, i.TypeURL); err != nil {
   364  			return err
   365  		}
   366  		if _, err := io.WriteString(out, " | "); err != nil {
   367  			return err
   368  		}
   369  
   370  		if _, err := io.WriteString(out, getLengthStr(i)); err != nil {
   371  			return err
   372  		}
   373  
   374  		if _, err := io.WriteString(out, " | "); err != nil {
   375  			return err
   376  		}
   377  		// empty notes table data by default // TODO(ismail): make this configurable
   378  
   379  		if _, err := io.WriteString(out, " |\n"); err != nil {
   380  			return err
   381  		}
   382  	}
   383  	// finish table
   384  	return nil
   385  }
   386  
   387  // A heuristic to guess the size of a registered type and return it as a string.
   388  // If the size is not fixed it returns "variable".
   389  func getLengthStr(info *TypeInfo) string {
   390  	switch info.Type.Kind() {
   391  	case reflect.Array,
   392  		reflect.Int8,
   393  		reflect.Int16, reflect.Int32, reflect.Int64,
   394  		reflect.Float32, reflect.Float64,
   395  		reflect.Complex64, reflect.Complex128:
   396  		s := info.Type.Size()
   397  		return fmt.Sprintf("0x%X", s)
   398  	default:
   399  		return "variable"
   400  	}
   401  }
   402  
   403  // ----------------------------------------
   404  
   405  func (cdc *Codec) assertNotSealed() {
   406  	cdc.mtx.Lock()
   407  	defer cdc.mtx.Unlock()
   408  
   409  	if cdc.sealed {
   410  		panic("codec sealed")
   411  	}
   412  }
   413  
   414  func (cdc *Codec) doAutoseal() {
   415  	cdc.mtx.Lock()
   416  	defer cdc.mtx.Unlock()
   417  
   418  	if cdc.autoseal {
   419  		cdc.sealed = true
   420  		cdc.autoseal = false
   421  	}
   422  }
   423  
   424  // assumes write lock is held.
   425  // primary should generally be true, and must be true for the first type set
   426  // here that is info.Registered, except when registering secondary types for a
   427  // given (full) name, such as google.protobuf.*.  If primary is set to false
   428  // and info.Registered, the name must already be
   429  // registered, and no side effects occur.
   430  // CONTRACT: info.Type is set
   431  // CONTRACT: if info.Registered, info.TypeURL is set
   432  func (cdc *Codec) registerTypeInfoWLocked(info *TypeInfo, primary bool) {
   433  	if info.Type.Kind() == reflect.Ptr {
   434  		panic(fmt.Sprintf("unexpected pointer type"))
   435  	}
   436  	if existing, ok := cdc.typeInfos[info.Type]; !ok || existing != info {
   437  		if !ok {
   438  			// See corresponding comment in newTypeInfoUnregisteredWLocked.
   439  			panic("unrecognized *TypeInfo")
   440  		} else {
   441  			panic(fmt.Sprintf("unexpected *TypeInfo: existing: %v, new: %v", existing, info))
   442  		}
   443  	}
   444  	if !info.Registered {
   445  		panic("expected registered info")
   446  	}
   447  
   448  	// Everybody's dooing a brand-new dance, now
   449  	// Come on baby, doo the registration!
   450  	fullname := typeURLtoFullname(info.TypeURL)
   451  	existing, ok := cdc.fullnameToTypeInfo[fullname]
   452  	if primary {
   453  		if ok {
   454  			panic(fmt.Sprintf("fullname <%s> already registered for %v (TypeURL: %v)", fullname, existing.Type, info.TypeURL))
   455  		}
   456  		cdc.fullnameToTypeInfo[fullname] = info
   457  	} else {
   458  		if !ok {
   459  			panic(fmt.Sprintf("fullname <%s> not yet registered", fullname))
   460  		}
   461  	}
   462  }
   463  
   464  // XXX TODO: make this safe so modifications don't affect runtime codec,
   465  // and ensure that it stays safe.
   466  // NOTE: do not modify the returned Packages.
   467  func (cdc *Codec) GetPackages() pkg.PackageSet {
   468  	cdc.mtx.RLock()
   469  	defer cdc.mtx.RUnlock()
   470  
   471  	return cdc.packages
   472  }
   473  
   474  // This is used primarily for gengo.
   475  // XXX TODO: make this safe so modifications don't affect runtime codec,
   476  // and ensure that it stays safe.
   477  // NOTE: do not modify the returned TypeInfo.
   478  func (cdc *Codec) GetTypeInfo(rt reflect.Type) (info *TypeInfo, err error) {
   479  	return cdc.getTypeInfoWLock(rt)
   480  }
   481  
   482  func (cdc *Codec) getTypeInfoWLock(rt reflect.Type) (info *TypeInfo, err error) {
   483  	cdc.mtx.Lock() // requires wlock because we might set.
   484  	// NOTE: We must defer, or at least recover, otherwise panics in
   485  	// getTypeInfoWLocked() will render the codec locked.
   486  	defer cdc.mtx.Unlock()
   487  
   488  	info, err = cdc.getTypeInfoWLocked(rt)
   489  	return info, err
   490  }
   491  
   492  // If a new one is constructed and cached in state, it is not yet registered.
   493  // Automatically dereferences rt pointers.
   494  func (cdc *Codec) getTypeInfoWLocked(rt reflect.Type) (info *TypeInfo, err error) {
   495  	// Dereference pointer type.
   496  	for rt.Kind() == reflect.Ptr {
   497  		if rt.Elem().Kind() == reflect.Ptr {
   498  			return nil, fmt.Errorf("cannot support nested pointers, got %v", rt)
   499  		}
   500  		rt = rt.Elem()
   501  	}
   502  
   503  	info, ok := cdc.typeInfos[rt]
   504  	if !ok {
   505  		info = cdc.newTypeInfoUnregisteredWLocked(rt)
   506  	}
   507  	return info, nil
   508  }
   509  
   510  func (cdc *Codec) getTypeInfoFromTypeURLRLock(typeURL string, fopts FieldOptions) (info *TypeInfo, err error) {
   511  	fullname := typeURLtoFullname(typeURL)
   512  	return cdc.getTypeInfoFromFullnameRLock(fullname, fopts)
   513  }
   514  
   515  func (cdc *Codec) getTypeInfoFromFullnameRLock(fullname string, fopts FieldOptions) (info *TypeInfo, err error) {
   516  	// We do not use defer cdc.mtx.Unlock() here due to performance overhead of
   517  	// defer in go1.11 (and prior versions). Ensure new code paths unlock the
   518  	// mutex.
   519  	cdc.mtx.RLock()
   520  
   521  	// Special cases: time and duration
   522  	if fullname == "google.protobuf.Timestamp" && !fopts.UseGoogleTypes {
   523  		cdc.mtx.RUnlock()
   524  		info, err = cdc.getTypeInfoWLock(timeType)
   525  		return
   526  	}
   527  	if fullname == "google.protobuf.Duration" && !fopts.UseGoogleTypes {
   528  		cdc.mtx.RUnlock()
   529  		info, err = cdc.getTypeInfoWLock(durationType)
   530  		return
   531  	}
   532  
   533  	info, ok := cdc.fullnameToTypeInfo[fullname]
   534  	if !ok {
   535  		err = fmt.Errorf("amino: unrecognized concrete type full name %s", fullname)
   536  		cdc.mtx.RUnlock()
   537  		return
   538  	}
   539  	cdc.mtx.RUnlock()
   540  	return
   541  }
   542  
   543  // ----------------------------------------
   544  // TypeInfo registration
   545  
   546  // Constructs a *TypeInfo from scratch (except
   547  // dependencies).  The constructed TypeInfo is stored in
   548  // state, but not yet registered - no name or decoding
   549  // preferece (pointer or not) is known, so it cannot be
   550  // used to decode into an interface.
   551  //
   552  // cdc.registerType() calls this first for
   553  // initial construction.  Unregistered type infos can
   554  // still represent circular types because they still
   555  // populate the internal lookup map, but they don't have
   556  // certain fields set, such as:
   557  //
   558  //   - .Package - defaults to nil until registered.
   559  //   - .ConcreteInfo.PointerPreferred - how it prefers to
   560  //     be decoded
   561  //   - .ConcreteInfo.TypeURL - for Any serialization
   562  //
   563  // But it does set .ConcreteInfo.Elem, which may be
   564  // modified by the Codec instance.
   565  func (cdc *Codec) newTypeInfoUnregisteredWLock(rt reflect.Type) *TypeInfo {
   566  	cdc.mtx.Lock()
   567  	defer cdc.mtx.Unlock()
   568  
   569  	return cdc.newTypeInfoUnregisteredWLocked(rt)
   570  }
   571  
   572  func (cdc *Codec) newTypeInfoUnregisteredWLocked(rt reflect.Type) *TypeInfo {
   573  	switch rt.Kind() {
   574  	case reflect.Ptr:
   575  		panic(fmt.Sprintf("unexpected pointer type %v", rt)) // should not happen.
   576  	case reflect.Map:
   577  		panic(fmt.Sprintf("map type not supported %v", rt))
   578  	case reflect.Func:
   579  		panic(fmt.Sprintf("func type not supported %v", rt))
   580  	}
   581  	if _, exists := cdc.typeInfos[rt]; exists {
   582  		panic(fmt.Sprintf("type info already registered for %v", rt))
   583  	}
   584  
   585  	// Populate this early so it gets found when getTypeInfoWLocked() is
   586  	// called, esp for parseStructInfoWLocked() which may cause infinite
   587  	// recursion if two structs reference each other in declaration.
   588  	// TODO: can protobuf support this? If not, we would still want to, but
   589  	// restrict what can be compiled to protobuf, or something.
   590  	info := new(TypeInfo)
   591  	if _, exists := cdc.typeInfos[rt]; exists {
   592  		panic("should not happen, instance already exists")
   593  	}
   594  	cdc.typeInfos[rt] = info
   595  
   596  	info.Type = rt
   597  	info.PtrToType = reflect.PointerTo(rt)
   598  	info.ZeroValue = reflect.Zero(rt)
   599  	var isAminoMarshaler bool
   600  	var reprType reflect.Type
   601  	if rm, ok := rt.MethodByName("MarshalAmino"); ok {
   602  		isAminoMarshaler = true
   603  		reprType = marshalAminoReprType(rm)
   604  	}
   605  	if rm, ok := reflect.PointerTo(rt).MethodByName("UnmarshalAmino"); ok {
   606  		if !isAminoMarshaler {
   607  			panic("Must implement both (o).MarshalAmino and (*o).UnmarshalAmino")
   608  		}
   609  		reprType2 := unmarshalAminoReprType(rm)
   610  		if reprType != reprType2 {
   611  			panic("Must match MarshalAmino and UnmarshalAmino repr types")
   612  		}
   613  	}
   614  	/*
   615  		NOTE: this could used by genproto typeToP3Type,
   616  		but it isn't quite right... we don't want them to
   617  		preserve the "Time" name, we want "Timestamp".
   618  
   619  		// Special cases for well known types.
   620  		// TODO: refactor out and merge into wellknown.go somehow.
   621  		// NOTE: isAminoMarshaler remains false.
   622  		switch rt {
   623  		case timeType:
   624  			reprType = gTimestampType
   625  		case durationType:
   626  			reprType = gDurationType
   627  		}
   628  		// END Special cases for well known types.
   629  	*/
   630  	if isAminoMarshaler {
   631  		info.ConcreteInfo.IsAminoMarshaler = true
   632  		rinfo, err := cdc.getTypeInfoWLocked(reprType)
   633  		if err != nil {
   634  			panic(err)
   635  		}
   636  		info.ConcreteInfo.ReprType = rinfo
   637  	} else {
   638  		info.ConcreteInfo.IsAminoMarshaler = false
   639  		info.ConcreteInfo.ReprType = info
   640  	}
   641  	info.ConcreteInfo.IsBinaryWellKnownType = isBinaryWellKnownType(rt)
   642  	info.ConcreteInfo.IsJSONWellKnownType = isJSONWellKnownType(rt)
   643  	info.ConcreteInfo.IsJSONAnyValueType = isJSONAnyValueType(rt)
   644  	if rt.Kind() == reflect.Array || rt.Kind() == reflect.Slice {
   645  		einfo, err := cdc.getTypeInfoWLocked(rt.Elem())
   646  		if err != nil {
   647  			panic(err)
   648  		}
   649  		info.ConcreteInfo.Elem = einfo
   650  		info.ConcreteInfo.ElemIsPtr = rt.Elem().Kind() == reflect.Ptr
   651  	}
   652  	if rt.Kind() == reflect.Struct {
   653  		info.StructInfo = cdc.parseStructInfoWLocked(rt)
   654  	}
   655  	return info
   656  }
   657  
   658  // ----------------------------------------
   659  // ...
   660  
   661  func (cdc *Codec) parseStructInfoWLocked(rt reflect.Type) (sinfo StructInfo) {
   662  	defer func() {
   663  		if ex := recover(); ex != nil {
   664  			panic(fmt.Sprintf("panic parsing struct %v",
   665  				rt))
   666  		}
   667  	}()
   668  	if rt.Kind() != reflect.Struct {
   669  		panic("should not happen")
   670  	}
   671  
   672  	infos := make([]FieldInfo, 0, rt.NumField())
   673  	for i := 0; i < rt.NumField(); i++ {
   674  		field := rt.Field(i)
   675  		ftype := field.Type
   676  		if !isExported(field) {
   677  			continue // field is unexported
   678  		}
   679  		skip, fopts := parseFieldOptions(field)
   680  		if skip {
   681  			continue // e.g. json:"-"
   682  		}
   683  		// NOTE: This is going to change a bit.
   684  		// NOTE: BinFieldNum starts with 1.
   685  		fopts.BinFieldNum = uint32(len(infos) + 1)
   686  		fieldTypeInfo, err := cdc.getTypeInfoWLocked(ftype)
   687  		if err != nil {
   688  			panic(err)
   689  		}
   690  		frepr := fieldTypeInfo.ReprType.Type
   691  		unpackedList := false
   692  		if frepr.Kind() == reflect.Array || frepr.Kind() == reflect.Slice {
   693  			if frepr.Elem().Kind() == reflect.Uint8 {
   694  				// These get handled by our optimized methods,
   695  				// encodeReflectBinaryByte[Slice/Array].
   696  				unpackedList = false
   697  			} else {
   698  				etype := frepr.Elem()
   699  				for etype.Kind() == reflect.Ptr {
   700  					etype = etype.Elem()
   701  				}
   702  				typ3 := typeToTyp3(etype, fopts)
   703  				if typ3 == Typ3ByteLength {
   704  					unpackedList = true
   705  				}
   706  			}
   707  		}
   708  
   709  		fieldInfo := FieldInfo{
   710  			Type:         ftype,
   711  			TypeInfo:     fieldTypeInfo,
   712  			Name:         field.Name, // Mostly for debugging.
   713  			Index:        i,          // the field number for this go runtime (for decoding).
   714  			ZeroValue:    reflect.Zero(ftype),
   715  			UnpackedList: unpackedList,
   716  			FieldOptions: fopts,
   717  		}
   718  		fieldInfo.ValidateBasic()
   719  		infos = append(infos, fieldInfo)
   720  	}
   721  	sinfo = StructInfo{infos}
   722  	return sinfo
   723  }
   724  
   725  func parseFieldOptions(field reflect.StructField) (skip bool, fopts FieldOptions) {
   726  	binTag := field.Tag.Get("binary")
   727  	aminoTag := field.Tag.Get("amino")
   728  	jsonTag := field.Tag.Get("json")
   729  
   730  	// If `json:"-"`, don't encode.
   731  	// NOTE: This skips binary as well.
   732  	if jsonTag == "-" {
   733  		skip = true
   734  		return
   735  	}
   736  
   737  	// Get JSON field name.
   738  	jsonTagParts := strings.Split(jsonTag, ",")
   739  	if jsonTagParts[0] == "" {
   740  		fopts.JSONName = field.Name
   741  	} else {
   742  		fopts.JSONName = jsonTagParts[0]
   743  	}
   744  
   745  	// Get JSON omitempty.
   746  	if len(jsonTagParts) > 1 {
   747  		if jsonTagParts[1] == "omitempty" {
   748  			fopts.JSONOmitEmpty = true
   749  		}
   750  	}
   751  
   752  	// Parse binary tags.
   753  	// NOTE: these get validated later, we don't have TypeInfo yet.
   754  	if binTag == "fixed64" {
   755  		fopts.BinFixed64 = true
   756  	} else if binTag == "fixed32" {
   757  		fopts.BinFixed32 = true
   758  	}
   759  
   760  	// Parse amino tags.
   761  	aminoTags := strings.Split(aminoTag, ",")
   762  	for _, aminoTag := range aminoTags {
   763  		if aminoTag == "unsafe" {
   764  			fopts.Unsafe = true
   765  		}
   766  		if aminoTag == "write_empty" {
   767  			fopts.WriteEmpty = true
   768  		}
   769  		if aminoTag == "nil_elements" {
   770  			fopts.NilElements = true
   771  		}
   772  	}
   773  
   774  	return skip, fopts
   775  }
   776  
   777  // ----------------------------------------
   778  // Misc.
   779  
   780  func typeURLtoFullname(typeURL string) (fullname string) {
   781  	parts := strings.Split(typeURL, "/")
   782  	if len(parts) == 1 {
   783  		panic(fmt.Sprintf("invalid type_url \"%v\", must contain at least one slash and be followed by the full name", typeURL))
   784  	}
   785  	return parts[len(parts)-1]
   786  }
   787  
   788  func typeURLtoShortname(typeURL string) (name string) {
   789  	fullname := typeURLtoFullname(typeURL)
   790  	parts := strings.Split(fullname, ".")
   791  	if len(parts) == 1 {
   792  		panic(fmt.Sprintf("invalid type_url \"%v\", full name must contain dot", typeURL))
   793  	}
   794  	return parts[len(parts)-1]
   795  }
   796  
   797  func typeToTyp3(rt reflect.Type, opts FieldOptions) Typ3 {
   798  	// Special non-list cases:
   799  	switch rt {
   800  	case timeType:
   801  		return Typ3ByteLength // for completeness
   802  	case durationType:
   803  		return Typ3ByteLength // as a google.protobuf.Duration.
   804  	}
   805  	// General cases:
   806  	switch rt.Kind() {
   807  	case reflect.Interface:
   808  		return Typ3ByteLength
   809  	case reflect.Array, reflect.Slice:
   810  		return Typ3ByteLength
   811  	case reflect.String:
   812  		return Typ3ByteLength
   813  	case reflect.Struct, reflect.Map:
   814  		return Typ3ByteLength
   815  	case reflect.Int64, reflect.Uint64:
   816  		if opts.BinFixed64 {
   817  			return Typ38Byte
   818  		}
   819  		return Typ3Varint
   820  	case reflect.Int32, reflect.Uint32:
   821  		if opts.BinFixed32 {
   822  			return Typ34Byte
   823  		}
   824  		return Typ3Varint
   825  
   826  	case reflect.Int16, reflect.Int8, reflect.Int,
   827  		reflect.Uint16, reflect.Uint8, reflect.Uint, reflect.Bool:
   828  		return Typ3Varint
   829  	case reflect.Float64:
   830  		return Typ38Byte
   831  	case reflect.Float32:
   832  		return Typ34Byte
   833  	default:
   834  		panic(fmt.Sprintf("unsupported field type %v", rt))
   835  	}
   836  }