| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package protodesc |
| |
| import ( |
| _ "embed" |
| "fmt" |
| "os" |
| "sync" |
| |
| "google.golang.org/protobuf/internal/filedesc" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/types/descriptorpb" |
| ) |
| |
| const ( |
| SupportedEditionsMinimum = descriptorpb.Edition_EDITION_PROTO2 |
| SupportedEditionsMaximum = descriptorpb.Edition_EDITION_2023 |
| ) |
| |
| //go:embed editions_defaults.binpb |
| var binaryEditionDefaults []byte |
| var defaults = &descriptorpb.FeatureSetDefaults{} |
| var defaultsCacheMu sync.Mutex |
| var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet) |
| |
| func init() { |
| err := proto.Unmarshal(binaryEditionDefaults, defaults) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err) |
| os.Exit(1) |
| } |
| } |
| |
| func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition { |
| return filedesc.Edition(epb) |
| } |
| |
| func toEditionProto(ed filedesc.Edition) descriptorpb.Edition { |
| switch ed { |
| case filedesc.EditionUnknown: |
| return descriptorpb.Edition_EDITION_UNKNOWN |
| case filedesc.EditionProto2: |
| return descriptorpb.Edition_EDITION_PROTO2 |
| case filedesc.EditionProto3: |
| return descriptorpb.Edition_EDITION_PROTO3 |
| case filedesc.Edition2023: |
| return descriptorpb.Edition_EDITION_2023 |
| default: |
| panic(fmt.Sprintf("unknown value for edition: %v", ed)) |
| } |
| } |
| |
| func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet { |
| defaultsCacheMu.Lock() |
| defer defaultsCacheMu.Unlock() |
| if def, ok := defaultsCache[ed]; ok { |
| return def |
| } |
| edpb := toEditionProto(ed) |
| if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb { |
| // This should never happen protodesc.(FileOptions).New would fail when |
| // initializing the file descriptor. |
| // This most likely means the embedded defaults were not updated. |
| 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) |
| os.Exit(1) |
| } |
| fs := defaults.GetDefaults()[0].GetFeatures() |
| // Using a linear search for now. |
| // Editions are guaranteed to be sorted and thus we could use a binary search. |
| // Given that there are only a handful of editions (with one more per year) |
| // there is not much reason to use a binary search. |
| for _, def := range defaults.GetDefaults() { |
| if def.GetEdition() <= edpb { |
| fs = def.GetFeatures() |
| } else { |
| break |
| } |
| } |
| defaultsCache[ed] = fs |
| return fs |
| } |
| |
| func resolveFeatureHasFieldPresence(fileDesc *filedesc.File, fieldDesc *descriptorpb.FieldDescriptorProto) bool { |
| fs := fieldDesc.GetOptions().GetFeatures() |
| if fs == nil || fs.FieldPresence == nil { |
| return fileDesc.L1.EditionFeatures.IsFieldPresence |
| } |
| return fs.GetFieldPresence() == descriptorpb.FeatureSet_LEGACY_REQUIRED || |
| fs.GetFieldPresence() == descriptorpb.FeatureSet_EXPLICIT |
| } |
| |
| func resolveFeatureRepeatedFieldEncodingPacked(fileDesc *filedesc.File, fieldDesc *descriptorpb.FieldDescriptorProto) bool { |
| fs := fieldDesc.GetOptions().GetFeatures() |
| if fs == nil || fs.RepeatedFieldEncoding == nil { |
| return fileDesc.L1.EditionFeatures.IsPacked |
| } |
| return fs.GetRepeatedFieldEncoding() == descriptorpb.FeatureSet_PACKED |
| } |
| |
| func resolveFeatureEnforceUTF8(fileDesc *filedesc.File, fieldDesc *descriptorpb.FieldDescriptorProto) bool { |
| fs := fieldDesc.GetOptions().GetFeatures() |
| if fs == nil || fs.Utf8Validation == nil { |
| return fileDesc.L1.EditionFeatures.IsUTF8Validated |
| } |
| return fs.GetUtf8Validation() == descriptorpb.FeatureSet_VERIFY |
| } |
| |
| func resolveFeatureDelimitedEncoding(fileDesc *filedesc.File, fieldDesc *descriptorpb.FieldDescriptorProto) bool { |
| fs := fieldDesc.GetOptions().GetFeatures() |
| if fs == nil || fs.MessageEncoding == nil { |
| return fileDesc.L1.EditionFeatures.IsDelimitedEncoded |
| } |
| return fs.GetMessageEncoding() == descriptorpb.FeatureSet_DELIMITED |
| } |
| |
| // initFileDescFromFeatureSet initializes editions related fields in fd based |
| // on fs. If fs is nil it is assumed to be an empty featureset and all fields |
| // will be initialized with the appropriate default. fd.L1.Edition must be set |
| // before calling this function. |
| func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) { |
| dfs := getFeatureSetFor(fd.L1.Edition) |
| if fs == nil { |
| fs = &descriptorpb.FeatureSet{} |
| } |
| |
| var fieldPresence descriptorpb.FeatureSet_FieldPresence |
| if fp := fs.FieldPresence; fp != nil { |
| fieldPresence = *fp |
| } else { |
| fieldPresence = *dfs.FieldPresence |
| } |
| fd.L1.EditionFeatures.IsFieldPresence = fieldPresence == descriptorpb.FeatureSet_LEGACY_REQUIRED || |
| fieldPresence == descriptorpb.FeatureSet_EXPLICIT |
| |
| var enumType descriptorpb.FeatureSet_EnumType |
| if et := fs.EnumType; et != nil { |
| enumType = *et |
| } else { |
| enumType = *dfs.EnumType |
| } |
| fd.L1.EditionFeatures.IsOpenEnum = enumType == descriptorpb.FeatureSet_OPEN |
| |
| var respeatedFieldEncoding descriptorpb.FeatureSet_RepeatedFieldEncoding |
| if rfe := fs.RepeatedFieldEncoding; rfe != nil { |
| respeatedFieldEncoding = *rfe |
| } else { |
| respeatedFieldEncoding = *dfs.RepeatedFieldEncoding |
| } |
| fd.L1.EditionFeatures.IsPacked = respeatedFieldEncoding == descriptorpb.FeatureSet_PACKED |
| |
| var isUTF8Validated descriptorpb.FeatureSet_Utf8Validation |
| if utf8val := fs.Utf8Validation; utf8val != nil { |
| isUTF8Validated = *utf8val |
| } else { |
| isUTF8Validated = *dfs.Utf8Validation |
| } |
| fd.L1.EditionFeatures.IsUTF8Validated = isUTF8Validated == descriptorpb.FeatureSet_VERIFY |
| |
| var messageEncoding descriptorpb.FeatureSet_MessageEncoding |
| if me := fs.MessageEncoding; me != nil { |
| messageEncoding = *me |
| } else { |
| messageEncoding = *dfs.MessageEncoding |
| } |
| fd.L1.EditionFeatures.IsDelimitedEncoded = messageEncoding == descriptorpb.FeatureSet_DELIMITED |
| |
| var jsonFormat descriptorpb.FeatureSet_JsonFormat |
| if jf := fs.JsonFormat; jf != nil { |
| jsonFormat = *jf |
| } else { |
| jsonFormat = *dfs.JsonFormat |
| } |
| fd.L1.EditionFeatures.IsJSONCompliant = jsonFormat == descriptorpb.FeatureSet_ALLOW |
| } |