• 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.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