1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using Google.Protobuf.Compatibility; 34 using System; 35 using System.Diagnostics.CodeAnalysis; 36 using System.Reflection; 37 38 namespace Google.Protobuf.Reflection 39 { 40 /// <summary> 41 /// The methods in this class are somewhat evil, and should not be tampered with lightly. 42 /// Basically they allow the creation of relatively weakly typed delegates from MethodInfos 43 /// which are more strongly typed. They do this by creating an appropriate strongly typed 44 /// delegate from the MethodInfo, and then calling that within an anonymous method. 45 /// Mind-bending stuff (at least to your humble narrator) but the resulting delegates are 46 /// very fast compared with calling Invoke later on. 47 /// </summary> 48 internal static class ReflectionUtil 49 { ReflectionUtil()50 static ReflectionUtil() 51 { 52 ForceInitialize<string>(); // Handles all reference types 53 ForceInitialize<int>(); 54 ForceInitialize<long>(); 55 ForceInitialize<uint>(); 56 ForceInitialize<ulong>(); 57 ForceInitialize<float>(); 58 ForceInitialize<double>(); 59 ForceInitialize<bool>(); 60 ForceInitialize<int?>(); 61 ForceInitialize<long?>(); 62 ForceInitialize<uint?>(); 63 ForceInitialize<ulong?>(); 64 ForceInitialize<float?>(); 65 ForceInitialize<double?>(); 66 ForceInitialize<bool?>(); 67 ForceInitialize<SampleEnum>(); 68 SampleEnumMethod(); 69 } 70 ForceInitialize()71 internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>(); 72 73 /// <summary> 74 /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching. 75 /// </summary> 76 internal static readonly Type[] EmptyTypes = new Type[0]; 77 78 /// <summary> 79 /// Creates a delegate which will cast the argument to the type that declares the method, 80 /// call the method on it, then convert the result to object. 81 /// </summary> 82 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 83 /// implementation.</param> 84 internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => 85 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method); 86 87 /// <summary> 88 /// Creates a delegate which will cast the argument to the type that declares the method, 89 /// call the method on it, then convert the result to the specified type. The method is expected 90 /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that 91 /// means we need some extra work to perform conversions. 92 /// </summary> 93 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 94 /// implementation.</param> 95 internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => 96 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method); 97 98 /// <summary> 99 /// Creates a delegate which will execute the given method after casting the first argument to 100 /// the type that declares the method, and the second argument to the first parameter type of the method. 101 /// </summary> 102 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 103 /// implementation.</param> 104 internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => 105 GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method); 106 107 /// <summary> 108 /// Creates a delegate which will execute the given method after casting the first argument to 109 /// type that declares the method. 110 /// </summary> 111 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 112 /// implementation.</param> 113 internal static Action<IMessage> CreateActionIMessage(MethodInfo method) => 114 GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); 115 116 internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) => 117 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method); 118 119 [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] 120 internal static Func<IMessage, bool> CreateIsInitializedCaller([DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]Type msg) => 121 ((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller(); 122 123 /// <summary> 124 /// Creates a delegate which will execute the given method after casting the first argument to 125 /// the type that declares the method, and the second argument to the first parameter type of the method. 126 /// </summary> 127 [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] 128 internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) => 129 (IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1]), extension); 130 131 /// <summary> 132 /// Creates a reflection helper for the given type arguments. Currently these are created on demand 133 /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that 134 /// they can be garbage collected. We could cache them by type if that proves to be important, but creating 135 /// an object is pretty cheap. 136 /// </summary> 137 [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] GetReflectionHelper(Type t1, Type t2)138 private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => 139 (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); 140 141 // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically 142 // knowing the types involved. 143 private interface IReflectionHelper 144 { CreateFuncIMessageInt32(MethodInfo method)145 Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method); CreateActionIMessage(MethodInfo method)146 Action<IMessage> CreateActionIMessage(MethodInfo method); CreateFuncIMessageObject(MethodInfo method)147 Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method); CreateActionIMessageObject(MethodInfo method)148 Action<IMessage, object> CreateActionIMessageObject(MethodInfo method); CreateFuncIMessageBool(MethodInfo method)149 Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method); 150 } 151 152 internal interface IExtensionReflectionHelper 153 { GetExtension(IMessage message)154 object GetExtension(IMessage message); SetExtension(IMessage message, object value)155 void SetExtension(IMessage message, object value); HasExtension(IMessage message)156 bool HasExtension(IMessage message); ClearExtension(IMessage message)157 void ClearExtension(IMessage message); 158 } 159 160 private interface IExtensionSetReflector 161 { CreateIsInitializedCaller()162 Func<IMessage, bool> CreateIsInitializedCaller(); 163 } 164 165 private class ReflectionHelper<T1, T2> : IReflectionHelper 166 { 167 CreateFuncIMessageInt32(MethodInfo method)168 public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) 169 { 170 // On pleasant runtimes, we can create a Func<int> from a method returning 171 // an enum based on an int. That's the fast path. 172 if (CanConvertEnumFuncToInt32Func) 173 { 174 var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>)); 175 return message => del((T1) message); 176 } 177 else 178 { 179 // On some runtimes (e.g. old Mono) the return type has to be exactly correct, 180 // so we go via boxing. Reflection is already fairly inefficient, and this is 181 // only used for one-of case checking, fortunately. 182 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); 183 return message => (int) (object) del((T1) message); 184 } 185 } 186 CreateActionIMessage(MethodInfo method)187 public Action<IMessage> CreateActionIMessage(MethodInfo method) 188 { 189 var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>)); 190 return message => del((T1) message); 191 } 192 CreateFuncIMessageObject(MethodInfo method)193 public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) 194 { 195 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); 196 return message => del((T1) message); 197 } 198 CreateActionIMessageObject(MethodInfo method)199 public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) 200 { 201 var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>)); 202 return (message, arg) => del((T1) message, (T2) arg); 203 } 204 CreateFuncIMessageBool(MethodInfo method)205 public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) 206 { 207 var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>)); 208 return message => del((T1)message); 209 } 210 } 211 212 private class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper 213 where T1 : IExtendableMessage<T1> 214 { 215 private readonly Extension extension; 216 ExtensionReflectionHelper(Extension extension)217 public ExtensionReflectionHelper(Extension extension) 218 { 219 this.extension = extension; 220 } 221 GetExtension(IMessage message)222 public object GetExtension(IMessage message) 223 { 224 if (!(message is T1)) 225 { 226 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 227 } 228 229 T1 extensionMessage = (T1)message; 230 231 if (extension is Extension<T1, T3>) 232 { 233 return extensionMessage.GetExtension(extension as Extension<T1, T3>); 234 } 235 else if (extension is RepeatedExtension<T1, T3>) 236 { 237 return extensionMessage.GetOrInitializeExtension(extension as RepeatedExtension<T1, T3>); 238 } 239 else 240 { 241 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 242 } 243 } 244 HasExtension(IMessage message)245 public bool HasExtension(IMessage message) 246 { 247 if (!(message is T1)) 248 { 249 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 250 } 251 252 T1 extensionMessage = (T1)message; 253 254 if (extension is Extension<T1, T3>) 255 { 256 return extensionMessage.HasExtension(extension as Extension<T1, T3>); 257 } 258 else if (extension is RepeatedExtension<T1, T3>) 259 { 260 throw new InvalidOperationException("HasValue is not implemented for repeated extensions"); 261 } 262 else 263 { 264 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 265 } 266 } 267 SetExtension(IMessage message, object value)268 public void SetExtension(IMessage message, object value) 269 { 270 if (!(message is T1)) 271 { 272 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 273 } 274 275 T1 extensionMessage = (T1)message; 276 277 if (extension is Extension<T1, T3>) 278 { 279 extensionMessage.SetExtension(extension as Extension<T1, T3>, (T3)value); 280 } 281 else if (extension is RepeatedExtension<T1, T3>) 282 { 283 throw new InvalidOperationException("SetValue is not implemented for repeated extensions"); 284 } 285 else 286 { 287 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 288 } 289 } 290 ClearExtension(IMessage message)291 public void ClearExtension(IMessage message) 292 { 293 if (!(message is T1)) 294 { 295 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 296 } 297 298 T1 extensionMessage = (T1)message; 299 300 if (extension is Extension<T1, T3>) 301 { 302 extensionMessage.ClearExtension(extension as Extension<T1, T3>); 303 } 304 else if (extension is RepeatedExtension<T1, T3>) 305 { 306 extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>).Clear(); 307 } 308 else 309 { 310 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 311 } 312 } 313 } 314 315 private class ExtensionSetReflector< 316 [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] 317 T1> : IExtensionSetReflector where T1 : IExtendableMessage<T1> 318 { CreateIsInitializedCaller()319 public Func<IMessage, bool> CreateIsInitializedCaller() 320 { 321 var prop = typeof(T1).GetTypeInfo().GetDeclaredProperty("_Extensions"); 322 var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetMethod.CreateDelegate(typeof(Func<T1, ExtensionSet<T1>>)); 323 var initializedFunc = (Func<ExtensionSet<T1>, bool>) 324 typeof(ExtensionSet<T1>) 325 .GetTypeInfo() 326 .GetDeclaredMethod("IsInitialized") 327 .CreateDelegate(typeof(Func<ExtensionSet<T1>, bool>)); 328 return (m) => { 329 var set = getFunc((T1)m); 330 return set == null || initializedFunc(set); 331 }; 332 } 333 } 334 335 // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for 336 // details about why we're doing this. 337 338 // Deliberately not inside the generic type. We only want to check this once. 339 private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func(); 340 CheckCanConvertEnumFuncToInt32Func()341 private static bool CheckCanConvertEnumFuncToInt32Func() 342 { 343 try 344 { 345 // Try to do the conversion using reflection, so we can see whether it's supported. 346 MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod)); 347 // If this passes, we're in a reasonable runtime. 348 method.CreateDelegate(typeof(Func<int>)); 349 return true; 350 } 351 catch (ArgumentException) 352 { 353 return false; 354 } 355 } 356 357 public enum SampleEnum 358 { 359 X 360 } 361 362 // Public to make the reflection simpler. SampleEnumMethod()363 public static SampleEnum SampleEnumMethod() => SampleEnum.X; 364 } 365 } 366