diff options
Diffstat (limited to 'csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs')
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs | 144 |
1 files changed, 132 insertions, 12 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index be94cb10..6d4520c0 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -34,6 +34,10 @@ using Google.Protobuf.WellKnownTypes; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using static Google.Protobuf.Reflection.SourceCodeInfo.Types; namespace Google.Protobuf.Reflection { @@ -54,12 +58,14 @@ namespace Google.Protobuf.Reflection ForceReflectionInitialization<Value.KindOneofCase>(); } - private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) + private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations; + + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) { SerializedData = descriptorData; DescriptorPool = pool; Proto = proto; - Dependencies = new ReadOnlyCollection<FileDescriptor>((FileDescriptor[]) dependencies.Clone()); + Dependencies = new ReadOnlyCollection<FileDescriptor>(dependencies.ToList()); PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies); @@ -67,15 +73,90 @@ namespace Google.Protobuf.Reflection MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType, (message, index) => - new MessageDescriptor(message, this, null, index, generatedCodeInfo.NestedTypes[index])); + new MessageDescriptor(message, this, null, index, generatedCodeInfo?.NestedTypes[index])); EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType, (enumType, index) => - new EnumDescriptor(enumType, this, null, index, generatedCodeInfo.NestedEnums[index])); + new EnumDescriptor(enumType, this, null, index, generatedCodeInfo?.NestedEnums[index])); Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, (service, index) => new ServiceDescriptor(service, this, index)); + + declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication); + } + + private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap() + { + var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>(); + foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>()) + { + var descriptor = FindDescriptorForPath(location.Path); + if (descriptor != null) + { + dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location); + } + } + return dictionary; + + IDescriptor FindDescriptorForPath(IList<int> path) + { + // All complete declarations have an even, non-empty path length + // (There can be an empty path for a descriptor declaration, but that can't have any comments, + // so we currently ignore it.) + if (path.Count == 0 || (path.Count & 1) != 0) + { + return null; + } + IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]); + DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]); + + for (int i = 2; current != null && i < path.Count; i += 2) + { + var list = current.GetNestedDescriptorListForField(path[i]); + current = GetDescriptorFromList(list, path[i + 1]); + } + return current; + } + + DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index) + { + // This is fine: it may be a newer version of protobuf than we understand, with a new descriptor + // field. + if (list == null) + { + return null; + } + // We *could* return null to silently continue, but this is basically data corruption. + if (index < 0 || index >= list.Count) + { + // We don't have much extra information to give at this point unfortunately. If this becomes a problem, + // we can pass in the complete path and report that and the file name. + throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range"); + } + return list[index]; + } + + IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case FileDescriptorProto.ServiceFieldNumber: + return (IReadOnlyList<DescriptorBase>) Services; + case FileDescriptorProto.MessageTypeFieldNumber: + return (IReadOnlyList<DescriptorBase>) MessageTypes; + case FileDescriptorProto.EnumTypeFieldNumber: + return (IReadOnlyList<DescriptorBase>) EnumTypes; + default: + return null; + } + } + } + + internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor) + { + declarations.Value.TryGetValue(descriptor, out var declaration); + return declaration; } /// <summary> @@ -98,13 +179,9 @@ namespace Google.Protobuf.Reflection /// Extracts public dependencies from direct dependencies. This is a static method despite its /// first parameter, as the value we're in the middle of constructing is only used for exceptions. /// </summary> - private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, FileDescriptor[] dependencies, bool allowUnknownDependencies) + private static IList<FileDescriptor> DeterminePublicDependencies(FileDescriptor @this, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, bool allowUnknownDependencies) { - var nameToFileMap = new Dictionary<string, FileDescriptor>(); - foreach (var file in dependencies) - { - nameToFileMap[file.Name] = file; - } + var nameToFileMap = dependencies.ToDictionary(file => file.Name); var publicDependencies = new List<FileDescriptor>(); for (int i = 0; i < proto.PublicDependency.Count; i++) { @@ -114,8 +191,7 @@ namespace Google.Protobuf.Reflection throw new DescriptorValidationException(@this, "Invalid public dependency index."); } string name = proto.Dependency[index]; - FileDescriptor file = nameToFileMap[name]; - if (file == null) + if (!nameToFileMap.TryGetValue(name, out var file)) { if (!allowUnknownDependencies) { @@ -316,6 +392,50 @@ namespace Google.Protobuf.Reflection } /// <summary> + /// Converts the given descriptor binary data into FileDescriptor objects. + /// Note: reflection using the returned FileDescriptors is not currently supported. + /// </summary> + /// <param name="descriptorData">The binary file descriptor proto data. Must not be null, and any + /// dependencies must come before the descriptor which depends on them. (If A depends on B, and B + /// depends on C, then the descriptors must be presented in the order C, B, A.) This is compatible + /// with the order in which protoc provides descriptors to plugins.</param> + /// <returns>The file descriptors corresponding to <paramref name="descriptorData"/>.</returns> + public static IReadOnlyList<FileDescriptor> BuildFromByteStrings(IEnumerable<ByteString> descriptorData) + { + ProtoPreconditions.CheckNotNull(descriptorData, nameof(descriptorData)); + + // TODO: See if we can build a single DescriptorPool instead of building lots of them. + // This will all behave correctly, but it's less efficient than we'd like. + var descriptors = new List<FileDescriptor>(); + var descriptorsByName = new Dictionary<string, FileDescriptor>(); + foreach (var data in descriptorData) + { + var proto = FileDescriptorProto.Parser.ParseFrom(data); + var dependencies = new List<FileDescriptor>(); + foreach (var dependencyName in proto.Dependency) + { + if (!descriptorsByName.TryGetValue(dependencyName, out var dependency)) + { + throw new ArgumentException($"Dependency missing: {dependencyName}"); + } + dependencies.Add(dependency); + } + var pool = new DescriptorPool(dependencies); + FileDescriptor descriptor = new FileDescriptor( + data, proto, dependencies, pool, + allowUnknownDependencies: false, generatedCodeInfo: null); + descriptor.CrossLink(); + descriptors.Add(descriptor); + if (descriptorsByName.ContainsKey(descriptor.Name)) + { + throw new ArgumentException($"Duplicate descriptor name: {descriptor.Name}"); + } + descriptorsByName.Add(descriptor.Name, descriptor); + } + return new ReadOnlyCollection<FileDescriptor>(descriptors); + } + + /// <summary> /// Returns a <see cref="System.String" /> that represents this instance. /// </summary> /// <returns> |