aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/Google.Protobuf/Reflection
diff options
context:
space:
mode:
Diffstat (limited to 'csharp/src/Google.Protobuf/Reflection')
-rw-r--r--csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs45
-rw-r--r--csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs112
-rw-r--r--csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs11
-rw-r--r--csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs80
-rw-r--r--csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs15
-rw-r--r--csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs12
6 files changed, 256 insertions, 19 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
index 194041a8..bfbebf17 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
@@ -30,6 +30,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
+using System.Collections.Generic;
+
namespace Google.Protobuf.Reflection
{
/// <summary>
@@ -37,15 +39,11 @@ namespace Google.Protobuf.Reflection
/// </summary>
public abstract class DescriptorBase : IDescriptor
{
- private readonly FileDescriptor file;
- private readonly string fullName;
- private readonly int index;
-
internal DescriptorBase(FileDescriptor file, string fullName, int index)
{
- this.file = file;
- this.fullName = fullName;
- this.index = index;
+ File = file;
+ FullName = fullName;
+ Index = index;
}
/// <value>
@@ -56,10 +54,7 @@ namespace Google.Protobuf.Reflection
/// this descriptor's type. (There can be duplicate values for different
/// types, e.g. one enum type with index 0 and one message type with index 0.)
/// </remarks>
- public int Index
- {
- get { return index; }
- }
+ public int Index { get; }
/// <summary>
/// Returns the name of the entity (field, message etc) being described.
@@ -69,17 +64,29 @@ namespace Google.Protobuf.Reflection
/// <summary>
/// The fully qualified name of the descriptor's target.
/// </summary>
- public string FullName
- {
- get { return fullName; }
- }
+ public string FullName { get; }
/// <value>
/// The file this descriptor was declared in.
/// </value>
- public FileDescriptor File
- {
- get { return file; }
- }
+ public FileDescriptor File { get; }
+
+ /// <summary>
+ /// The declaration information about the descriptor, or null if no declaration information
+ /// is available for this descriptor.
+ /// </summary>
+ /// <remarks>
+ /// This information is typically only available for dynamically loaded descriptors,
+ /// for example within a protoc plugin where the full descriptors, including source info,
+ /// are passed to the code by protoc.
+ /// </remarks>
+ public DescriptorDeclaration Declaration => File.GetDeclaration(this);
+
+ /// <summary>
+ /// Retrieves the list of nested descriptors corresponding to the given field number, if any.
+ /// If the field is unknown or not a nested descriptor list, return null to terminate the search.
+ /// The default implementation returns null.
+ /// </summary>
+ internal virtual IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) => null;
}
} \ No newline at end of file
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
new file mode 100644
index 00000000..b22048f0
--- /dev/null
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
@@ -0,0 +1,112 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2018 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
+
+namespace Google.Protobuf.Reflection
+{
+ /// <summary>
+ /// Provides additional information about the declaration of a descriptor,
+ /// such as source location and comments.
+ /// </summary>
+ public sealed class DescriptorDeclaration
+ {
+ /// <summary>
+ /// The descriptor this declaration relates to.
+ /// </summary>
+ public IDescriptor Descriptor { get; }
+
+ /// <summary>
+ /// The start line of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int StartLine { get; }
+ /// <summary>
+ /// The start column of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int StartColumn { get; }
+
+ /// <summary>
+ /// // The end line of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int EndLine { get; }
+ /// <summary>
+ /// The end column of the declaration within the source file. This value is 1-based, and
+ /// exclusive. (The final character of the declaration is on the column before this value.)
+ /// </summary>
+ public int EndColumn { get; }
+
+ /// <summary>
+ /// Comments appearing before the declaration. Never null, but may be empty. Multi-line comments
+ /// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
+ /// are removed from each line.
+ /// </summary>
+ public string LeadingComments { get; }
+
+ /// <summary>
+ /// Comments appearing after the declaration. Never null, but may be empty. Multi-line comments
+ /// are represented as a newline-separated string. Leading whitespace and the comment marker ("//")
+ /// are removed from each line.
+ /// </summary>
+ public string TrailingComments { get; }
+
+ /// <summary>
+ /// Comments appearing before the declaration, but separated from it by blank
+ /// lines. Each string represents a newline-separated paragraph of comments.
+ /// Leading whitespace and the comment marker ("//") are removed from each line.
+ /// The list is never null, but may be empty. Likewise each element is never null, but may be empty.
+ /// </summary>
+ public IReadOnlyList<string> LeadingDetachedComments { get; }
+
+ private DescriptorDeclaration(IDescriptor descriptor, Location location)
+ {
+ // TODO: Validation
+ Descriptor = descriptor;
+ bool hasEndLine = location.Span.Count == 4;
+ // Lines and columns are 0-based in the proto.
+ StartLine = location.Span[0] + 1;
+ StartColumn = location.Span[1] + 1;
+ EndLine = hasEndLine ? location.Span[2] + 1 : StartLine;
+ EndColumn = location.Span[hasEndLine ? 3 : 2] + 1;
+ LeadingComments = location.LeadingComments;
+ TrailingComments = location.TrailingComments;
+ LeadingDetachedComments = new ReadOnlyCollection<string>(location.LeadingDetachedComments.ToList());
+ }
+
+ internal static DescriptorDeclaration FromProto(IDescriptor descriptor, Location location) =>
+ new DescriptorDeclaration(descriptor, location);
+ }
+}
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
index 89c73a61..4c5457a7 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
@@ -72,6 +72,17 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name { get { return proto.Name; } }
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case EnumDescriptorProto.ValueFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) Values;
+ default:
+ return null;
+ }
+ }
+
/// <summary>
/// The CLR type for this enum. For generated code, this will be a CLR enum type.
/// </summary>
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>
diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
index dbb6768b..03fd63ce 100644
--- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
@@ -115,6 +115,21 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name => Proto.Name;
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case DescriptorProto.FieldFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
+ case DescriptorProto.NestedTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) NestedTypes;
+ case DescriptorProto.EnumTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) EnumTypes;
+ default:
+ return null;
+ }
+ }
+
internal DescriptorProto Proto { get; }
/// <summary>
diff --git a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
index fe5c072c..da570551 100644
--- a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
@@ -32,6 +32,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
namespace Google.Protobuf.Reflection
{
@@ -58,6 +59,17 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name { get { return proto.Name; } }
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case ServiceDescriptorProto.MethodFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) methods;
+ default:
+ return null;
+ }
+ }
+
internal ServiceDescriptorProto Proto { get { return proto; } }
/// <value>