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 }