xref: /aosp_15_r20/external/golang-protobuf/internal/descfmt/stringer.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
1// 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
5// Package descfmt provides functionality to format descriptors.
6package descfmt
7
8import (
9	"fmt"
10	"io"
11	"reflect"
12	"strconv"
13	"strings"
14
15	"google.golang.org/protobuf/internal/detrand"
16	"google.golang.org/protobuf/internal/pragma"
17	"google.golang.org/protobuf/reflect/protoreflect"
18)
19
20type list interface {
21	Len() int
22	pragma.DoNotImplement
23}
24
25func FormatList(s fmt.State, r rune, vs list) {
26	io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
27}
28func formatListOpt(vs list, isRoot, allowMulti bool) string {
29	start, end := "[", "]"
30	if isRoot {
31		var name string
32		switch vs.(type) {
33		case protoreflect.Names:
34			name = "Names"
35		case protoreflect.FieldNumbers:
36			name = "FieldNumbers"
37		case protoreflect.FieldRanges:
38			name = "FieldRanges"
39		case protoreflect.EnumRanges:
40			name = "EnumRanges"
41		case protoreflect.FileImports:
42			name = "FileImports"
43		case protoreflect.Descriptor:
44			name = reflect.ValueOf(vs).MethodByName("Get").Type().Out(0).Name() + "s"
45		default:
46			name = reflect.ValueOf(vs).Elem().Type().Name()
47		}
48		start, end = name+"{", "}"
49	}
50
51	var ss []string
52	switch vs := vs.(type) {
53	case protoreflect.Names:
54		for i := 0; i < vs.Len(); i++ {
55			ss = append(ss, fmt.Sprint(vs.Get(i)))
56		}
57		return start + joinStrings(ss, false) + end
58	case protoreflect.FieldNumbers:
59		for i := 0; i < vs.Len(); i++ {
60			ss = append(ss, fmt.Sprint(vs.Get(i)))
61		}
62		return start + joinStrings(ss, false) + end
63	case protoreflect.FieldRanges:
64		for i := 0; i < vs.Len(); i++ {
65			r := vs.Get(i)
66			if r[0]+1 == r[1] {
67				ss = append(ss, fmt.Sprintf("%d", r[0]))
68			} else {
69				ss = append(ss, fmt.Sprintf("%d:%d", r[0], r[1])) // enum ranges are end exclusive
70			}
71		}
72		return start + joinStrings(ss, false) + end
73	case protoreflect.EnumRanges:
74		for i := 0; i < vs.Len(); i++ {
75			r := vs.Get(i)
76			if r[0] == r[1] {
77				ss = append(ss, fmt.Sprintf("%d", r[0]))
78			} else {
79				ss = append(ss, fmt.Sprintf("%d:%d", r[0], int64(r[1])+1)) // enum ranges are end inclusive
80			}
81		}
82		return start + joinStrings(ss, false) + end
83	case protoreflect.FileImports:
84		for i := 0; i < vs.Len(); i++ {
85			var rs records
86			rs.Append(reflect.ValueOf(vs.Get(i)), "Path", "Package", "IsPublic", "IsWeak")
87			ss = append(ss, "{"+rs.Join()+"}")
88		}
89		return start + joinStrings(ss, allowMulti) + end
90	default:
91		_, isEnumValue := vs.(protoreflect.EnumValueDescriptors)
92		for i := 0; i < vs.Len(); i++ {
93			m := reflect.ValueOf(vs).MethodByName("Get")
94			v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
95			ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue))
96		}
97		return start + joinStrings(ss, allowMulti && isEnumValue) + end
98	}
99}
100
101// descriptorAccessors is a list of accessors to print for each descriptor.
102//
103// Do not print all accessors since some contain redundant information,
104// while others are pointers that we do not want to follow since the descriptor
105// is actually a cyclic graph.
106//
107// Using a list allows us to print the accessors in a sensible order.
108var descriptorAccessors = map[reflect.Type][]string{
109	reflect.TypeOf((*protoreflect.FileDescriptor)(nil)).Elem():      {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
110	reflect.TypeOf((*protoreflect.MessageDescriptor)(nil)).Elem():   {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
111	reflect.TypeOf((*protoreflect.FieldDescriptor)(nil)).Elem():     {"Number", "Cardinality", "Kind", "HasJSONName", "JSONName", "HasPresence", "IsExtension", "IsPacked", "IsWeak", "IsList", "IsMap", "MapKey", "MapValue", "HasDefault", "Default", "ContainingOneof", "ContainingMessage", "Message", "Enum"},
112	reflect.TypeOf((*protoreflect.OneofDescriptor)(nil)).Elem():     {"Fields"}, // not directly used; must keep in sync with formatDescOpt
113	reflect.TypeOf((*protoreflect.EnumDescriptor)(nil)).Elem():      {"Values", "ReservedNames", "ReservedRanges"},
114	reflect.TypeOf((*protoreflect.EnumValueDescriptor)(nil)).Elem(): {"Number"},
115	reflect.TypeOf((*protoreflect.ServiceDescriptor)(nil)).Elem():   {"Methods"},
116	reflect.TypeOf((*protoreflect.MethodDescriptor)(nil)).Elem():    {"Input", "Output", "IsStreamingClient", "IsStreamingServer"},
117}
118
119func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
120	io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
121}
122func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool) string {
123	rv := reflect.ValueOf(t)
124	rt := rv.MethodByName("ProtoType").Type().In(0)
125
126	start, end := "{", "}"
127	if isRoot {
128		start = rt.Name() + "{"
129	}
130
131	_, isFile := t.(protoreflect.FileDescriptor)
132	rs := records{allowMulti: allowMulti}
133	if t.IsPlaceholder() {
134		if isFile {
135			rs.Append(rv, "Path", "Package", "IsPlaceholder")
136		} else {
137			rs.Append(rv, "FullName", "IsPlaceholder")
138		}
139	} else {
140		switch {
141		case isFile:
142			rs.Append(rv, "Syntax")
143		case isRoot:
144			rs.Append(rv, "Syntax", "FullName")
145		default:
146			rs.Append(rv, "Name")
147		}
148		switch t := t.(type) {
149		case protoreflect.FieldDescriptor:
150			for _, s := range descriptorAccessors[rt] {
151				switch s {
152				case "MapKey":
153					if k := t.MapKey(); k != nil {
154						rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
155					}
156				case "MapValue":
157					if v := t.MapValue(); v != nil {
158						switch v.Kind() {
159						case protoreflect.EnumKind:
160							rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())})
161						case protoreflect.MessageKind, protoreflect.GroupKind:
162							rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Message().FullName())})
163						default:
164							rs.recs = append(rs.recs, [2]string{"MapValue", v.Kind().String()})
165						}
166					}
167				case "ContainingOneof":
168					if od := t.ContainingOneof(); od != nil {
169						rs.recs = append(rs.recs, [2]string{"Oneof", string(od.Name())})
170					}
171				case "ContainingMessage":
172					if t.IsExtension() {
173						rs.recs = append(rs.recs, [2]string{"Extendee", string(t.ContainingMessage().FullName())})
174					}
175				case "Message":
176					if !t.IsMap() {
177						rs.Append(rv, s)
178					}
179				default:
180					rs.Append(rv, s)
181				}
182			}
183		case protoreflect.OneofDescriptor:
184			var ss []string
185			fs := t.Fields()
186			for i := 0; i < fs.Len(); i++ {
187				ss = append(ss, string(fs.Get(i).Name()))
188			}
189			if len(ss) > 0 {
190				rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
191			}
192		default:
193			rs.Append(rv, descriptorAccessors[rt]...)
194		}
195		if rv.MethodByName("GoType").IsValid() {
196			rs.Append(rv, "GoType")
197		}
198	}
199	return start + rs.Join() + end
200}
201
202type records struct {
203	recs       [][2]string
204	allowMulti bool
205}
206
207func (rs *records) Append(v reflect.Value, accessors ...string) {
208	for _, a := range accessors {
209		var rv reflect.Value
210		if m := v.MethodByName(a); m.IsValid() {
211			rv = m.Call(nil)[0]
212		}
213		if v.Kind() == reflect.Struct && !rv.IsValid() {
214			rv = v.FieldByName(a)
215		}
216		if !rv.IsValid() {
217			panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a))
218		}
219		if _, ok := rv.Interface().(protoreflect.Value); ok {
220			rv = rv.MethodByName("Interface").Call(nil)[0]
221			if !rv.IsNil() {
222				rv = rv.Elem()
223			}
224		}
225
226		// Ignore zero values.
227		var isZero bool
228		switch rv.Kind() {
229		case reflect.Interface, reflect.Slice:
230			isZero = rv.IsNil()
231		case reflect.Bool:
232			isZero = rv.Bool() == false
233		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
234			isZero = rv.Int() == 0
235		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
236			isZero = rv.Uint() == 0
237		case reflect.String:
238			isZero = rv.String() == ""
239		}
240		if n, ok := rv.Interface().(list); ok {
241			isZero = n.Len() == 0
242		}
243		if isZero {
244			continue
245		}
246
247		// Format the value.
248		var s string
249		v := rv.Interface()
250		switch v := v.(type) {
251		case list:
252			s = formatListOpt(v, false, rs.allowMulti)
253		case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
254			s = string(v.(protoreflect.Descriptor).Name())
255		case protoreflect.Descriptor:
256			s = string(v.FullName())
257		case string:
258			s = strconv.Quote(v)
259		case []byte:
260			s = fmt.Sprintf("%q", v)
261		default:
262			s = fmt.Sprint(v)
263		}
264		rs.recs = append(rs.recs, [2]string{a, s})
265	}
266}
267
268func (rs *records) Join() string {
269	var ss []string
270
271	// In single line mode, simply join all records with commas.
272	if !rs.allowMulti {
273		for _, r := range rs.recs {
274			ss = append(ss, r[0]+formatColon(0)+r[1])
275		}
276		return joinStrings(ss, false)
277	}
278
279	// In allowMulti line mode, align single line records for more readable output.
280	var maxLen int
281	flush := func(i int) {
282		for _, r := range rs.recs[len(ss):i] {
283			ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
284		}
285		maxLen = 0
286	}
287	for i, r := range rs.recs {
288		if isMulti := strings.Contains(r[1], "\n"); isMulti {
289			flush(i)
290			ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
291		} else if maxLen < len(r[0]) {
292			maxLen = len(r[0])
293		}
294	}
295	flush(len(rs.recs))
296	return joinStrings(ss, true)
297}
298
299func formatColon(padding int) string {
300	// Deliberately introduce instability into the debug output to
301	// discourage users from performing string comparisons.
302	// This provides us flexibility to change the output in the future.
303	if detrand.Bool() {
304		return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
305	} else {
306		return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
307	}
308}
309
310func joinStrings(ss []string, isMulti bool) string {
311	if len(ss) == 0 {
312		return ""
313	}
314	if isMulti {
315		return "\n\t" + strings.Join(ss, "\n\t") + "\n"
316	}
317	return strings.Join(ss, ", ")
318}
319