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.Reflection; 36 37 namespace Google.Protobuf.Reflection 38 { 39 /// <summary> 40 /// The methods in this class are somewhat evil, and should not be tampered with lightly. 41 /// Basically they allow the creation of relatively weakly typed delegates from MethodInfos 42 /// which are more strongly typed. They do this by creating an appropriate strongly typed 43 /// delegate from the MethodInfo, and then calling that within an anonymous method. 44 /// Mind-bending stuff (at least to your humble narrator) but the resulting delegates are 45 /// very fast compared with calling Invoke later on. 46 /// </summary> 47 internal static class ReflectionUtil 48 { ReflectionUtil()49 static ReflectionUtil() 50 { 51 ForceInitialize<string>(); // Handles all reference types 52 ForceInitialize<int>(); 53 ForceInitialize<long>(); 54 ForceInitialize<uint>(); 55 ForceInitialize<ulong>(); 56 ForceInitialize<float>(); 57 ForceInitialize<double>(); 58 ForceInitialize<bool>(); 59 ForceInitialize<int?>(); 60 ForceInitialize<long?>(); 61 ForceInitialize<uint?>(); 62 ForceInitialize<ulong?>(); 63 ForceInitialize<float?>(); 64 ForceInitialize<double?>(); 65 ForceInitialize<bool?>(); 66 ForceInitialize<SampleEnum>(); 67 SampleEnumMethod(); 68 } 69 ForceInitialize()70 internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>(); 71 72 /// <summary> 73 /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching. 74 /// </summary> 75 internal static readonly Type[] EmptyTypes = new Type[0]; 76 77 /// <summary> 78 /// Creates a delegate which will cast the argument to the type that declares the method, 79 /// call the method on it, then convert the result to object. 80 /// </summary> 81 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 82 /// implementation.</param> 83 internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => 84 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method); 85 86 /// <summary> 87 /// Creates a delegate which will cast the argument to the type that declares the method, 88 /// call the method on it, then convert the result to the specified type. The method is expected 89 /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that 90 /// means we need some extra work to perform conversions. 91 /// </summary> 92 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 93 /// implementation.</param> 94 internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => 95 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method); 96 97 /// <summary> 98 /// Creates a delegate which will execute the given method after casting the first argument to 99 /// the type that declares the method, and the second argument to the first parameter type of the method. 100 /// </summary> 101 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 102 /// implementation.</param> 103 internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => 104 GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method); 105 106 /// <summary> 107 /// Creates a delegate which will execute the given method after casting the first argument to 108 /// type that declares the method. 109 /// </summary> 110 /// <param name="method">The method to create a delegate for, which must be declared in an IMessage 111 /// implementation.</param> 112 internal static Action<IMessage> CreateActionIMessage(MethodInfo method) => 113 GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); 114 115 internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) => 116 GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method); 117 118 /// <summary> 119 /// Creates a delegate which will execute the given method after casting the first argument to 120 /// the type that declares the method, and the second argument to the first parameter type of the method. 121 /// </summary> 122 internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) => 123 (IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1])); 124 125 /// <summary> 126 /// Creates a reflection helper for the given type arguments. Currently these are created on demand 127 /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that 128 /// they can be garbage collected. We could cache them by type if that proves to be important, but creating 129 /// an object is pretty cheap. 130 /// </summary> GetReflectionHelper(Type t1, Type t2)131 private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => 132 (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); 133 134 // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically 135 // knowing the types involved. 136 private interface IReflectionHelper 137 { CreateFuncIMessageInt32(MethodInfo method)138 Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method); CreateActionIMessage(MethodInfo method)139 Action<IMessage> CreateActionIMessage(MethodInfo method); CreateFuncIMessageObject(MethodInfo method)140 Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method); CreateActionIMessageObject(MethodInfo method)141 Action<IMessage, object> CreateActionIMessageObject(MethodInfo method); CreateFuncIMessageBool(MethodInfo method)142 Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method); 143 } 144 145 internal interface IExtensionReflectionHelper 146 { GetExtension(IMessage message)147 object GetExtension(IMessage message); SetExtension(IMessage message, object value)148 void SetExtension(IMessage message, object value); HasExtension(IMessage message)149 bool HasExtension(IMessage message); ClearExtension(IMessage message)150 void ClearExtension(IMessage message); 151 } 152 153 private class ReflectionHelper<T1, T2> : IReflectionHelper 154 { 155 CreateFuncIMessageInt32(MethodInfo method)156 public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) 157 { 158 // On pleasant runtimes, we can create a Func<int> from a method returning 159 // an enum based on an int. That's the fast path. 160 if (CanConvertEnumFuncToInt32Func) 161 { 162 var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>)); 163 return message => del((T1) message); 164 } 165 else 166 { 167 // On some runtimes (e.g. old Mono) the return type has to be exactly correct, 168 // so we go via boxing. Reflection is already fairly inefficient, and this is 169 // only used for one-of case checking, fortunately. 170 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); 171 return message => (int) (object) del((T1) message); 172 } 173 } 174 CreateActionIMessage(MethodInfo method)175 public Action<IMessage> CreateActionIMessage(MethodInfo method) 176 { 177 var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>)); 178 return message => del((T1) message); 179 } 180 CreateFuncIMessageObject(MethodInfo method)181 public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) 182 { 183 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); 184 return message => del((T1) message); 185 } 186 CreateActionIMessageObject(MethodInfo method)187 public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) 188 { 189 var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>)); 190 return (message, arg) => del((T1) message, (T2) arg); 191 } 192 CreateFuncIMessageBool(MethodInfo method)193 public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) 194 { 195 var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>)); 196 return message => del((T1)message); 197 } 198 } 199 200 private class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper 201 where T1 : IExtendableMessage<T1> 202 { 203 private readonly Extension extension; 204 ExtensionReflectionHelper(Extension extension)205 public ExtensionReflectionHelper(Extension extension) 206 { 207 this.extension = extension; 208 } 209 GetExtension(IMessage message)210 public object GetExtension(IMessage message) 211 { 212 if (!(message is T1)) 213 { 214 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 215 } 216 217 T1 extensionMessage = (T1)message; 218 219 if (extension is Extension<T1, T3>) 220 { 221 return extensionMessage.GetExtension(extension as Extension<T1, T3>); 222 } 223 else if (extension is RepeatedExtension<T1, T3>) 224 { 225 return extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>); 226 } 227 else 228 { 229 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 230 } 231 } 232 HasExtension(IMessage message)233 public bool HasExtension(IMessage message) 234 { 235 if (!(message is T1)) 236 { 237 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 238 } 239 240 T1 extensionMessage = (T1)message; 241 242 if (extension is Extension<T1, T3>) 243 { 244 return extensionMessage.HasExtension(extension as Extension<T1, T3>); 245 } 246 else if (extension is RepeatedExtension<T1, T3>) 247 { 248 throw new InvalidOperationException("HasValue is not implemented for repeated extensions"); 249 } 250 else 251 { 252 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 253 } 254 } 255 SetExtension(IMessage message, object value)256 public void SetExtension(IMessage message, object value) 257 { 258 if (!(message is T1)) 259 { 260 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 261 } 262 263 T1 extensionMessage = (T1)message; 264 265 if (extension is Extension<T1, T3>) 266 { 267 extensionMessage.SetExtension(extension as Extension<T1, T3>, (T3)value); 268 } 269 else if (extension is RepeatedExtension<T1, T3>) 270 { 271 throw new InvalidOperationException("SetValue is not implemented for repeated extensions"); 272 } 273 else 274 { 275 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 276 } 277 } 278 ClearExtension(IMessage message)279 public void ClearExtension(IMessage message) 280 { 281 if (!(message is T1)) 282 { 283 throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage"); 284 } 285 286 T1 extensionMessage = (T1)message; 287 288 if (extension is Extension<T1, T3>) 289 { 290 extensionMessage.ClearExtension(extension as Extension<T1, T3>); 291 } 292 else if (extension is RepeatedExtension<T1, T3>) 293 { 294 extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>).Clear(); 295 } 296 else 297 { 298 throw new InvalidCastException("The provided extension is not a valid extension identifier type"); 299 } 300 } 301 } 302 303 // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for 304 // details about why we're doing this. 305 306 // Deliberately not inside the generic type. We only want to check this once. 307 private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func(); 308 CheckCanConvertEnumFuncToInt32Func()309 private static bool CheckCanConvertEnumFuncToInt32Func() 310 { 311 try 312 { 313 // Try to do the conversion using reflection, so we can see whether it's supported. 314 MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod)); 315 // If this passes, we're in a reasonable runtime. 316 method.CreateDelegate(typeof(Func<int>)); 317 return true; 318 } 319 catch (ArgumentException) 320 { 321 return false; 322 } 323 } 324 325 public enum SampleEnum 326 { 327 X 328 } 329 330 // Public to make the reflection simpler. SampleEnumMethod()331 public static SampleEnum SampleEnumMethod() => SampleEnum.X; 332 } 333 } 334