xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
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