diff options
Diffstat (limited to 'csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs')
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index 216e03cc..6d4520c0 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -34,7 +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 { @@ -55,6 +58,8 @@ namespace Google.Protobuf.Reflection ForceReflectionInitialization<Value.KindOneofCase>(); } + private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations; + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) { SerializedData = descriptorData; @@ -77,6 +82,81 @@ namespace Google.Protobuf.Reflection 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> |