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 }