blob: 147b8c7398d5d3773806e5ddbbff5991915c7193 [file] [log] [blame]
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +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"
Abhay Kumarfe505f22025-11-10 14:16:31 +000014 "google.golang.org/protobuf/internal/genid"
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053015 "google.golang.org/protobuf/proto"
16 "google.golang.org/protobuf/reflect/protoreflect"
17 "google.golang.org/protobuf/types/descriptorpb"
Abhay Kumarfe505f22025-11-10 14:16:31 +000018 "google.golang.org/protobuf/types/gofeaturespb"
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053019)
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
Abhay Kumarfe505f22025-11-10 14:16:31 +000047 case filedesc.Edition2024:
48 return descriptorpb.Edition_EDITION_2024
bseenivadd66c362026-02-12 19:13:26 +053049 case filedesc.EditionUnstable:
50 return descriptorpb.Edition_EDITION_UNSTABLE
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053051 default:
52 panic(fmt.Sprintf("unknown value for edition: %v", ed))
53 }
54}
55
56func 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)
bseenivadd66c362026-02-12 19:13:26 +053063 if (defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb) && edpb != descriptorpb.Edition_EDITION_UNSTABLE {
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053064 // 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 }
Abhay Kumarfe505f22025-11-10 14:16:31 +000070 fsed := defaults.GetDefaults()[0]
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053071 // 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 {
Abhay Kumarfe505f22025-11-10 14:16:31 +000077 fsed = def
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053078 } else {
79 break
80 }
81 }
Abhay Kumarfe505f22025-11-10 14:16:31 +000082 fs := proto.Clone(fsed.GetFixedFeatures()).(*descriptorpb.FeatureSet)
83 proto.Merge(fs, fsed.GetOverridableFeatures())
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +053084 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.
93func 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
Abhay Kumarfe505f22025-11-10 14:16:31 +0000131 // 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())
Akash Reddy Kankanala105581b2024-09-11 05:20:38 +0530168 }
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.
177func 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}