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