David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 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. |
| 6 | package descfmt |
| 7 | |
| 8 | import ( |
| 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 | pref "google.golang.org/protobuf/reflect/protoreflect" |
| 18 | ) |
| 19 | |
| 20 | type list interface { |
| 21 | Len() int |
| 22 | pragma.DoNotImplement |
| 23 | } |
| 24 | |
| 25 | func FormatList(s fmt.State, r rune, vs list) { |
| 26 | io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) |
| 27 | } |
| 28 | func formatListOpt(vs list, isRoot, allowMulti bool) string { |
| 29 | start, end := "[", "]" |
| 30 | if isRoot { |
| 31 | var name string |
| 32 | switch vs.(type) { |
| 33 | case pref.Names: |
| 34 | name = "Names" |
| 35 | case pref.FieldNumbers: |
| 36 | name = "FieldNumbers" |
| 37 | case pref.FieldRanges: |
| 38 | name = "FieldRanges" |
| 39 | case pref.EnumRanges: |
| 40 | name = "EnumRanges" |
| 41 | case pref.FileImports: |
| 42 | name = "FileImports" |
| 43 | case pref.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 pref.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 pref.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 pref.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 pref.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 pref.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.(pref.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.(pref.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. |
| 108 | var descriptorAccessors = map[reflect.Type][]string{ |
| 109 | reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"}, |
| 110 | reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"}, |
| 111 | reflect.TypeOf((*pref.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((*pref.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt |
| 113 | reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem(): {"Values", "ReservedNames", "ReservedRanges"}, |
| 114 | reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"}, |
| 115 | reflect.TypeOf((*pref.ServiceDescriptor)(nil)).Elem(): {"Methods"}, |
| 116 | reflect.TypeOf((*pref.MethodDescriptor)(nil)).Elem(): {"Input", "Output", "IsStreamingClient", "IsStreamingServer"}, |
| 117 | } |
| 118 | |
| 119 | func FormatDesc(s fmt.State, r rune, t pref.Descriptor) { |
| 120 | io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) |
| 121 | } |
| 122 | func formatDescOpt(t pref.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.(pref.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 pref.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 pref.EnumKind: |
| 160 | rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())}) |
| 161 | case pref.MessageKind, pref.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 pref.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 | |
| 202 | type records struct { |
| 203 | recs [][2]string |
| 204 | allowMulti bool |
| 205 | } |
| 206 | |
| 207 | func (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().(pref.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 pref.FieldDescriptor, pref.OneofDescriptor, pref.EnumValueDescriptor, pref.MethodDescriptor: |
| 254 | s = string(v.(pref.Descriptor).Name()) |
| 255 | case pref.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 | |
| 268 | func (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 | |
| 299 | func 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 | |
| 310 | func 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 | } |