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.Diagnostics.CodeAnalysis; 12 using System.Reflection; 13 using System.Runtime.CompilerServices; 14 using Google.Protobuf.Compatibility; 15 16 namespace Google.Protobuf.Reflection { 17 /// <summary> 18 /// The methods in this class are somewhat evil, and should not be tampered with lightly. 19 /// Basically they allow the creation of relatively weakly typed delegates from MethodInfos 20 /// which are more strongly typed. They do this by creating an appropriate strongly typed 21 /// delegate from the MethodInfo, and then calling that within an anonymous method. 22 /// Mind-bending stuff (at least to your humble narrator) but the resulting delegates are 23 /// very fast compared with calling Invoke later on. 24 /// </summary> 25 internal static class ReflectionUtil { ReflectionUtil()26 static ReflectionUtil() { 27 ForceInitialize<string>(); // Handles all reference types 28 ForceInitialize<int>(); 29 ForceInitialize<long>(); 30 ForceInitialize<uint>(); 31 ForceInitialize<ulong>(); 32 ForceInitialize<float>(); 33 ForceInitialize<double>(); 34 ForceInitialize<bool>(); 35 ForceInitialize < int ? > (); 36 ForceInitialize < long ? > (); 37 ForceInitialize < uint ? > (); 38 ForceInitialize < ulong ? > (); 39 ForceInitialize < float ? > (); 40 ForceInitialize < double ? > (); 41 ForceInitialize < bool ? > (); 42 ForceInitialize<SampleEnum>(); 43 SampleEnumMethod(); 44 } 45 ForceInitialize()46 internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>(); 47 48 /// <summary> 49 /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching. 50 /// </summary> 51 internal static readonly Type[] EmptyTypes = new Type[0]; 52 53 /// <summary> 54 /// Creates a delegate which will cast the argument to the type that declares the method, 55 /// call the method on it, then convert the result to object. 56 /// </summary> 57 /// <param name="method">The method to create a delegate for, which must be declared in an 58 /// IMessage implementation.</param> 59 internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => 60 GetReflectionHelper(method.DeclaringType, method.ReturnType) 61 .CreateFuncIMessageObject(method); 62 63 /// <summary> 64 /// Creates a delegate which will cast the argument to the type that declares the method, 65 /// call the method on it, then convert the result to the specified type. The method is expected 66 /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes 67 /// that means we need some extra work to perform conversions. 68 /// </summary> 69 /// <param name="method">The method to create a delegate for, which must be declared in an 70 /// IMessage implementation.</param> 71 internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => 72 GetReflectionHelper(method.DeclaringType, method.ReturnType) 73 .CreateFuncIMessageInt32(method); 74 75 /// <summary> 76 /// Creates a delegate which will execute the given method after casting the first argument to 77 /// the type that declares the method, and the second argument to the first parameter type of 78 /// the method. 79 /// </summary> 80 /// <param name="method">The method to create a delegate for, which must be declared in an 81 /// IMessage implementation.</param> 82 internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => 83 GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType) 84 .CreateActionIMessageObject(method); 85 86 /// <summary> 87 /// Creates a delegate which will execute the given method after casting the first argument to 88 /// type that declares the method. 89 /// </summary> 90 /// <param name="method">The method to create a delegate for, which must be declared in an 91 /// IMessage implementation.</param> 92 internal static Action<IMessage> CreateActionIMessage(MethodInfo method) => 93 GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); 94 95 internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) => 96 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method); 97 98 [UnconditionalSuppressMessage( 99 "Trimming", "IL2026", 100 Justification = 101 "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] 102 [UnconditionalSuppressMessage( 103 "AotAnalysis", "IL3050:RequiresDynamicCode", 104 Justification = 105 "Type definition is explicitly specified and type argument is always a message type.")] 106 internal static Func<IMessage, bool> CreateIsInitializedCaller([ 107 DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility) 108 ] Type msg) => ((IExtensionSetReflector)Activator 109 .CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))) 110 .CreateIsInitializedCaller(); 111 112 /// <summary> 113 /// Creates a delegate which will execute the given method after casting the first argument to 114 /// the type that declares the method, and the second argument to the first parameter type of 115 /// the method. 116 /// </summary> 117 [UnconditionalSuppressMessage( 118 "Trimming", "IL2026", 119 Justification = 120 "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] 121 [UnconditionalSuppressMessage("AOT", "IL3050", 122 Justification = "Dynamic code won't call Type.MakeGenericType.")] CreateExtensionHelper(Extension extension)123 internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) { 124 #if NET5_0_OR_GREATER 125 if (!RuntimeFeature.IsDynamicCodeSupported) { 126 // Using extensions with reflection is not supported with AOT. 127 // This helper is created when descriptors are populated. Delay throwing error until an app 128 // uses IFieldAccessor with an extension field. 129 return new AotExtensionReflectionHelper(); 130 } 131 #endif 132 133 var t1 = extension.TargetType; 134 var t3 = extension.GetType().GenericTypeArguments[1]; 135 return (IExtensionReflectionHelper)Activator.CreateInstance( 136 typeof(ExtensionReflectionHelper<, >).MakeGenericType(t1, t3), extension); 137 } 138 139 /// <summary> 140 /// Creates a reflection helper for the given type arguments. Currently these are created on 141 /// demand rather than cached; this will be "busy" when initially loading a message's 142 /// descriptor, but after that they can be garbage collected. We could cache them by type if 143 /// that proves to be important, but creating an object is pretty cheap. 144 /// </summary> 145 [UnconditionalSuppressMessage( 146 "Trimming", "IL2026", 147 Justification = 148 "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] 149 [UnconditionalSuppressMessage("AOT", "IL3050", 150 Justification = "Dynamic code won't call Type.MakeGenericType.")] GetReflectionHelper(Type t1, Type t2)151 private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) { 152 #if NET5_0_OR_GREATER 153 if (!RuntimeFeature.IsDynamicCodeSupported) { 154 return new AotReflectionHelper(); 155 } 156 #endif 157 158 return (IReflectionHelper)Activator.CreateInstance( 159 typeof(ReflectionHelper<, >).MakeGenericType(t1, t2)); 160 } 161 162 // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without 163 // statically knowing the types involved. 164 private interface IReflectionHelper { CreateFuncIMessageInt32(MethodInfo method)165 Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method); CreateActionIMessage(MethodInfo method)166 Action<IMessage> CreateActionIMessage(MethodInfo method); CreateFuncIMessageObject(MethodInfo method)167 Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method); CreateActionIMessageObject(MethodInfo method)168 Action<IMessage, object> CreateActionIMessageObject(MethodInfo method); CreateFuncIMessageBool(MethodInfo method)169 Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method); 170 } 171 172 internal interface IExtensionReflectionHelper { GetExtension(IMessage message)173 object GetExtension(IMessage message); SetExtension(IMessage message, object value)174 void SetExtension(IMessage message, object value); HasExtension(IMessage message)175 bool HasExtension(IMessage message); ClearExtension(IMessage message)176 void ClearExtension(IMessage message); 177 } 178 179 private interface IExtensionSetReflector { CreateIsInitializedCaller()180 Func<IMessage, bool> CreateIsInitializedCaller(); 181 } 182 183 private sealed class ReflectionHelper<T1, T2> : IReflectionHelper { CreateFuncIMessageInt32(MethodInfo method)184 public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) { 185 // On pleasant runtimes, we can create a Func<int> from a method returning 186 // an enum based on an int. That's the fast path. 187 if (CanConvertEnumFuncToInt32Func) { 188 var del = (Func<T1, int>)method.CreateDelegate(typeof(Func<T1, int>)); 189 return message => del((T1)message); 190 } else { 191 // On some runtimes (e.g. old Mono) the return type has to be exactly correct, 192 // so we go via boxing. Reflection is already fairly inefficient, and this is 193 // only used for one-of case checking, fortunately. 194 var del = (Func<T1, T2>)method.CreateDelegate(typeof(Func<T1, T2>)); 195 return message => (int)(object)del((T1)message); 196 } 197 } 198 CreateActionIMessage(MethodInfo method)199 public Action<IMessage> CreateActionIMessage(MethodInfo method) { 200 var del = (Action<T1>)method.CreateDelegate(typeof(Action<T1>)); 201 return message => del((T1)message); 202 } 203 CreateFuncIMessageObject(MethodInfo method)204 public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) { 205 var del = (Func<T1, T2>)method.CreateDelegate(typeof(Func<T1, T2>)); 206 return message => del((T1)message); 207 } 208 CreateActionIMessageObject(MethodInfo method)209 public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) { 210 var del = (Action<T1, T2>)method.CreateDelegate(typeof(Action<T1, T2>)); 211 return (message, arg) => del((T1)message, (T2)arg); 212 } 213 CreateFuncIMessageBool(MethodInfo method)214 public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) { 215 var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>)); 216 return message => del((T1)message); 217 } 218 } 219 220 private sealed class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper 221 where T1 : IExtendableMessage<T1> { 222 private readonly Extension extension; 223 ExtensionReflectionHelper(Extension extension)224 public ExtensionReflectionHelper(Extension extension) { 225 this.extension = extension; 226 } 227 GetExtension(IMessage message)228 public object GetExtension(IMessage message) { 229 if (message is not T1 extensionMessage) { 230 throw new InvalidCastException( 231 "Cannot access extension on message that isn't IExtensionMessage"); 232 } 233 234 if (extension is Extension<T1, T3> ext13) { 235 return extensionMessage.GetExtension(ext13); 236 } else if (extension is RepeatedExtension<T1, T3> repeatedExt13) { 237 return extensionMessage.GetOrInitializeExtension(repeatedExt13); 238 } else { 239 throw new InvalidCastException( 240 "The provided extension is not a valid extension identifier type"); 241 } 242 } 243 HasExtension(IMessage message)244 public bool HasExtension(IMessage message) { 245 if (message is not T1 extensionMessage) { 246 throw new InvalidCastException( 247 "Cannot access extension on message that isn't IExtensionMessage"); 248 } 249 250 if (extension is Extension<T1, T3> ext13) { 251 return extensionMessage.HasExtension(ext13); 252 } else if (extension is RepeatedExtension<T1, T3>) { 253 throw new InvalidOperationException( 254 "HasValue is not implemented for repeated extensions"); 255 } else { 256 throw new InvalidCastException( 257 "The provided extension is not a valid extension identifier type"); 258 } 259 } 260 SetExtension(IMessage message, object value)261 public void SetExtension(IMessage message, object value) { 262 if (message is not T1 extensionMessage) { 263 throw new InvalidCastException( 264 "Cannot access extension on message that isn't IExtensionMessage"); 265 } 266 267 if (extension is Extension<T1, T3> ext13) { 268 extensionMessage.SetExtension(ext13, (T3)value); 269 } else if (extension is RepeatedExtension<T1, T3>) { 270 throw new InvalidOperationException( 271 "SetValue is not implemented for repeated extensions"); 272 } else { 273 throw new InvalidCastException( 274 "The provided extension is not a valid extension identifier type"); 275 } 276 } 277 ClearExtension(IMessage message)278 public void ClearExtension(IMessage message) { 279 if (message is not T1 extensionMessage) { 280 throw new InvalidCastException( 281 "Cannot access extension on message that isn't IExtensionMessage"); 282 } 283 284 if (extension is Extension<T1, T3> ext13) { 285 extensionMessage.ClearExtension(ext13); 286 } else if (extension is RepeatedExtension<T1, T3> repeatedExt13) { 287 extensionMessage.GetExtension(repeatedExt13).Clear(); 288 } else { 289 throw new InvalidCastException( 290 "The provided extension is not a valid extension identifier type"); 291 } 292 } 293 } 294 295 #if NET5_0_OR_GREATER 296 /// <summary> 297 /// This helper is compatible with .NET Native AOT. 298 /// MakeGenericType doesn't work when a type argument is a value type in AOT. 299 /// MethodInfo.Invoke is used instead of compiled expressions because it's faster in AOT. 300 /// </summary> 301 private sealed class AotReflectionHelper : IReflectionHelper { 302 private static readonly object[] EmptyObjectArray = new object[0]; 303 public Action<IMessage> CreateActionIMessage(MethodInfo method) => message => 304 method.Invoke(message, EmptyObjectArray); 305 public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => 306 (message, arg) => method.Invoke(message, new object[] { arg }); 307 public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) => message => 308 (bool)method.Invoke(message, EmptyObjectArray); 309 public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => message => 310 (int)method.Invoke(message, EmptyObjectArray); 311 public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => message => 312 method.Invoke(message, EmptyObjectArray); 313 } 314 315 /// <summary> 316 /// Reflection with extensions isn't supported because IExtendableMessage members are used to 317 /// get values. Can't use reflection to invoke those methods because they have a generic 318 /// argument. MakeGenericMethod can't be used because it will break whenever the extension type 319 /// is a value type. This could be made to work if there were non-generic methods available for 320 /// getting and setting extension values. 321 /// </summary> 322 private sealed class AotExtensionReflectionHelper : IExtensionReflectionHelper { 323 private const string Message = "Extensions reflection is not supported with AOT."; 324 public object GetExtension(IMessage message) => throw new NotSupportedException(Message); 325 public bool HasExtension(IMessage message) => throw new NotSupportedException(Message); SetExtension(IMessage message, object value)326 public void SetExtension(IMessage message, 327 object value) => throw new NotSupportedException(Message); 328 public void ClearExtension(IMessage message) => throw new NotSupportedException(Message); 329 } 330 #endif 331 332 private sealed class ExtensionSetReflector<[DynamicallyAccessedMembers( 333 DynamicallyAccessedMemberTypes.PublicProperties | 334 DynamicallyAccessedMemberTypes.NonPublicProperties)] T1> : IExtensionSetReflector 335 where T1 : IExtendableMessage<T1> { CreateIsInitializedCaller()336 public Func<IMessage, bool> CreateIsInitializedCaller() { 337 var prop = typeof(T1).GetTypeInfo().GetDeclaredProperty("_Extensions"); 338 var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetMethod.CreateDelegate( 339 typeof(Func<T1, ExtensionSet<T1>>)); 340 var initializedFunc = (Func<ExtensionSet<T1>, bool>)typeof(ExtensionSet<T1>) 341 .GetTypeInfo() 342 .GetDeclaredMethod("IsInitialized") 343 .CreateDelegate(typeof(Func<ExtensionSet<T1>, bool>)); 344 return (m) => { 345 var set = getFunc((T1)m); 346 return set == null || initializedFunc(set); 347 }; 348 } 349 } 350 351 // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 352 // for details about why we're doing this. 353 354 // Deliberately not inside the generic type. We only want to check this once. 355 private static bool CanConvertEnumFuncToInt32Func { get; } = 356 CheckCanConvertEnumFuncToInt32Func(); 357 CheckCanConvertEnumFuncToInt32Func()358 private static bool CheckCanConvertEnumFuncToInt32Func() { 359 try { 360 // Try to do the conversion using reflection, so we can see whether it's supported. 361 MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod)); 362 // If this passes, we're in a reasonable runtime. 363 method.CreateDelegate(typeof(Func<int>)); 364 return true; 365 } catch (ArgumentException) { 366 return false; 367 } 368 } 369 370 public enum SampleEnum { X } 371 372 // Public to make the reflection simpler. SampleEnumMethod()373 public static SampleEnum SampleEnumMethod() => SampleEnum.X; 374 } 375 } 376