blob: 2a6b29d1791c7754567f8963ebed4309feeef04b [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"
14 "google.golang.org/protobuf/proto"
15 "google.golang.org/protobuf/reflect/protoreflect"
16 "google.golang.org/protobuf/types/descriptorpb"
17 gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
18)
19
20const (
21 SupportedEditionsMinimum = descriptorpb.Edition_EDITION_PROTO2
22 SupportedEditionsMaximum = descriptorpb.Edition_EDITION_2023
23)
24
25var defaults = &descriptorpb.FeatureSetDefaults{}
26var defaultsCacheMu sync.Mutex
27var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet)
28
29func init() {
30 err := proto.Unmarshal(editiondefaults.Defaults, defaults)
31 if err != nil {
32 fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err)
33 os.Exit(1)
34 }
35}
36
37func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition {
38 return filedesc.Edition(epb)
39}
40
41func toEditionProto(ed filedesc.Edition) descriptorpb.Edition {
42 switch ed {
43 case filedesc.EditionUnknown:
44 return descriptorpb.Edition_EDITION_UNKNOWN
45 case filedesc.EditionProto2:
46 return descriptorpb.Edition_EDITION_PROTO2
47 case filedesc.EditionProto3:
48 return descriptorpb.Edition_EDITION_PROTO3
49 case filedesc.Edition2023:
50 return descriptorpb.Edition_EDITION_2023
51 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)
63 if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb {
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 fs := defaults.GetDefaults()[0].GetFeatures()
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 fs = def.GetFeatures()
78 } else {
79 break
80 }
81 }
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 if goFeatures, ok := proto.GetExtension(child, gofeaturespb.E_Go).(*gofeaturespb.GoFeatures); ok && goFeatures != nil {
130 if luje := goFeatures.LegacyUnmarshalJsonEnum; luje != nil {
131 parentFS.GenerateLegacyUnmarshalJSON = *luje
132 }
133 }
134
135 return parentFS
136}
137
138// initFileDescFromFeatureSet initializes editions related fields in fd based
139// on fs. If fs is nil it is assumed to be an empty featureset and all fields
140// will be initialized with the appropriate default. fd.L1.Edition must be set
141// before calling this function.
142func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) {
143 dfs := getFeatureSetFor(fd.L1.Edition)
144 // initialize the featureset with the defaults
145 fd.L1.EditionFeatures = mergeEditionFeatures(fd, dfs)
146 // overwrite any options explicitly specified
147 fd.L1.EditionFeatures = mergeEditionFeatures(fd, fs)
148}