• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using Google.Protobuf.Collections;
11 using Google.Protobuf.WellKnownTypes;
12 using System;
13 using System.Collections.Generic;
14 using System.Collections.ObjectModel;
15 using System.Linq;
16 using System.Threading;
17 using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
18 
19 namespace Google.Protobuf.Reflection
20 {
21     /// <summary>
22     /// The syntax of a .proto file
23     /// </summary>
24     [Obsolete("Use features instead")]
25     public enum Syntax
26     {
27         /// <summary>
28         /// Proto2 syntax
29         /// </summary>
30         Proto2,
31         /// <summary>
32         /// Proto3 syntax
33         /// </summary>
34         Proto3,
35         /// <summary>
36         /// Editions syntax
37         /// </summary>
38         Editions,
39         /// <summary>
40         /// An unknown declared syntax
41         /// </summary>
42         Unknown
43     }
44 
45     /// <summary>
46     /// Describes a .proto file, including everything defined within.
47     /// IDescriptor is implemented such that the File property returns this descriptor,
48     /// and the FullName is the same as the Name.
49     /// </summary>
50     public sealed class FileDescriptor : IDescriptor
51     {
52         // Prevent linker failures when using IL2CPP with the well-known types.
FileDescriptor()53         static FileDescriptor()
54         {
55 #pragma warning disable CS0618 // Type or member is obsolete
56             ForceReflectionInitialization<Syntax>();
57 #pragma warning restore CS0618 // Type or member is obsolete
58             ForceReflectionInitialization<NullValue>();
59             ForceReflectionInitialization<Field.Types.Cardinality>();
60             ForceReflectionInitialization<Field.Types.Kind>();
61             ForceReflectionInitialization<Value.KindOneofCase>();
62         }
63 
64         private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations;
65 
FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)66         private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
67         {
68             SerializedData = descriptorData;
69             DescriptorPool = pool;
70             Proto = proto;
71             // Note: the Edition property relies on the proto being set first, so this line
72             // has to come after Proto = proto.
73             Features = FeatureSetDescriptor.GetEditionDefaults(Edition).MergedWith(proto.Options?.Features);
74             Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList());
75 
76             PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies);
77 
78             pool.AddPackage(Package, this);
79 
80             MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType,
81                                                                  (message, index) =>
82                                                                  new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index]));
83 
84             EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType,
85                                                               (enumType, index) =>
86                                                               new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index]));
87 
88             Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
89                                                              (service, index) =>
90                                                              new ServiceDescriptor(service, this, index));
91 
92             Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions);
93 
94             declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication);
95         }
96 
CreateDeclarationMap()97         private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap()
98         {
99             var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>();
100             foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>())
101             {
102                 var descriptor = FindDescriptorForPath(location.Path);
103                 if (descriptor != null)
104                 {
105                     dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location);
106                 }
107             }
108             return dictionary;
109         }
110 
FindDescriptorForPath(IList<int> path)111         private IDescriptor FindDescriptorForPath(IList<int> path)
112         {
113             // All complete declarations have an even, non-empty path length
114             // (There can be an empty path for a descriptor declaration, but that can't have any comments,
115             // so we currently ignore it.)
116             if (path.Count == 0 || (path.Count & 1) != 0)
117             {
118                 return null;
119             }
120             IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]);
121             DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]);
122 
123             for (int i = 2; current != null && i < path.Count; i += 2)
124             {
125                 var list = current.GetNestedDescriptorListForField(path[i]);
126                 current = GetDescriptorFromList(list, path[i + 1]);
127             }
128             return current;
129         }
130 
GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)131         private DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)
132         {
133             // This is fine: it may be a newer version of protobuf than we understand, with a new descriptor
134             // field.
135             if (list == null)
136             {
137                 return null;
138             }
139             // We *could* return null to silently continue, but this is basically data corruption.
140             if (index < 0 || index >= list.Count)
141             {
142                 // We don't have much extra information to give at this point unfortunately. If this becomes a problem,
143                 // we can pass in the complete path and report that and the file name.
144                 throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range");
145             }
146             return list[index];
147         }
148 
GetNestedDescriptorListForField(int fieldNumber)149         private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
150             fieldNumber switch
151             {
152                 FileDescriptorProto.ServiceFieldNumber => (IReadOnlyList<DescriptorBase>)Services,
153                 FileDescriptorProto.MessageTypeFieldNumber => (IReadOnlyList<DescriptorBase>)MessageTypes,
154                 FileDescriptorProto.EnumTypeFieldNumber => (IReadOnlyList<DescriptorBase>)EnumTypes,
155                 _ => null,
156             };
157 
GetDeclaration(IDescriptor descriptor)158         internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor)
159         {
160             declarations.Value.TryGetValue(descriptor, out DescriptorDeclaration declaration);
161             return declaration;
162         }
163 
164         /// <summary>
165         /// Computes the full name of a descriptor within this file, with an optional parent message.
166         /// </summary>
ComputeFullName(MessageDescriptor parent, string name)167         internal string ComputeFullName(MessageDescriptor parent, string name)
168         {
169             if (parent != null)
170             {
171                 return parent.FullName + "." + name;
172             }
173             if (Package.Length > 0)
174             {
175                 return Package + "." + name;
176             }
177             return name;
178         }
179 
180         /// <summary>
181         /// Extracts public dependencies from direct dependencies. This is a static method despite its
182         /// first parameter, as the value we're in the middle of constructing is only used for exceptions.
183         /// </summary>
184         private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies)
185         {
186             var nameToFileMap = dependencies.ToDictionary(file => file.Name);
187             var publicDependencies = new List<FileDescriptor>();
188             for (int i = 0; i < proto.PublicDependency.Count; i++)
189             {
190                 int index = proto.PublicDependency[i];
191                 if (index < 0 || index >= proto.Dependency.Count)
192                 {
193                     throw new DescriptorValidationException(@this, "Invalid public dependency index.");
194                 }
195                 string name = proto.Dependency[index];
196                 if (!nameToFileMap.TryGetValue(name, out FileDescriptor file))
197                 {
198                     if (!allowUnknownDependencies)
199                     {
200                         throw new DescriptorValidationException(@this, "Invalid public dependency: " + name);
201                     }
202                     // Ignore unknown dependencies.
203                 }
204                 else
205                 {
206                     publicDependencies.Add(file);
207                 }
208             }
209             return new ReadOnlyCollection<FileDescriptor>(publicDependencies);
210         }
211 
212         /// <value>
213         /// The descriptor in its protocol message representation.
214         /// </value>
215         internal FileDescriptorProto Proto { get; }
216 
217         /// <summary>
218         /// Returns a clone of the underlying <see cref="FileDescriptorProto"/> describing this file.
219         /// Note that a copy is taken every time this method is called, so clients using it frequently
220         /// (and not modifying it) may want to cache the returned value.
221         /// </summary>
222         /// <returns>A protobuf representation of this file descriptor.</returns>
ToProto()223         public FileDescriptorProto ToProto() => Proto.Clone();
224 
225         /// <summary>
226         /// The feature set for this file, including inherited features.
227         /// </summary>
228         internal FeatureSetDescriptor Features { get; }
229 
230         /// <summary>
231         /// Returns the edition of the file descriptor.
232         /// </summary>
233         internal Edition Edition => Proto.Syntax switch
234         {
235             "editions" => Proto.Edition,
236             "proto3" => Edition.Proto3,
237             _ => Edition.Proto2
238         };
239 
240         /// <summary>
241         /// The syntax of the file.
242         /// </summary>
243         [Obsolete("Use features instead of proto syntax.")]
244         public Syntax Syntax => Proto.HasEdition ?  Syntax.Editions
245             : Proto.Syntax switch
246             {
247                 "proto3" => Syntax.Proto3,
248                 "proto2" => Syntax.Proto2,
249                 "" => Syntax.Proto2,
250                 _ => throw new InvalidOperationException("No edition or known syntax present")
251             };
252 
253         /// <value>
254         /// The file name.
255         /// </value>
256         public string Name => Proto.Name;
257 
258         /// <summary>
259         /// The package as declared in the .proto file. This may or may not
260         /// be equivalent to the .NET namespace of the generated classes.
261         /// </summary>
262         public string Package => Proto.Package;
263 
264         /// <value>
265         /// Unmodifiable list of top-level message types declared in this file.
266         /// </value>
267         public IList<MessageDescriptor> MessageTypes { get; }
268 
269         /// <value>
270         /// Unmodifiable list of top-level enum types declared in this file.
271         /// </value>
272         public IList<EnumDescriptor> EnumTypes { get; }
273 
274         /// <value>
275         /// Unmodifiable list of top-level services declared in this file.
276         /// </value>
277         public IList<ServiceDescriptor> Services { get; }
278 
279         /// <summary>
280         /// Unmodifiable list of top-level extensions declared in this file.
281         /// Note that some extensions may be incomplete (FieldDescriptor.Extension may be null)
282         /// if this descriptor was generated using a version of protoc that did not fully
283         /// support extensions in C#.
284         /// </summary>
285         public ExtensionCollection Extensions { get; }
286 
287         /// <value>
288         /// Unmodifiable list of this file's dependencies (imports).
289         /// </value>
290         public IList<FileDescriptor> Dependencies { get; }
291 
292         /// <value>
293         /// Unmodifiable list of this file's public dependencies (public imports).
294         /// </value>
295         public IList<FileDescriptor> PublicDependencies { get; }
296 
297         /// <value>
298         /// The original serialized binary form of this descriptor.
299         /// </value>
300         public ByteString SerializedData { get; }
301 
302         /// <value>
303         /// Implementation of IDescriptor.FullName - just returns the same as Name.
304         /// </value>
305         string IDescriptor.FullName => Name;
306 
307         /// <value>
308         /// Implementation of IDescriptor.File - just returns this descriptor.
309         /// </value>
310         FileDescriptor IDescriptor.File => this;
311 
312         /// <value>
313         /// Pool containing symbol descriptors.
314         /// </value>
315         internal DescriptorPool DescriptorPool { get; }
316 
317         /// <summary>
318         /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types.
319         /// </summary>
320         /// <param name="name">The unqualified type name to look for.</param>
321         /// <typeparam name="T">The type of descriptor to look for</typeparam>
322         /// <returns>The type's descriptor, or null if not found.</returns>
323         public T FindTypeByName<T>(string name)
324             where T : class, IDescriptor
325         {
326             // Don't allow looking up nested types.  This will make optimization
327             // easier later.
328             if (name.IndexOf('.') != -1)
329             {
330                 return null;
331             }
332             if (Package.Length > 0)
333             {
334                 name = Package + "." + name;
335             }
336             T result = DescriptorPool.FindSymbol<T>(name);
337             if (result != null && result.File == this)
338             {
339                 return result;
340             }
341             return null;
342         }
343 
344         /// <summary>
345         /// Builds a FileDescriptor from its protocol buffer representation.
346         /// </summary>
347         /// <param name="descriptorData">The original serialized descriptor data.
348         /// We have only limited proto2 support, so serializing FileDescriptorProto
349         /// would not necessarily give us this.</param>
350         /// <param name="proto">The protocol message form of the FileDescriptor.</param>
351         /// <param name="dependencies">FileDescriptors corresponding to all of the
352         /// file's dependencies, in the exact order listed in the .proto file. May be null,
353         /// in which case it is treated as an empty array.</param>
354         /// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param>
355         /// <param name="generatedCodeInfo">Details about generated code, for the purposes of reflection.</param>
356         /// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not
357         /// a valid descriptor. This can occur for a number of reasons, such as a field
358         /// having an undefined type or because two messages were defined with the same name.</exception>
BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)359         private static FileDescriptor BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
360         {
361             // Building descriptors involves two steps: translating and linking.
362             // In the translation step (implemented by FileDescriptor's
363             // constructor), we build an object tree mirroring the
364             // FileDescriptorProto's tree and put all of the descriptors into the
365             // DescriptorPool's lookup tables.  In the linking step, we look up all
366             // type references in the DescriptorPool, so that, for example, a
367             // FieldDescriptor for an embedded message contains a pointer directly
368             // to the Descriptor for that message's type.  We also detect undefined
369             // types in the linking step.
370             if (dependencies == null)
371             {
372                 dependencies = new FileDescriptor[0];
373             }
374 
375             DescriptorPool pool = new DescriptorPool(dependencies);
376             FileDescriptor result = new FileDescriptor(descriptorData, proto, dependencies, pool, allowUnknownDependencies, generatedCodeInfo);
377 
378             // Validate that the dependencies we've been passed (as FileDescriptors) are actually the ones we
379             // need.
380             if (dependencies.Length != proto.Dependency.Count)
381             {
382                 throw new DescriptorValidationException(
383                     result,
384                     "Dependencies passed to FileDescriptor.BuildFrom() don't match " +
385                     "those listed in the FileDescriptorProto.");
386             }
387 
388             result.CrossLink();
389             return result;
390         }
391 
CrossLink()392         private void CrossLink()
393         {
394             foreach (MessageDescriptor message in MessageTypes)
395             {
396                 message.CrossLink();
397             }
398 
399             foreach (ServiceDescriptor service in Services)
400             {
401                 service.CrossLink();
402             }
403 
404             Extensions.CrossLink();
405         }
406 
407         /// <summary>
408         /// Creates a descriptor for generated code.
409         /// </summary>
410         /// <remarks>
411         /// This method is only designed to be used by the results of generating code with protoc,
412         /// which creates the appropriate dependencies etc. It has to be public because the generated
413         /// code is "external", but should not be called directly by end users.
414         /// </remarks>
FromGeneratedCode( byte[] descriptorData, FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedCodeInfo)415         public static FileDescriptor FromGeneratedCode(
416             byte[] descriptorData,
417             FileDescriptor[] dependencies,
418             GeneratedClrTypeInfo generatedCodeInfo)
419         {
420             ExtensionRegistry registry = new ExtensionRegistry();
421             registry.AddRange(GetAllExtensions(dependencies, generatedCodeInfo));
422 
423             FileDescriptorProto proto;
424             try
425             {
426                 proto = FileDescriptorProto.Parser.WithExtensionRegistry(registry).ParseFrom(descriptorData);
427             }
428             catch (InvalidProtocolBufferException e)
429             {
430                 throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
431             }
432 
433             try
434             {
435                 // When building descriptors for generated code, we allow unknown
436                 // dependencies by default.
437                 return BuildFrom(ByteString.CopyFrom(descriptorData), proto, dependencies, true, generatedCodeInfo);
438             }
439             catch (DescriptorValidationException e)
440             {
441                 throw new ArgumentException($"Invalid embedded descriptor for \"{proto.Name}\".", e);
442             }
443         }
444 
GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo)445         private static IEnumerable<Extension> GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo)
446         {
447             return dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo));
448         }
449 
GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)450         private static IEnumerable<Extension> GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)
451         {
452             return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions));
453         }
454 
GetAllDependedExtensions(FileDescriptor descriptor)455         private static IEnumerable<Extension> GetAllDependedExtensions(FileDescriptor descriptor)
456         {
457             return descriptor.Extensions.UnorderedExtensions
458                 .Select(s => s.Extension)
459                 .Where(e => e != null)
460                 .Concat(descriptor.Dependencies.Concat(descriptor.PublicDependencies).SelectMany(GetAllDependedExtensions))
461                 .Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage));
462         }
463 
GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)464         private static IEnumerable<Extension> GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)
465         {
466             return descriptor.Extensions.UnorderedExtensions
467                 .Select(s => s.Extension)
468                 .Where(e => e != null)
469                 .Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage));
470         }
471 
472         /// <summary>
473         /// Converts the given descriptor binary data into FileDescriptor objects.
474         /// Note: reflection using the returned FileDescriptors is not currently supported.
475         /// </summary>
476         /// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any
477         /// dependencies must come before the descriptor which depends on them. (If A depends on B, and B
478         /// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible
479         /// with the order in which protoc provides descriptors to plugins.</param>
480         /// <param name="registry">The extension registry to use when parsing, or null if no extensions are required.</param>
481         /// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns>
BuildFromByteStrings(IEnumerable<ByteString> descriptorData, ExtensionRegistry registry)482         public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData, ExtensionRegistry registry)
483         {
484             ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData));
485 
486             var parser = FileDescriptorProto.Parser.WithExtensionRegistry(registry);
487 
488             // TODO: See if we can build a single DescriptorPool instead of building lots of them.
489             // This will all behave correctly, but it's less efficient than we'd like.
490             var descriptors = new List<FileDescriptor>();
491             var descriptorsByName = new Dictionary<string, FileDescriptor>();
492             foreach (var data in descriptorData)
493             {
494                 var proto = parser.ParseFrom(data);
495                 var dependencies = new List<FileDescriptor>();
496                 foreach (var dependencyName in proto.Dependency)
497                 {
498                     if (!descriptorsByName.TryGetValue(dependencyName, out FileDescriptor dependency))
499                     {
500                         throw new ArgumentException($"Dependency missing: {dependencyName}");
501                     }
502                     dependencies.Add(dependency);
503                 }
504                 var pool = new DescriptorPool(dependencies);
505                 FileDescriptor descriptor = new FileDescriptor(
506                     data, proto, dependencies, pool,
507                     allowUnknownDependencies: false, generatedCodeInfo: null);
508                 descriptor.CrossLink();
509                 descriptors.Add(descriptor);
510                 if (descriptorsByName.ContainsKey(descriptor.Name))
511                 {
512                     throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}");
513                 }
514                 descriptorsByName.Add(descriptor.Name, descriptor);
515             }
516             return new ReadOnlyCollection<FileDescriptor>(descriptors);
517         }
518 
519         /// <summary>
520         /// Converts the given descriptor binary data into FileDescriptor objects.
521         /// Note: reflection using the returned FileDescriptors is not currently supported.
522         /// </summary>
523         /// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any
524         /// dependencies must come before the descriptor which depends on them. (If A depends on B, and B
525         /// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible
526         /// with the order in which protoc provides descriptors to plugins.</param>
527         /// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns>
528         public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData) =>
529             BuildFromByteStrings(descriptorData, null);
530 
531         /// <summary>
532         /// Returns a <see cref="System.String" /> that represents this instance.
533         /// </summary>
534         /// <returns>
535         /// A <see cref="System.String" /> that represents this instance.
536         /// </returns>
ToString()537         public override string ToString()
538         {
539             return $"FileDescriptor for {Name}";
540         }
541 
542         /// <summary>
543         /// Returns the file descriptor for descriptor.proto.
544         /// </summary>
545         /// <remarks>
546         /// This is used for protos which take a direct dependency on <c>descriptor.proto</c>, typically for
547         /// annotations. While <c>descriptor.proto</c> is a proto2 file, it is built into the Google.Protobuf
548         /// runtime for reflection purposes. The messages are internal to the runtime as they would require
549         /// proto2 semantics for full support, but the file descriptor is available via this property. The
550         /// C# codegen in protoc automatically uses this property when it detects a dependency on <c>descriptor.proto</c>.
551         /// </remarks>
552         /// <value>
553         /// The file descriptor for <c>descriptor.proto</c>.
554         /// </value>
555         public static FileDescriptor DescriptorProtoFileDescriptor => DescriptorReflection.Descriptor;
556 
557         /// <summary>
558         /// The (possibly empty) set of custom options for this file.
559         /// </summary>
560         [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
561         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
562 
563         /// <summary>
564         /// The <c>FileOptions</c>, defined in <c>descriptor.proto</c>.
565         /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
566         /// Custom options can be retrieved as extensions of the returned message.
567         /// NOTE: A defensive copy is created each time this property is retrieved.
568         /// </summary>
GetOptions()569         public FileOptions GetOptions()
570         {
571             var clone = Proto.Options?.Clone();
572             if (clone is null)
573             {
574                 return null;
575             }
576             // Clients should be using feature accessor methods, not accessing features on the
577             // options proto.
578             clone.Features = null;
579             return clone;
580         }
581 
582         /// <summary>
583         /// Gets a single value file option for this descriptor
584         /// </summary>
585         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(Extension<FileOptions, T> extension)586         public T GetOption<T>(Extension<FileOptions, T> extension)
587         {
588             var value = Proto.Options.GetExtension(extension);
589             return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
590         }
591 
592         /// <summary>
593         /// Gets a repeated value file option for this descriptor
594         /// </summary>
595         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(RepeatedExtension<FileOptions, T> extension)596         public RepeatedField<T> GetOption<T>(RepeatedExtension<FileOptions, T> extension)
597         {
598             return Proto.Options.GetExtension(extension).Clone();
599         }
600 
601         /// <summary>
602         /// Performs initialization for the given generic type argument.
603         /// </summary>
604         /// <remarks>
605         /// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated)
606         /// to make calls into the reflection machinery of this library to express an intention to use that type
607         /// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers
608         /// attempting to determine which generic type arguments need to be handled will spot the code path and act
609         /// accordingly.
610         /// </remarks>
611         /// <typeparam name="T">The type to force initialization for.</typeparam>
ForceReflectionInitialization()612         public static void ForceReflectionInitialization<T>() => ReflectionUtil.ForceInitialize<T>();
613     }
614 }
615