blob: 9aa4a3eedcf03cdf4324d24db9ddcb3657411ec0 [file] [log] [blame]
khenaidooefff76e2021-12-15 16:51:30 -05001package internal
2
3import (
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
20func 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
48func 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.
72func 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}