Elia Battiston | ac8d23f | 2022-03-14 17:54:56 +0100 | [diff] [blame] | 1 | package internal |
| 2 | |
| 3 | import ( |
| 4 | "github.com/golang/protobuf/proto" |
| 5 | dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" |
| 6 | "github.com/jhump/protoreflect/internal/codec" |
| 7 | "reflect" |
| 8 | "strings" |
| 9 | |
| 10 | "github.com/jhump/protoreflect/internal" |
| 11 | ) |
| 12 | |
| 13 | // NB: We use reflection or unknown fields in case we are linked against an older |
| 14 | // version of the proto runtime which does not know about the proto3_optional field. |
| 15 | // We don't require linking with newer version (which would greatly simplify this) |
| 16 | // because that means pulling in v1.4+ of the protobuf runtime, which has some |
| 17 | // compatibility issues. (We'll be nice to users and not require they upgrade to |
| 18 | // that latest runtime to upgrade to newer protoreflect.) |
| 19 | |
| 20 | func GetProto3Optional(fd *dpb.FieldDescriptorProto) bool { |
| 21 | type newerFieldDesc interface { |
| 22 | GetProto3Optional() bool |
| 23 | } |
| 24 | var pm proto.Message = fd |
| 25 | if fd, ok := pm.(newerFieldDesc); ok { |
| 26 | return fd.GetProto3Optional() |
| 27 | } |
| 28 | |
| 29 | // Field does not exist, so we have to examine unknown fields |
| 30 | // (we just silently bail if we have problems parsing them) |
| 31 | unk := internal.GetUnrecognized(pm) |
| 32 | buf := codec.NewBuffer(unk) |
| 33 | for { |
| 34 | tag, wt, err := buf.DecodeTagAndWireType() |
| 35 | if err != nil { |
| 36 | return false |
| 37 | } |
| 38 | if tag == Field_proto3OptionalTag && wt == proto.WireVarint { |
| 39 | v, _ := buf.DecodeVarint() |
| 40 | return v != 0 |
| 41 | } |
| 42 | if err := buf.SkipField(wt); err != nil { |
| 43 | return false |
| 44 | } |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | func SetProto3Optional(fd *dpb.FieldDescriptorProto) { |
| 49 | rv := reflect.ValueOf(fd).Elem() |
| 50 | fld := rv.FieldByName("Proto3Optional") |
| 51 | if fld.IsValid() { |
| 52 | fld.Set(reflect.ValueOf(proto.Bool(true))) |
| 53 | return |
| 54 | } |
| 55 | |
| 56 | // Field does not exist, so we have to store as unknown field. |
| 57 | var buf codec.Buffer |
| 58 | if err := buf.EncodeTagAndWireType(Field_proto3OptionalTag, proto.WireVarint); err != nil { |
| 59 | // TODO: panic? log? |
| 60 | return |
| 61 | } |
| 62 | if err := buf.EncodeVarint(1); err != nil { |
| 63 | // TODO: panic? log? |
| 64 | return |
| 65 | } |
| 66 | internal.SetUnrecognized(fd, buf.Bytes()) |
| 67 | } |
| 68 | |
| 69 | // ProcessProto3OptionalFields adds synthetic oneofs to the given message descriptor |
| 70 | // for each proto3 optional field. It also updates the fields to have the correct |
| 71 | // oneof index reference. |
| 72 | func ProcessProto3OptionalFields(msgd *dpb.DescriptorProto) { |
| 73 | var allNames map[string]struct{} |
| 74 | for _, fd := range msgd.Field { |
| 75 | if GetProto3Optional(fd) { |
| 76 | // lazy init the set of all names |
| 77 | if allNames == nil { |
| 78 | allNames = map[string]struct{}{} |
| 79 | for _, fd := range msgd.Field { |
| 80 | allNames[fd.GetName()] = struct{}{} |
| 81 | } |
| 82 | for _, fd := range msgd.Extension { |
| 83 | allNames[fd.GetName()] = struct{}{} |
| 84 | } |
| 85 | for _, ed := range msgd.EnumType { |
| 86 | allNames[ed.GetName()] = struct{}{} |
| 87 | for _, evd := range ed.Value { |
| 88 | allNames[evd.GetName()] = struct{}{} |
| 89 | } |
| 90 | } |
| 91 | for _, fd := range msgd.NestedType { |
| 92 | allNames[fd.GetName()] = struct{}{} |
| 93 | } |
| 94 | for _, n := range msgd.ReservedName { |
| 95 | allNames[n] = struct{}{} |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // Compute a name for the synthetic oneof. This uses the same |
| 100 | // algorithm as used in protoc: |
| 101 | // https://github.com/protocolbuffers/protobuf/blob/74ad62759e0a9b5a21094f3fb9bb4ebfaa0d1ab8/src/google/protobuf/compiler/parser.cc#L785-L803 |
| 102 | ooName := fd.GetName() |
| 103 | if !strings.HasPrefix(ooName, "_") { |
| 104 | ooName = "_" + ooName |
| 105 | } |
| 106 | for { |
| 107 | _, ok := allNames[ooName] |
| 108 | if !ok { |
| 109 | // found a unique name |
| 110 | allNames[ooName] = struct{}{} |
| 111 | break |
| 112 | } |
| 113 | ooName = "X" + ooName |
| 114 | } |
| 115 | |
| 116 | fd.OneofIndex = proto.Int32(int32(len(msgd.OneofDecl))) |
| 117 | msgd.OneofDecl = append(msgd.OneofDecl, &dpb.OneofDescriptorProto{Name: proto.String(ooName)}) |
| 118 | } |
| 119 | } |
| 120 | } |