• 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 System;
11 using System.Collections.Generic;
12 using System.Text;
13 
14 namespace Google.Protobuf.Reflection
15 {
16     /// <summary>
17     /// Contains lookup tables containing all the descriptors defined in a particular file.
18     /// </summary>
19     internal sealed class DescriptorPool
20     {
21         private readonly IDictionary<string, IDescriptor> descriptorsByName =
22             new Dictionary<string, IDescriptor>();
23 
24         private readonly IDictionary<ObjectIntPair<IDescriptor>, FieldDescriptor> fieldsByNumber =
25             new Dictionary<ObjectIntPair<IDescriptor>, FieldDescriptor>();
26 
27         private readonly IDictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor> enumValuesByNumber =
28             new Dictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor>();
29 
30         private readonly IDictionary<EnumValueByNameDescriptorKey, EnumValueDescriptor> enumValuesByName =
31             new Dictionary<EnumValueByNameDescriptorKey, EnumValueDescriptor>();
32 
33         private readonly HashSet<FileDescriptor> dependencies = new HashSet<FileDescriptor>();
34 
DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles)35         internal DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles)
36         {
37             foreach (FileDescriptor dependencyFile in dependencyFiles)
38             {
39                 dependencies.Add(dependencyFile);
40                 ImportPublicDependencies(dependencyFile);
41             }
42 
43             foreach (FileDescriptor dependency in dependencyFiles)
44             {
45                 AddPackage(dependency.Package, dependency);
46             }
47         }
48 
ImportPublicDependencies(FileDescriptor file)49         private void ImportPublicDependencies(FileDescriptor file)
50         {
51             foreach (FileDescriptor dependency in file.PublicDependencies)
52             {
53                 if (dependencies.Add(dependency))
54                 {
55                     ImportPublicDependencies(dependency);
56                 }
57             }
58         }
59 
60         /// <summary>
61         /// Finds a symbol of the given name within the pool.
62         /// </summary>
63         /// <typeparam name="T">The type of symbol to look for</typeparam>
64         /// <param name="fullName">Fully-qualified name to look up</param>
65         /// <returns>The symbol with the given name and type,
66         /// or null if the symbol doesn't exist or has the wrong type</returns>
67         internal T FindSymbol<T>(string fullName) where T : class
68         {
descriptorsByName.TryGetValue(fullName, out IDescriptor result)69             descriptorsByName.TryGetValue(fullName, out IDescriptor result);
70             if (result is T descriptor)
71             {
72                 return descriptor;
73             }
74 
75             // dependencies contains direct dependencies and any *public* dependencies
76             // of those dependencies (transitively)... so we don't need to recurse here.
77             foreach (FileDescriptor dependency in dependencies)
78             {
79                 dependency.DescriptorPool.descriptorsByName.TryGetValue(fullName, out result);
80                 descriptor = result as T;
81                 if (descriptor != null)
82                 {
83                     return descriptor;
84                 }
85             }
86 
87             return null;
88         }
89 
90         /// <summary>
91         /// Adds a package to the symbol tables. If a package by the same name
92         /// already exists, that is fine, but if some other kind of symbol
93         /// exists under the same name, an exception is thrown. If the package
94         /// has multiple components, this also adds the parent package(s).
95         /// </summary>
AddPackage(string fullName, FileDescriptor file)96         internal void AddPackage(string fullName, FileDescriptor file)
97         {
98             int dotpos = fullName.LastIndexOf('.');
99             String name;
100             if (dotpos != -1)
101             {
102                 AddPackage(fullName.Substring(0, dotpos), file);
103                 name = fullName.Substring(dotpos + 1);
104             }
105             else
106             {
107                 name = fullName;
108             }
109 
110             if (descriptorsByName.TryGetValue(fullName, out IDescriptor old))
111             {
112                 if (old is not PackageDescriptor)
113                 {
114                     throw new DescriptorValidationException(file,
115                                                             "\"" + name +
116                                                             "\" is already defined (as something other than a " +
117                                                             "package) in file \"" + old.File.Name + "\".");
118                 }
119             }
120             descriptorsByName[fullName] = new PackageDescriptor(name, fullName, file);
121         }
122 
123         /// <summary>
124         /// Adds a symbol to the symbol table.
125         /// </summary>
126         /// <exception cref="DescriptorValidationException">The symbol already existed
127         /// in the symbol table.</exception>
AddSymbol(IDescriptor descriptor)128         internal void AddSymbol(IDescriptor descriptor)
129         {
130             ValidateSymbolName(descriptor);
131             string fullName = descriptor.FullName;
132 
133             if (descriptorsByName.TryGetValue(fullName, out IDescriptor old))
134             {
135                 throw new DescriptorValidationException(descriptor,
136                     GetDescriptorAlreadyAddedExceptionMessage(descriptor, fullName, old));
137             }
138             descriptorsByName[fullName] = descriptor;
139         }
140 
GetDescriptorAlreadyAddedExceptionMessage(IDescriptor descriptor, string fullName, IDescriptor old)141         private static string GetDescriptorAlreadyAddedExceptionMessage(IDescriptor descriptor, string fullName, IDescriptor old)
142         {
143             int dotPos = fullName.LastIndexOf('.');
144             return descriptor.File != old.File ? $"\"{fullName}\" is already defined in file \"{old.File.Name}\"."
145                 : dotPos == -1 ? $"{fullName} is already defined."
146                 : $"\"{fullName.Substring(dotPos + 1)}\" is already defined in \"{fullName.Substring(0, dotPos)}\".";
147         }
148 
149         /// <summary>
150         /// Verifies that the descriptor's name is valid (i.e. it contains
151         /// only letters, digits and underscores, and does not start with a digit).
152         /// </summary>
153         /// <param name="descriptor"></param>
ValidateSymbolName(IDescriptor descriptor)154         private static void ValidateSymbolName(IDescriptor descriptor)
155         {
156             if (descriptor.Name.Length == 0)
157             {
158                 throw new DescriptorValidationException(descriptor, "Missing name.");
159             }
160 
161             // Symbol name must start with a letter or underscore, and it can contain letters,
162             // numbers and underscores.
163             string name = descriptor.Name;
164             if (!IsAsciiLetter(name[0]) && name[0] != '_')
165             {
166                 ThrowInvalidSymbolNameException(descriptor);
167             }
168             for (int i = 1; i < name.Length; i++)
169             {
170                 if (!IsAsciiLetter(name[i]) && !IsAsciiDigit(name[i]) && name[i] != '_')
171                 {
172                     ThrowInvalidSymbolNameException(descriptor);
173                 }
174             }
175 
176             static bool IsAsciiLetter(char c) => (uint)((c | 0x20) - 'a') <= 'z' - 'a';
177             static bool IsAsciiDigit(char c) => (uint)(c - '0') <= '9' - '0';
178             static void ThrowInvalidSymbolNameException(IDescriptor descriptor) =>
179                 throw new DescriptorValidationException(
180                     descriptor, "\"" + descriptor.Name + "\" is not a valid identifier.");
181         }
182 
183         /// <summary>
184         /// Returns the field with the given number in the given descriptor,
185         /// or null if it can't be found.
186         /// </summary>
FindFieldByNumber(MessageDescriptor messageDescriptor, int number)187         internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number)
188         {
189             fieldsByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(messageDescriptor, number), out FieldDescriptor ret);
190             return ret;
191         }
192 
FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)193         internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)
194         {
195             enumValuesByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(enumDescriptor, number), out EnumValueDescriptor ret);
196             return ret;
197         }
198 
FindEnumValueByName(EnumDescriptor enumDescriptor, string name)199         internal EnumValueDescriptor FindEnumValueByName(EnumDescriptor enumDescriptor, string name)
200         {
201             enumValuesByName.TryGetValue(new EnumValueByNameDescriptorKey(enumDescriptor, name), out EnumValueDescriptor ret);
202             return ret;
203         }
204 
205         /// <summary>
206         /// Adds a field to the fieldsByNumber table.
207         /// </summary>
208         /// <exception cref="DescriptorValidationException">A field with the same
209         /// containing type and number already exists.</exception>
AddFieldByNumber(FieldDescriptor field)210         internal void AddFieldByNumber(FieldDescriptor field)
211         {
212             // for extensions, we use the extended type, otherwise we use the containing type
213             ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(field.Proto.HasExtendee ? field.ExtendeeType : field.ContainingType, field.FieldNumber);
214             if (fieldsByNumber.TryGetValue(key, out FieldDescriptor old))
215             {
216                 throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
217                                                                "has already been used in \"" +
218                                                                field.ContainingType.FullName +
219                                                                "\" by field \"" + old.Name + "\".");
220             }
221             fieldsByNumber[key] = field;
222         }
223 
224         /// <summary>
225         /// Adds an enum value to the enumValuesByNumber and enumValuesByName tables. If an enum value
226         /// with the same type and number already exists, this method does nothing to enumValuesByNumber.
227         /// (This is allowed; the first value defined with the number takes precedence.) If an enum
228         /// value with the same name already exists, this method throws DescriptorValidationException.
229         /// (It is expected that this method is called after AddSymbol, which would already have thrown
230         /// an exception in this failure case.)
231         /// </summary>
AddEnumValue(EnumValueDescriptor enumValue)232         internal void AddEnumValue(EnumValueDescriptor enumValue)
233         {
234             ObjectIntPair<IDescriptor> numberKey = new ObjectIntPair<IDescriptor>(enumValue.EnumDescriptor, enumValue.Number);
235             if (!enumValuesByNumber.ContainsKey(numberKey))
236             {
237                 enumValuesByNumber[numberKey] = enumValue;
238             }
239 
240             EnumValueByNameDescriptorKey nameKey = new EnumValueByNameDescriptorKey(enumValue.EnumDescriptor, enumValue.Name);
241             if (enumValuesByName.TryGetValue(nameKey, out EnumValueDescriptor old))
242             {
243                 throw new DescriptorValidationException(enumValue,
244                 GetDescriptorAlreadyAddedExceptionMessage(enumValue, enumValue.FullName, old));
245             }
246             enumValuesByName[nameKey] = enumValue;
247         }
248 
249         /// <summary>
250         /// Looks up a descriptor by name, relative to some other descriptor.
251         /// The name may be fully-qualified (with a leading '.'), partially-qualified,
252         /// or unqualified. C++-like name lookup semantics are used to search for the
253         /// matching descriptor.
254         /// </summary>
255         /// <remarks>
256         /// This isn't heavily optimized, but it's only used during cross linking anyway.
257         /// If it starts being used more widely, we should look at performance more carefully.
258         /// </remarks>
LookupSymbol(string name, IDescriptor relativeTo)259         internal IDescriptor LookupSymbol(string name, IDescriptor relativeTo)
260         {
261             IDescriptor result;
262             if (name.StartsWith("."))
263             {
264                 // Fully-qualified name.
265                 result = FindSymbol<IDescriptor>(name.Substring(1));
266             }
267             else
268             {
269                 // If "name" is a compound identifier, we want to search for the
270                 // first component of it, then search within it for the rest.
271                 int firstPartLength = name.IndexOf('.');
272                 string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength);
273 
274                 // We will search each parent scope of "relativeTo" looking for the
275                 // symbol.
276                 StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName);
277 
278                 while (true)
279                 {
280                     // Chop off the last component of the scope.
281 
282                     int dotpos = scopeToTry.ToString().LastIndexOf(".");
283                     if (dotpos == -1)
284                     {
285                         result = FindSymbol<IDescriptor>(name);
286                         break;
287                     }
288                     else
289                     {
290                         scopeToTry.Length = dotpos + 1;
291 
292                         // Append firstPart and try to find.
293                         scopeToTry.Append(firstPart);
294                         result = FindSymbol<IDescriptor>(scopeToTry.ToString());
295 
296                         if (result != null)
297                         {
298                             if (firstPartLength != -1)
299                             {
300                                 // We only found the first part of the symbol.  Now look for
301                                 // the whole thing.  If this fails, we *don't* want to keep
302                                 // searching parent scopes.
303                                 scopeToTry.Length = dotpos + 1;
304                                 scopeToTry.Append(name);
305                                 result = FindSymbol<IDescriptor>(scopeToTry.ToString());
306                             }
307                             break;
308                         }
309 
310                         // Not found.  Remove the name so we can try again.
311                         scopeToTry.Length = dotpos;
312                     }
313                 }
314             }
315 
316             if (result == null)
317             {
318                 throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined.");
319             }
320             else
321             {
322                 return result;
323             }
324         }
325 
326         /// <summary>
327         /// Struct used to hold the keys for the enumValuesByName table.
328         /// </summary>
329         private struct EnumValueByNameDescriptorKey : IEquatable<EnumValueByNameDescriptorKey>
330         {
331             private readonly string name;
332             private readonly IDescriptor descriptor;
333 
EnumValueByNameDescriptorKeyGoogle.Protobuf.Reflection.DescriptorPool.EnumValueByNameDescriptorKey334             internal EnumValueByNameDescriptorKey(EnumDescriptor descriptor, string valueName)
335             {
336                 this.descriptor = descriptor;
337                 this.name = valueName;
338             }
339 
340             public bool Equals(EnumValueByNameDescriptorKey other) =>
341                 descriptor == other.descriptor
342                 && name == other.name;
343 
344             public override bool Equals(object obj) =>
345                 obj is EnumValueByNameDescriptorKey pair && Equals(pair);
346 
GetHashCodeGoogle.Protobuf.Reflection.DescriptorPool.EnumValueByNameDescriptorKey347             public override int GetHashCode()
348             {
349                 unchecked
350                 {
351                     var hashCode = descriptor.GetHashCode();
352                     hashCode = (hashCode * 397) ^ (name != null ? name.GetHashCode() : 0);
353                     return hashCode;
354                 }
355             }
356         }
357    }
358 }