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