editions.go raw

   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