1 // Copyright 2019 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 protodesc
6 7 import (
8 "fmt"
9 "os"
10 "sync"
11 12 "google.golang.org/protobuf/internal/editiondefaults"
13 "google.golang.org/protobuf/internal/filedesc"
14 "google.golang.org/protobuf/internal/genid"
15 "google.golang.org/protobuf/proto"
16 "google.golang.org/protobuf/reflect/protoreflect"
17 "google.golang.org/protobuf/types/descriptorpb"
18 "google.golang.org/protobuf/types/gofeaturespb"
19 )
20 21 var defaults = &descriptorpb.FeatureSetDefaults{}
22 var defaultsCacheMu sync.Mutex
23 var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet)
24 25 func init() {
26 err := proto.Unmarshal(editiondefaults.Defaults, defaults)
27 if err != nil {
28 fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err)
29 os.Exit(1)
30 }
31 }
32 33 func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition {
34 return filedesc.Edition(epb)
35 }
36 37 func toEditionProto(ed filedesc.Edition) descriptorpb.Edition {
38 switch ed {
39 case filedesc.EditionUnknown:
40 return descriptorpb.Edition_EDITION_UNKNOWN
41 case filedesc.EditionProto2:
42 return descriptorpb.Edition_EDITION_PROTO2
43 case filedesc.EditionProto3:
44 return descriptorpb.Edition_EDITION_PROTO3
45 case filedesc.Edition2023:
46 return descriptorpb.Edition_EDITION_2023
47 case filedesc.Edition2024:
48 return descriptorpb.Edition_EDITION_2024
49 case filedesc.EditionUnstable:
50 return descriptorpb.Edition_EDITION_UNSTABLE
51 default:
52 panic(fmt.Sprintf("unknown value for edition: %v", ed))
53 }
54 }
55 56 func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet {
57 defaultsCacheMu.Lock()
58 defer defaultsCacheMu.Unlock()
59 if def, ok := defaultsCache[ed]; ok {
60 return def
61 }
62 edpb := toEditionProto(ed)
63 if (defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb) && edpb != descriptorpb.Edition_EDITION_UNSTABLE {
64 // This should never happen protodesc.(FileOptions).New would fail when
65 // initializing the file descriptor.
66 // This most likely means the embedded defaults were not updated.
67 fmt.Fprintf(os.Stderr, "internal error: unsupported edition %v (did you forget to update the embedded defaults (i.e. the bootstrap descriptor proto)?)\n", edpb)
68 os.Exit(1)
69 }
70 fsed := defaults.GetDefaults()[0]
71 // Using a linear search for now.
72 // Editions are guaranteed to be sorted and thus we could use a binary search.
73 // Given that there are only a handful of editions (with one more per year)
74 // there is not much reason to use a binary search.
75 for _, def := range defaults.GetDefaults() {
76 if def.GetEdition() <= edpb {
77 fsed = def
78 } else {
79 break
80 }
81 }
82 fs := proto.Clone(fsed.GetFixedFeatures()).(*descriptorpb.FeatureSet)
83 proto.Merge(fs, fsed.GetOverridableFeatures())
84 defaultsCache[ed] = fs
85 return fs
86 }
87 88 // mergeEditionFeatures merges the parent and child feature sets. This function
89 // should be used when initializing Go descriptors from descriptor protos which
90 // is why the parent is a filedesc.EditionsFeatures (Go representation) while
91 // the child is a descriptorproto.FeatureSet (protoc representation).
92 // Any feature set by the child overwrites what is set by the parent.
93 func mergeEditionFeatures(parentDesc protoreflect.Descriptor, child *descriptorpb.FeatureSet) filedesc.EditionFeatures {
94 var parentFS filedesc.EditionFeatures
95 switch p := parentDesc.(type) {
96 case *filedesc.File:
97 parentFS = p.L1.EditionFeatures
98 case *filedesc.Message:
99 parentFS = p.L1.EditionFeatures
100 default:
101 panic(fmt.Sprintf("unknown parent type %T", parentDesc))
102 }
103 if child == nil {
104 return parentFS
105 }
106 if fp := child.FieldPresence; fp != nil {
107 parentFS.IsFieldPresence = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED ||
108 *fp == descriptorpb.FeatureSet_EXPLICIT
109 parentFS.IsLegacyRequired = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED
110 }
111 if et := child.EnumType; et != nil {
112 parentFS.IsOpenEnum = *et == descriptorpb.FeatureSet_OPEN
113 }
114 115 if rfe := child.RepeatedFieldEncoding; rfe != nil {
116 parentFS.IsPacked = *rfe == descriptorpb.FeatureSet_PACKED
117 }
118 119 if utf8val := child.Utf8Validation; utf8val != nil {
120 parentFS.IsUTF8Validated = *utf8val == descriptorpb.FeatureSet_VERIFY
121 }
122 123 if me := child.MessageEncoding; me != nil {
124 parentFS.IsDelimitedEncoded = *me == descriptorpb.FeatureSet_DELIMITED
125 }
126 127 if jf := child.JsonFormat; jf != nil {
128 parentFS.IsJSONCompliant = *jf == descriptorpb.FeatureSet_ALLOW
129 }
130 131 // We must not use proto.GetExtension(child, gofeaturespb.E_Go)
132 // because that only works for messages we generated, but not for
133 // dynamicpb messages. See golang/protobuf#1669.
134 //
135 // Further, we harden this code against adversarial inputs: a
136 // service which accepts descriptors from a possibly malicious
137 // source shouldn't crash.
138 goFeatures := child.ProtoReflect().Get(gofeaturespb.E_Go.TypeDescriptor())
139 if !goFeatures.IsValid() {
140 return parentFS
141 }
142 gf, ok := goFeatures.Interface().(protoreflect.Message)
143 if !ok {
144 return parentFS
145 }
146 // gf.Interface() could be *dynamicpb.Message or *gofeaturespb.GoFeatures.
147 fields := gf.Descriptor().Fields()
148 149 if fd := fields.ByNumber(genid.GoFeatures_LegacyUnmarshalJsonEnum_field_number); fd != nil &&
150 !fd.IsList() &&
151 fd.Kind() == protoreflect.BoolKind &&
152 gf.Has(fd) {
153 parentFS.GenerateLegacyUnmarshalJSON = gf.Get(fd).Bool()
154 }
155 156 if fd := fields.ByNumber(genid.GoFeatures_StripEnumPrefix_field_number); fd != nil &&
157 !fd.IsList() &&
158 fd.Kind() == protoreflect.EnumKind &&
159 gf.Has(fd) {
160 parentFS.StripEnumPrefix = int(gf.Get(fd).Enum())
161 }
162 163 if fd := fields.ByNumber(genid.GoFeatures_ApiLevel_field_number); fd != nil &&
164 !fd.IsList() &&
165 fd.Kind() == protoreflect.EnumKind &&
166 gf.Has(fd) {
167 parentFS.APILevel = int(gf.Get(fd).Enum())
168 }
169 170 return parentFS
171 }
172 173 // initFileDescFromFeatureSet initializes editions related fields in fd based
174 // on fs. If fs is nil it is assumed to be an empty featureset and all fields
175 // will be initialized with the appropriate default. fd.L1.Edition must be set
176 // before calling this function.
177 func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) {
178 dfs := getFeatureSetFor(fd.L1.Edition)
179 // initialize the featureset with the defaults
180 fd.L1.EditionFeatures = mergeEditionFeatures(fd, dfs)
181 // overwrite any options explicitly specified
182 fd.L1.EditionFeatures = mergeEditionFeatures(fd, fs)
183 }
184