1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using Google.Protobuf.Collections; 34 using Google.Protobuf.WellKnownTypes; 35 using System; 36 using System.Collections.Generic; 37 using System.Collections.ObjectModel; 38 using System.Diagnostics; 39 using System.Linq; 40 using System.Threading; 41 using static Google.Protobuf.Reflection.SourceCodeInfo.Types; 42 43 namespace Google.Protobuf.Reflection 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 ForceReflectionInitialization<Syntax>(); 56 ForceReflectionInitialization<NullValue>(); 57 ForceReflectionInitialization<Field.Types.Cardinality>(); 58 ForceReflectionInitialization<Field.Types.Kind>(); 59 ForceReflectionInitialization<Value.KindOneofCase>(); 60 } 61 62 private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations; 63 FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)64 private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) 65 { 66 SerializedData = descriptorData; 67 DescriptorPool = pool; 68 Proto = proto; 69 Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList()); 70 71 PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies); 72 73 pool.AddPackage(Package, this); 74 75 MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType, 76 (message, index) => 77 new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index])); 78 79 EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType, 80 (enumType, index) => 81 new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index])); 82 83 Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, 84 (service, index) => 85 new ServiceDescriptor(service, this, index)); 86 87 Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions); 88 89 declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication); 90 } 91 CreateDeclarationMap()92 private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap() 93 { 94 var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>(); 95 foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>()) 96 { 97 var descriptor = FindDescriptorForPath(location.Path); 98 if (descriptor != null) 99 { 100 dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location); 101 } 102 } 103 return dictionary; 104 } 105 FindDescriptorForPath(IList<int> path)106 private IDescriptor FindDescriptorForPath(IList<int> path) 107 { 108 // All complete declarations have an even, non-empty path length 109 // (There can be an empty path for a descriptor declaration, but that can't have any comments, 110 // so we currently ignore it.) 111 if (path.Count == 0 || (path.Count & 1) != 0) 112 { 113 return null; 114 } 115 IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]); 116 DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]); 117 118 for (int i = 2; current != null && i < path.Count; i += 2) 119 { 120 var list = current.GetNestedDescriptorListForField(path[i]); 121 current = GetDescriptorFromList(list, path[i + 1]); 122 } 123 return current; 124 } 125 GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)126 private DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index) 127 { 128 // This is fine: it may be a newer version of protobuf than we understand, with a new descriptor 129 // field. 130 if (list == null) 131 { 132 return null; 133 } 134 // We *could* return null to silently continue, but this is basically data corruption. 135 if (index < 0 || index >= list.Count) 136 { 137 // We don't have much extra information to give at this point unfortunately. If this becomes a problem, 138 // we can pass in the complete path and report that and the file name. 139 throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range"); 140 } 141 return list[index]; 142 } 143 GetNestedDescriptorListForField(int fieldNumber)144 private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) 145 { 146 switch (fieldNumber) 147 { 148 case FileDescriptorProto.ServiceFieldNumber: 149 return (IReadOnlyList<DescriptorBase>) Services; 150 case FileDescriptorProto.MessageTypeFieldNumber: 151 return (IReadOnlyList<DescriptorBase>) MessageTypes; 152 case FileDescriptorProto.EnumTypeFieldNumber: 153 return (IReadOnlyList<DescriptorBase>) EnumTypes; 154 default: 155 return null; 156 } 157 } 158 GetDeclaration(IDescriptor descriptor)159 internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor) 160 { 161 DescriptorDeclaration declaration; 162 declarations.Value.TryGetValue(descriptor, out declaration); 163 return declaration; 164 } 165 166 /// <summary> 167 /// Computes the full name of a descriptor within this file, with an optional parent message. 168 /// </summary> ComputeFullName(MessageDescriptor parent, string name)169 internal string ComputeFullName(MessageDescriptor parent, string name) 170 { 171 if (parent != null) 172 { 173 return parent.FullName + "." + name; 174 } 175 if (Package.Length > 0) 176 { 177 return Package + "." + name; 178 } 179 return name; 180 } 181 182 /// <summary> 183 /// Extracts public dependencies from direct dependencies. This is a static method despite its 184 /// first parameter, as the value we're in the middle of constructing is only used for exceptions. 185 /// </summary> 186 private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies) 187 { 188 var nameToFileMap = dependencies.ToDictionary(file => file.Name); 189 var publicDependencies = new List<FileDescriptor>(); 190 for (int i = 0; i < proto.PublicDependency.Count; i++) 191 { 192 int index = proto.PublicDependency[i]; 193 if (index < 0 || index >= proto.Dependency.Count) 194 { 195 throw new DescriptorValidationException(@this, "Invalid public dependency index."); 196 } 197 string name = proto.Dependency[index]; 198 FileDescriptor file; 199 if (!nameToFileMap.TryGetValue(name, out file)) 200 { 201 if (!allowUnknownDependencies) 202 { 203 throw new DescriptorValidationException(@this, "Invalid public dependency: " + name); 204 } 205 // Ignore unknown dependencies. 206 } 207 else 208 { 209 publicDependencies.Add(file); 210 } 211 } 212 return new ReadOnlyCollection<FileDescriptor>(publicDependencies); 213 } 214 215 /// <value> 216 /// The descriptor in its protocol message representation. 217 /// </value> 218 internal FileDescriptorProto Proto { get; } 219 220 /// <value> 221 /// The file name. 222 /// </value> 223 public string Name => Proto.Name; 224 225 /// <summary> 226 /// The package as declared in the .proto file. This may or may not 227 /// be equivalent to the .NET namespace of the generated classes. 228 /// </summary> 229 public string Package => Proto.Package; 230 231 /// <value> 232 /// Unmodifiable list of top-level message types declared in this file. 233 /// </value> 234 public IList<MessageDescriptor> MessageTypes { get; } 235 236 /// <value> 237 /// Unmodifiable list of top-level enum types declared in this file. 238 /// </value> 239 public IList<EnumDescriptor> EnumTypes { get; } 240 241 /// <value> 242 /// Unmodifiable list of top-level services declared in this file. 243 /// </value> 244 public IList<ServiceDescriptor> Services { get; } 245 246 /// <summary> 247 /// Unmodifiable list of top-level extensions declared in this file. 248 /// </summary> 249 public ExtensionCollection Extensions { get; } 250 251 /// <value> 252 /// Unmodifiable list of this file's dependencies (imports). 253 /// </value> 254 public IList<FileDescriptor> Dependencies { get; } 255 256 /// <value> 257 /// Unmodifiable list of this file's public dependencies (public imports). 258 /// </value> 259 public IList<FileDescriptor> PublicDependencies { get; } 260 261 /// <value> 262 /// The original serialized binary form of this descriptor. 263 /// </value> 264 public ByteString SerializedData { get; } 265 266 /// <value> 267 /// Implementation of IDescriptor.FullName - just returns the same as Name. 268 /// </value> 269 string IDescriptor.FullName => Name; 270 271 /// <value> 272 /// Implementation of IDescriptor.File - just returns this descriptor. 273 /// </value> 274 FileDescriptor IDescriptor.File => this; 275 276 /// <value> 277 /// Pool containing symbol descriptors. 278 /// </value> 279 internal DescriptorPool DescriptorPool { get; } 280 281 /// <summary> 282 /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types. 283 /// </summary> 284 /// <param name="name">The unqualified type name to look for.</param> 285 /// <typeparam name="T">The type of descriptor to look for</typeparam> 286 /// <returns>The type's descriptor, or null if not found.</returns> 287 public T FindTypeByName<T>(String name) 288 where T : class, IDescriptor 289 { 290 // Don't allow looking up nested types. This will make optimization 291 // easier later. 292 if (name.IndexOf('.') != -1) 293 { 294 return null; 295 } 296 if (Package.Length > 0) 297 { 298 name = Package + "." + name; 299 } 300 T result = DescriptorPool.FindSymbol<T>(name); 301 if (result != null && result.File == this) 302 { 303 return result; 304 } 305 return null; 306 } 307 308 /// <summary> 309 /// Builds a FileDescriptor from its protocol buffer representation. 310 /// </summary> 311 /// <param name="descriptorData">The original serialized descriptor data. 312 /// We have only limited proto2 support, so serializing FileDescriptorProto 313 /// would not necessarily give us this.</param> 314 /// <param name="proto">The protocol message form of the FileDescriptor.</param> 315 /// <param name="dependencies">FileDescriptors corresponding to all of the 316 /// file's dependencies, in the exact order listed in the .proto file. May be null, 317 /// in which case it is treated as an empty array.</param> 318 /// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param> 319 /// <param name="generatedCodeInfo">Details about generated code, for the purposes of reflection.</param> 320 /// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not 321 /// a valid descriptor. This can occur for a number of reasons, such as a field 322 /// 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)323 private static FileDescriptor BuildFrom(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) 324 { 325 // Building descriptors involves two steps: translating and linking. 326 // In the translation step (implemented by FileDescriptor's 327 // constructor), we build an object tree mirroring the 328 // FileDescriptorProto's tree and put all of the descriptors into the 329 // DescriptorPool's lookup tables. In the linking step, we look up all 330 // type references in the DescriptorPool, so that, for example, a 331 // FieldDescriptor for an embedded message contains a pointer directly 332 // to the Descriptor for that message's type. We also detect undefined 333 // types in the linking step. 334 if (dependencies == null) 335 { 336 dependencies = new FileDescriptor[0]; 337 } 338 339 DescriptorPool pool = new DescriptorPool(dependencies); 340 FileDescriptor result = new FileDescriptor(descriptorData, proto, dependencies, pool, allowUnknownDependencies, generatedCodeInfo); 341 342 // Validate that the dependencies we've been passed (as FileDescriptors) are actually the ones we 343 // need. 344 if (dependencies.Length != proto.Dependency.Count) 345 { 346 throw new DescriptorValidationException( 347 result, 348 "Dependencies passed to FileDescriptor.BuildFrom() don't match " + 349 "those listed in the FileDescriptorProto."); 350 } 351 352 result.CrossLink(); 353 return result; 354 } 355 CrossLink()356 private void CrossLink() 357 { 358 foreach (MessageDescriptor message in MessageTypes) 359 { 360 message.CrossLink(); 361 } 362 363 foreach (ServiceDescriptor service in Services) 364 { 365 service.CrossLink(); 366 } 367 368 Extensions.CrossLink(); 369 } 370 371 /// <summary> 372 /// Creates a descriptor for generated code. 373 /// </summary> 374 /// <remarks> 375 /// This method is only designed to be used by the results of generating code with protoc, 376 /// which creates the appropriate dependencies etc. It has to be public because the generated 377 /// code is "external", but should not be called directly by end users. 378 /// </remarks> FromGeneratedCode( byte[] descriptorData, FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedCodeInfo)379 public static FileDescriptor FromGeneratedCode( 380 byte[] descriptorData, 381 FileDescriptor[] dependencies, 382 GeneratedClrTypeInfo generatedCodeInfo) 383 { 384 ExtensionRegistry registry = new ExtensionRegistry(); 385 AddAllExtensions(dependencies, generatedCodeInfo, registry); 386 FileDescriptorProto proto; 387 try 388 { 389 proto = FileDescriptorProto.Parser.WithExtensionRegistry(registry).ParseFrom(descriptorData); 390 } 391 catch (InvalidProtocolBufferException e) 392 { 393 throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e); 394 } 395 396 try 397 { 398 // When building descriptors for generated code, we allow unknown 399 // dependencies by default. 400 return BuildFrom(ByteString.CopyFrom(descriptorData), proto, dependencies, true, generatedCodeInfo); 401 } 402 catch (DescriptorValidationException e) 403 { 404 throw new ArgumentException($"Invalid embedded descriptor for \"{proto.Name}\".", e); 405 } 406 } 407 AddAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo, ExtensionRegistry registry)408 private static void AddAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo, ExtensionRegistry registry) 409 { 410 registry.AddRange(dependencies.SelectMany(GetAllDependedExtensions).Concat(GetAllGeneratedExtensions(generatedInfo)).ToArray()); 411 } 412 GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)413 private static IEnumerable<Extension> GetAllGeneratedExtensions(GeneratedClrTypeInfo generated) 414 { 415 return generated.Extensions.Concat(generated.NestedTypes.Where(t => t != null).SelectMany(GetAllGeneratedExtensions)); 416 } 417 GetAllDependedExtensions(FileDescriptor descriptor)418 private static IEnumerable<Extension> GetAllDependedExtensions(FileDescriptor descriptor) 419 { 420 return descriptor.Extensions.UnorderedExtensions 421 .Select(s => s.Extension) 422 .Concat(descriptor.Dependencies.Concat(descriptor.PublicDependencies).SelectMany(GetAllDependedExtensions)) 423 .Concat(descriptor.MessageTypes.SelectMany(GetAllDependedExtensionsFromMessage)); 424 } 425 GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor)426 private static IEnumerable<Extension> GetAllDependedExtensionsFromMessage(MessageDescriptor descriptor) 427 { 428 return descriptor.Extensions.UnorderedExtensions 429 .Select(s => s.Extension) 430 .Concat(descriptor.NestedTypes.SelectMany(GetAllDependedExtensionsFromMessage)); 431 } 432 433 /// <summary> 434 /// Converts the given descriptor binary data into FileDescriptor objects. 435 /// Note: reflection using the returned FileDescriptors is not currently supported. 436 /// </summary> 437 /// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any 438 /// dependencies must come before the descriptor which depends on them. (If A depends on B, and B 439 /// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible 440 /// with the order in which protoc provides descriptors to plugins.</param> 441 /// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns> BuildFromByteStrings(IEnumerable<ByteString> descriptorData)442 public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData) 443 { 444 ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData)); 445 446 // TODO: See if we can build a single DescriptorPool instead of building lots of them. 447 // This will all behave correctly, but it's less efficient than we'd like. 448 var descriptors = new List<FileDescriptor>(); 449 var descriptorsByName = new Dictionary<string, FileDescriptor>(); 450 foreach (var data in descriptorData) 451 { 452 var proto = FileDescriptorProto.Parser.ParseFrom(data); 453 var dependencies = new List<FileDescriptor>(); 454 foreach (var dependencyName in proto.Dependency) 455 { 456 FileDescriptor dependency; 457 if (!descriptorsByName.TryGetValue(dependencyName, out dependency)) 458 { 459 throw new ArgumentException($"Dependency missing: {dependencyName}"); 460 } 461 dependencies.Add(dependency); 462 } 463 var pool = new DescriptorPool(dependencies); 464 FileDescriptor descriptor = new FileDescriptor( 465 data, proto, dependencies, pool, 466 allowUnknownDependencies: false, generatedCodeInfo: null); 467 descriptor.CrossLink(); 468 descriptors.Add(descriptor); 469 if (descriptorsByName.ContainsKey(descriptor.Name)) 470 { 471 throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}"); 472 } 473 descriptorsByName.Add(descriptor.Name, descriptor); 474 } 475 return new ReadOnlyCollection<FileDescriptor>(descriptors); 476 } 477 478 /// <summary> 479 /// Returns a <see cref="System.String" /> that represents this instance. 480 /// </summary> 481 /// <returns> 482 /// A <see cref="System.String" /> that represents this instance. 483 /// </returns> ToString()484 public override string ToString() 485 { 486 return $"FileDescriptor for {Name}"; 487 } 488 489 /// <summary> 490 /// Returns the file descriptor for descriptor.proto. 491 /// </summary> 492 /// <remarks> 493 /// This is used for protos which take a direct dependency on <c>descriptor.proto</c>, typically for 494 /// annotations. While <c>descriptor.proto</c> is a proto2 file, it is built into the Google.Protobuf 495 /// runtime for reflection purposes. The messages are internal to the runtime as they would require 496 /// proto2 semantics for full support, but the file descriptor is available via this property. The 497 /// C# codegen in protoc automatically uses this property when it detects a dependency on <c>descriptor.proto</c>. 498 /// </remarks> 499 /// <value> 500 /// The file descriptor for <c>descriptor.proto</c>. 501 /// </value> 502 public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } } 503 504 /// <summary> 505 /// The (possibly empty) set of custom options for this file. 506 /// </summary> 507 //[Obsolete("CustomOptions are obsolete. Use GetOption")] 508 public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber); 509 510 /* // uncomment this in the full proto2 support PR 511 /// <summary> 512 /// Gets a single value enum option for this descriptor 513 /// </summary> 514 public T GetOption<T>(Extension<FileOptions, T> extension) 515 { 516 var value = Proto.Options.GetExtension(extension); 517 return value is IDeepCloneable<T> clonable ? clonable.Clone() : value; 518 } 519 520 /// <summary> 521 /// Gets a repeated value enum option for this descriptor 522 /// </summary> 523 public RepeatedField<T> GetOption<T>(RepeatedExtension<FileOptions, T> extension) 524 { 525 return Proto.Options.GetExtension(extension).Clone(); 526 } 527 */ 528 529 /// <summary> 530 /// Performs initialization for the given generic type argument. 531 /// </summary> 532 /// <remarks> 533 /// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated) 534 /// to make calls into the reflection machinery of this library to express an intention to use that type 535 /// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers 536 /// attempting to determine which generic type arguments need to be handled will spot the code path and act 537 /// accordingly. 538 /// </remarks> 539 /// <typeparam name="T">The type to force initialization for.</typeparam> ForceReflectionInitialization()540 public static void ForceReflectionInitialization<T>() => ReflectionUtil.ForceInitialize<T>(); 541 } 542 } 543