blob: c54cfc169aec090d9abb58bc14bb40fff2083806 [file] [log] [blame]
Joe Tsai90fe9962018-10-18 11:06:29 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Joe Tsai21ade492019-05-22 13:42:54 -04005package impl
Joe Tsai90fe9962018-10-18 11:06:29 -07006
7import (
8 "fmt"
Joe Tsai90fe9962018-10-18 11:06:29 -07009 "reflect"
Joe Tsai90fe9962018-10-18 11:06:29 -070010 "strings"
11 "sync"
12 "unicode"
13
Damien Neile89e6242019-05-13 23:55:40 -070014 ptag "google.golang.org/protobuf/internal/encoding/tag"
Damien Neile89e6242019-05-13 23:55:40 -070015 ptype "google.golang.org/protobuf/internal/prototype"
16 pref "google.golang.org/protobuf/reflect/protoreflect"
Joe Tsaib2f66be2019-05-22 00:42:45 -040017 "google.golang.org/protobuf/reflect/prototype"
Joe Tsai90fe9962018-10-18 11:06:29 -070018)
19
Joe Tsai21ade492019-05-22 13:42:54 -040020// legacyWrapMessage wraps v as a protoreflect.ProtoMessage,
Joe Tsaif0c01e42018-11-06 13:05:20 -080021// where v must be a *struct kind and not implement the v2 API already.
Joe Tsai21ade492019-05-22 13:42:54 -040022func legacyWrapMessage(v reflect.Value) pref.ProtoMessage {
23 mt := legacyLoadMessageInfo(v.Type())
Joe Tsai08e00302018-11-26 22:32:06 -080024 return mt.MessageOf(v.Interface()).Interface()
Joe Tsaif0c01e42018-11-06 13:05:20 -080025}
26
Joe Tsai21ade492019-05-22 13:42:54 -040027var legacyMessageTypeCache sync.Map // map[reflect.Type]*MessageInfo
Joe Tsaice6edd32018-10-19 16:27:46 -070028
Joe Tsai21ade492019-05-22 13:42:54 -040029// legacyLoadMessageInfo dynamically loads a *MessageInfo for t,
Joe Tsaif0c01e42018-11-06 13:05:20 -080030// where t must be a *struct kind and not implement the v2 API already.
Joe Tsai21ade492019-05-22 13:42:54 -040031func legacyLoadMessageInfo(t reflect.Type) *MessageInfo {
Joe Tsai4fe96632019-05-22 05:12:36 -040032 // Fast-path: check if a MessageInfo is cached for this concrete type.
Joe Tsai21ade492019-05-22 13:42:54 -040033 if mt, ok := legacyMessageTypeCache.Load(t); ok {
34 return mt.(*MessageInfo)
Joe Tsaice6edd32018-10-19 16:27:46 -070035 }
36
Joe Tsai4fe96632019-05-22 05:12:36 -040037 // Slow-path: derive message descriptor and initialize MessageInfo.
Joe Tsai21ade492019-05-22 13:42:54 -040038 md := LegacyLoadMessageDesc(t)
39 mt := new(MessageInfo)
Damien Neil8012b442019-01-18 09:32:24 -080040 mt.GoType = t
Joe Tsaib2f66be2019-05-22 00:42:45 -040041 mt.PBType = &prototype.Message{
42 MessageDescriptor: md,
43 NewMessage: func() pref.Message {
44 return mt.MessageOf(reflect.New(t.Elem()).Interface())
45 },
46 }
Joe Tsai21ade492019-05-22 13:42:54 -040047 if mt, ok := legacyMessageTypeCache.LoadOrStore(t, mt); ok {
48 return mt.(*MessageInfo)
Joe Tsaib9365042019-03-19 14:14:29 -070049 }
Joe Tsaif0c01e42018-11-06 13:05:20 -080050 return mt
Joe Tsaice6edd32018-10-19 16:27:46 -070051}
52
Joe Tsai21ade492019-05-22 13:42:54 -040053var (
54 legacyMessageDescLock sync.Mutex
55 legacyMessageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
56)
Joe Tsai90fe9962018-10-18 11:06:29 -070057
Joe Tsai21ade492019-05-22 13:42:54 -040058// LegacyLoadMessageDesc returns an MessageDescriptor derived from the Go type,
Joe Tsaice6edd32018-10-19 16:27:46 -070059// which must be a *struct kind and not implement the v2 API already.
Joe Tsai35ec98f2019-03-25 14:41:32 -070060//
61// This is exported for testing purposes.
Joe Tsai21ade492019-05-22 13:42:54 -040062func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
63 return legacyMessageDescSet{}.Load(t)
Joe Tsai90fe9962018-10-18 11:06:29 -070064}
65
Joe Tsai21ade492019-05-22 13:42:54 -040066type legacyMessageDescSet struct {
Joe Tsai90fe9962018-10-18 11:06:29 -070067 visited map[reflect.Type]*ptype.StandaloneMessage
68 descs []*ptype.StandaloneMessage
69 types []reflect.Type
70}
71
Joe Tsai21ade492019-05-22 13:42:54 -040072func (ms legacyMessageDescSet) Load(t reflect.Type) pref.MessageDescriptor {
Joe Tsai90fe9962018-10-18 11:06:29 -070073 // Fast-path: check if a MessageDescriptor is cached for this concrete type.
Joe Tsai21ade492019-05-22 13:42:54 -040074 if mi, ok := legacyMessageDescCache.Load(t); ok {
Joe Tsai90fe9962018-10-18 11:06:29 -070075 return mi.(pref.MessageDescriptor)
76 }
77
78 // Slow-path: initialize MessageDescriptor from the Go type.
Joe Tsaib9365042019-03-19 14:14:29 -070079 //
80 // Hold a global lock during message creation to ensure that each Go type
81 // maps to exactly one MessageDescriptor. After obtaining the lock, we must
82 // check again whether the message has already been handled.
Joe Tsai21ade492019-05-22 13:42:54 -040083 legacyMessageDescLock.Lock()
84 defer legacyMessageDescLock.Unlock()
85 if mi, ok := legacyMessageDescCache.Load(t); ok {
Joe Tsaib9365042019-03-19 14:14:29 -070086 return mi.(pref.MessageDescriptor)
87 }
Joe Tsai90fe9962018-10-18 11:06:29 -070088
89 // Processing t recursively populates descs and types with all sub-messages.
90 // The descriptor for the first type is guaranteed to be at the front.
91 ms.processMessage(t)
92
93 // Within a proto file it is possible for cyclic dependencies to exist
94 // between multiple message types. When these cases arise, the set of
95 // message descriptors must be created together.
96 mds, err := ptype.NewMessages(ms.descs)
97 if err != nil {
98 panic(err)
99 }
100 for i, md := range mds {
101 // Protobuf semantics represents map entries under-the-hood as
102 // pseudo-messages (has a descriptor, but no generated Go type).
103 // Avoid caching these fake messages.
104 if t := ms.types[i]; t.Kind() != reflect.Map {
Joe Tsai21ade492019-05-22 13:42:54 -0400105 legacyMessageDescCache.Store(t, md)
Joe Tsai90fe9962018-10-18 11:06:29 -0700106 }
107 }
108 return mds[0]
109}
110
Joe Tsai21ade492019-05-22 13:42:54 -0400111func (ms *legacyMessageDescSet) processMessage(t reflect.Type) pref.MessageDescriptor {
Joe Tsai90fe9962018-10-18 11:06:29 -0700112 // Fast-path: Obtain a placeholder if the message is already processed.
113 if m, ok := ms.visited[t]; ok {
114 return ptype.PlaceholderMessage(m.FullName)
115 }
116
117 // Slow-path: Walk over the struct fields to derive the message descriptor.
Joe Tsai6f9095c2018-11-10 14:12:21 -0800118 if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct || t.Elem().PkgPath() == "" {
119 panic(fmt.Sprintf("got %v, want named *struct kind", t))
Joe Tsai90fe9962018-10-18 11:06:29 -0700120 }
121
122 // Derive name and syntax from the raw descriptor.
123 m := new(ptype.StandaloneMessage)
124 mv := reflect.New(t.Elem()).Interface()
125 if _, ok := mv.(pref.ProtoMessage); ok {
126 panic(fmt.Sprintf("%v already implements proto.Message", t))
127 }
Joe Tsai6dbffb72018-12-04 14:06:19 -0800128 if md, ok := mv.(messageV1); ok {
Joe Tsai90fe9962018-10-18 11:06:29 -0700129 b, idxs := md.Descriptor()
Joe Tsai21ade492019-05-22 13:42:54 -0400130 fd := legacyLoadFileDesc(b)
Joe Tsai90fe9962018-10-18 11:06:29 -0700131
132 // Derive syntax.
133 switch fd.GetSyntax() {
134 case "proto2", "":
135 m.Syntax = pref.Proto2
136 case "proto3":
137 m.Syntax = pref.Proto3
138 }
139
140 // Derive full name.
141 md := fd.MessageType[idxs[0]]
142 m.FullName = pref.FullName(fd.GetPackage()).Append(pref.Name(md.GetName()))
143 for _, i := range idxs[1:] {
144 md = md.NestedType[i]
145 m.FullName = m.FullName.Append(pref.Name(md.GetName()))
146 }
147 } else {
Joe Tsai6dbffb72018-12-04 14:06:19 -0800148 // If the type does not implement messageV1, then the only way to
Joe Tsai90fe9962018-10-18 11:06:29 -0700149 // obtain the full name is through the registry. However, this is
150 // unreliable as some generated messages register with a fork of
151 // golang/protobuf, so the registry may not have this information.
Joe Tsai21ade492019-05-22 13:42:54 -0400152 m.FullName = legacyDeriveFullName(t.Elem())
Joe Tsai90fe9962018-10-18 11:06:29 -0700153 m.Syntax = pref.Proto2
154
155 // Try to determine if the message is using proto3 by checking scalars.
156 for i := 0; i < t.Elem().NumField(); i++ {
157 f := t.Elem().Field(i)
158 if tag := f.Tag.Get("protobuf"); tag != "" {
159 switch f.Type.Kind() {
160 case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
161 m.Syntax = pref.Proto3
162 }
163 for _, s := range strings.Split(tag, ",") {
164 if s == "proto3" {
165 m.Syntax = pref.Proto3
166 }
167 }
168 }
169 }
170 }
171 ms.visit(m, t)
172
173 // Obtain a list of oneof wrapper types.
174 var oneofWrappers []reflect.Type
175 if fn, ok := t.MethodByName("XXX_OneofFuncs"); ok {
176 vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3]
177 for _, v := range vs.Interface().([]interface{}) {
178 oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
179 }
180 }
Joe Tsai25cc69d2018-11-28 23:43:49 -0800181 if fn, ok := t.MethodByName("XXX_OneofWrappers"); ok {
182 vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
183 for _, v := range vs.Interface().([]interface{}) {
184 oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
185 }
186 }
Joe Tsai90fe9962018-10-18 11:06:29 -0700187
188 // Obtain a list of the extension ranges.
189 if fn, ok := t.MethodByName("ExtensionRangeArray"); ok {
190 vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
191 for i := 0; i < vs.Len(); i++ {
192 v := vs.Index(i)
193 m.ExtensionRanges = append(m.ExtensionRanges, [2]pref.FieldNumber{
194 pref.FieldNumber(v.FieldByName("Start").Int()),
195 pref.FieldNumber(v.FieldByName("End").Int() + 1),
196 })
197 }
198 }
199
200 // Derive the message fields by inspecting the struct fields.
201 for i := 0; i < t.Elem().NumField(); i++ {
202 f := t.Elem().Field(i)
203 if tag := f.Tag.Get("protobuf"); tag != "" {
204 tagKey := f.Tag.Get("protobuf_key")
205 tagVal := f.Tag.Get("protobuf_val")
206 m.Fields = append(m.Fields, ms.parseField(tag, tagKey, tagVal, f.Type, m))
207 }
208 if tag := f.Tag.Get("protobuf_oneof"); tag != "" {
209 name := pref.Name(tag)
210 m.Oneofs = append(m.Oneofs, ptype.Oneof{Name: name})
211 for _, t := range oneofWrappers {
212 if t.Implements(f.Type) {
213 f := t.Elem().Field(0)
214 if tag := f.Tag.Get("protobuf"); tag != "" {
215 ft := ms.parseField(tag, "", "", f.Type, m)
216 ft.OneofName = name
217 m.Fields = append(m.Fields, ft)
218 }
219 }
220 }
221 }
222 }
223
224 return ptype.PlaceholderMessage(m.FullName)
225}
226
Joe Tsai21ade492019-05-22 13:42:54 -0400227func (ms *legacyMessageDescSet) parseField(tag, tagKey, tagVal string, goType reflect.Type, parent *ptype.StandaloneMessage) ptype.Field {
Joe Tsai05828db2018-11-01 13:52:16 -0700228 t := goType
Joe Tsai90fe9962018-10-18 11:06:29 -0700229 isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
230 isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
231 if isOptional || isRepeated {
232 t = t.Elem()
233 }
Joe Tsai05828db2018-11-01 13:52:16 -0700234 f := ptag.Unmarshal(tag, t)
Joe Tsai90fe9962018-10-18 11:06:29 -0700235
236 // Populate EnumType and MessageType.
237 if f.EnumType == nil && f.Kind == pref.EnumKind {
Damien Neila8593ba2019-01-08 16:18:07 -0800238 if ev, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
Joe Tsai0fc49f82019-05-01 12:29:25 -0700239 f.EnumType = ev.Descriptor()
Joe Tsai90fe9962018-10-18 11:06:29 -0700240 } else {
Joe Tsai21ade492019-05-22 13:42:54 -0400241 f.EnumType = LegacyLoadEnumDesc(t)
Joe Tsai90fe9962018-10-18 11:06:29 -0700242 }
243 }
244 if f.MessageType == nil && (f.Kind == pref.MessageKind || f.Kind == pref.GroupKind) {
245 if mv, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok {
Joe Tsai0fc49f82019-05-01 12:29:25 -0700246 f.MessageType = mv.ProtoReflect().Descriptor()
Joe Tsai90fe9962018-10-18 11:06:29 -0700247 } else if t.Kind() == reflect.Map {
248 m := &ptype.StandaloneMessage{
Damien Neil232ea152018-12-10 15:14:36 -0800249 Syntax: parent.Syntax,
Joe Tsai21ade492019-05-22 13:42:54 -0400250 FullName: parent.FullName.Append(legacyMapEntryName(f.Name)),
Damien Neil232ea152018-12-10 15:14:36 -0800251 IsMapEntry: true,
Joe Tsai90fe9962018-10-18 11:06:29 -0700252 Fields: []ptype.Field{
253 ms.parseField(tagKey, "", "", t.Key(), nil),
254 ms.parseField(tagVal, "", "", t.Elem(), nil),
255 },
256 }
257 ms.visit(m, t)
258 f.MessageType = ptype.PlaceholderMessage(m.FullName)
Joe Tsai21ade492019-05-22 13:42:54 -0400259 } else if mv, ok := legacyMessageDescCache.Load(t); ok {
Joe Tsai90fe9962018-10-18 11:06:29 -0700260 f.MessageType = mv.(pref.MessageDescriptor)
261 } else {
262 f.MessageType = ms.processMessage(t)
263 }
264 }
265 return f
266}
267
Joe Tsai21ade492019-05-22 13:42:54 -0400268func (ms *legacyMessageDescSet) visit(m *ptype.StandaloneMessage, t reflect.Type) {
Joe Tsai90fe9962018-10-18 11:06:29 -0700269 if ms.visited == nil {
270 ms.visited = make(map[reflect.Type]*ptype.StandaloneMessage)
271 }
272 if t.Kind() != reflect.Map {
273 ms.visited[t] = m
274 }
275 ms.descs = append(ms.descs, m)
276 ms.types = append(ms.types, t)
277}
278
Joe Tsai21ade492019-05-22 13:42:54 -0400279// legacyDeriveFullName derives a fully qualified protobuf name for the given Go type
Joe Tsai90fe9962018-10-18 11:06:29 -0700280// The provided name is not guaranteed to be stable nor universally unique.
281// It should be sufficiently unique within a program.
Joe Tsai21ade492019-05-22 13:42:54 -0400282func legacyDeriveFullName(t reflect.Type) pref.FullName {
Joe Tsai90fe9962018-10-18 11:06:29 -0700283 sanitize := func(r rune) rune {
284 switch {
285 case r == '/':
286 return '.'
287 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
288 return r
289 default:
290 return '_'
291 }
292 }
293 prefix := strings.Map(sanitize, t.PkgPath())
294 suffix := strings.Map(sanitize, t.Name())
295 if suffix == "" {
296 suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
297 }
298
299 ss := append(strings.Split(prefix, "."), suffix)
300 for i, s := range ss {
301 if s == "" || ('0' <= s[0] && s[0] <= '9') {
302 ss[i] = "x" + s
303 }
304 }
305 return pref.FullName(strings.Join(ss, "."))
306}
307
Joe Tsai21ade492019-05-22 13:42:54 -0400308// legacyMapEntryName derives the message name for a map field of a given name.
Joe Tsai90fe9962018-10-18 11:06:29 -0700309// This is identical to MapEntryName from parser.cc in the protoc source.
Joe Tsai21ade492019-05-22 13:42:54 -0400310func legacyMapEntryName(s pref.Name) pref.Name {
Joe Tsai90fe9962018-10-18 11:06:29 -0700311 var b []byte
312 nextUpper := true
313 for i := 0; i < len(s); i++ {
314 if c := s[i]; c == '_' {
315 nextUpper = true
316 } else {
317 if nextUpper {
318 c = byte(unicode.ToUpper(rune(c)))
319 nextUpper = false
320 }
321 b = append(b, c)
322 }
323 }
324 return pref.Name(append(b, "Entry"...))
325}