• 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         internal static Func<IMessage, bool> CreateIsInitializedCaller(Type msg) =>
119             ((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller();
120 
121         /// <summary>
122         /// Creates a delegate which will execute the given method after casting the first argument to
123         /// the type that declares the method, and the second argument to the first parameter type of the method.
124         /// </summary>
125         internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) =>
126             (IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1]), extension);
127 
128         /// <summary>
129         /// Creates a reflection helper for the given type arguments. Currently these are created on demand
130         /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that
131         /// they can be garbage collected. We could cache them by type if that proves to be important, but creating
132         /// an object is pretty cheap.
133         /// </summary>
GetReflectionHelper(Type t1, Type t2)134         private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) =>
135             (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2));
136 
137         // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically
138         // knowing the types involved.
139         private interface IReflectionHelper
140         {
CreateFuncIMessageInt32(MethodInfo method)141             Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method);
CreateActionIMessage(MethodInfo method)142             Action<IMessage> CreateActionIMessage(MethodInfo method);
CreateFuncIMessageObject(MethodInfo method)143             Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method);
CreateActionIMessageObject(MethodInfo method)144             Action<IMessage, object> CreateActionIMessageObject(MethodInfo method);
CreateFuncIMessageBool(MethodInfo method)145             Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method);
146         }
147 
148         internal interface IExtensionReflectionHelper
149         {
GetExtension(IMessage message)150             object GetExtension(IMessage message);
SetExtension(IMessage message, object value)151             void SetExtension(IMessage message, object value);
HasExtension(IMessage message)152             bool HasExtension(IMessage message);
ClearExtension(IMessage message)153             void ClearExtension(IMessage message);
154         }
155 
156         private interface IExtensionSetReflector
157         {
CreateIsInitializedCaller()158             Func<IMessage, bool> CreateIsInitializedCaller();
159         }
160 
161         private class ReflectionHelper<T1, T2> : IReflectionHelper
162         {
163 
CreateFuncIMessageInt32(MethodInfo method)164             public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method)
165             {
166                 // On pleasant runtimes, we can create a Func<int> from a method returning
167                 // an enum based on an int. That's the fast path.
168                 if (CanConvertEnumFuncToInt32Func)
169                 {
170                     var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>));
171                     return message => del((T1) message);
172                 }
173                 else
174                 {
175                     // On some runtimes (e.g. old Mono) the return type has to be exactly correct,
176                     // so we go via boxing. Reflection is already fairly inefficient, and this is
177                     // only used for one-of case checking, fortunately.
178                     var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
179                     return message => (int) (object) del((T1) message);
180                 }
181             }
182 
CreateActionIMessage(MethodInfo method)183             public Action<IMessage> CreateActionIMessage(MethodInfo method)
184             {
185                 var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>));
186                 return message => del((T1) message);
187             }
188 
CreateFuncIMessageObject(MethodInfo method)189             public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method)
190             {
191                 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
192                 return message => del((T1) message);
193             }
194 
CreateActionIMessageObject(MethodInfo method)195             public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method)
196             {
197                 var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>));
198                 return (message, arg) => del((T1) message, (T2) arg);
199             }
200 
CreateFuncIMessageBool(MethodInfo method)201             public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method)
202             {
203                 var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>));
204                 return message => del((T1)message);
205             }
206         }
207 
208         private class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper
209             where T1 : IExtendableMessage<T1>
210         {
211             private readonly Extension extension;
212 
ExtensionReflectionHelper(Extension extension)213             public ExtensionReflectionHelper(Extension extension)
214             {
215                 this.extension = extension;
216             }
217 
GetExtension(IMessage message)218             public object GetExtension(IMessage message)
219             {
220                 if (!(message is T1))
221                 {
222                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
223                 }
224 
225                 T1 extensionMessage = (T1)message;
226 
227                 if (extension is Extension<T1, T3>)
228                 {
229                     return extensionMessage.GetExtension(extension as Extension<T1, T3>);
230                 }
231                 else if (extension is RepeatedExtension<T1, T3>)
232                 {
233                     return extensionMessage.GetOrInitializeExtension(extension as RepeatedExtension<T1, T3>);
234                 }
235                 else
236                 {
237                     throw new InvalidCastException("The provided extension is not a valid extension identifier type");
238                 }
239             }
240 
HasExtension(IMessage message)241             public bool HasExtension(IMessage message)
242             {
243                 if (!(message is T1))
244                 {
245                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
246                 }
247 
248                 T1 extensionMessage = (T1)message;
249 
250                 if (extension is Extension<T1, T3>)
251                 {
252                     return extensionMessage.HasExtension(extension as Extension<T1, T3>);
253                 }
254                 else if (extension is RepeatedExtension<T1, T3>)
255                 {
256                     throw new InvalidOperationException("HasValue is not implemented for repeated extensions");
257                 }
258                 else
259                 {
260                     throw new InvalidCastException("The provided extension is not a valid extension identifier type");
261                 }
262             }
263 
SetExtension(IMessage message, object value)264             public void SetExtension(IMessage message, object value)
265             {
266                 if (!(message is T1))
267                 {
268                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
269                 }
270 
271                 T1 extensionMessage = (T1)message;
272 
273                 if (extension is Extension<T1, T3>)
274                 {
275                     extensionMessage.SetExtension(extension as Extension<T1, T3>, (T3)value);
276                 }
277                 else if (extension is RepeatedExtension<T1, T3>)
278                 {
279                     throw new InvalidOperationException("SetValue is not implemented for repeated extensions");
280                 }
281                 else
282                 {
283                     throw new InvalidCastException("The provided extension is not a valid extension identifier type");
284                 }
285             }
286 
ClearExtension(IMessage message)287             public void ClearExtension(IMessage message)
288             {
289                 if (!(message is T1))
290                 {
291                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
292                 }
293 
294                 T1 extensionMessage = (T1)message;
295 
296                 if (extension is Extension<T1, T3>)
297                 {
298                     extensionMessage.ClearExtension(extension as Extension<T1, T3>);
299                 }
300                 else if (extension is RepeatedExtension<T1, T3>)
301                 {
302                     extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>).Clear();
303                 }
304                 else
305                 {
306                     throw new InvalidCastException("The provided extension is not a valid extension identifier type");
307                 }
308             }
309         }
310 
311         private class ExtensionSetReflector<T1> : IExtensionSetReflector where T1 : IExtendableMessage<T1>
312         {
CreateIsInitializedCaller()313             public Func<IMessage, bool> CreateIsInitializedCaller()
314             {
315                 var prop = typeof(T1).GetTypeInfo().GetDeclaredProperty("_Extensions");
316 #if NET35
317                 var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetGetMethod(true).CreateDelegate(typeof(Func<T1, ExtensionSet<T1>>));
318 #else
319                 var getFunc = (Func<T1, ExtensionSet<T1>>)prop.GetMethod.CreateDelegate(typeof(Func<T1, ExtensionSet<T1>>));
320 #endif
321                 var initializedFunc = (Func<ExtensionSet<T1>, bool>)
322                     typeof(ExtensionSet<T1>)
323                         .GetTypeInfo()
324                         .GetDeclaredMethod("IsInitialized")
325                         .CreateDelegate(typeof(Func<ExtensionSet<T1>, bool>));
326                 return (m) => {
327                     var set = getFunc((T1)m);
328                     return set == null || initializedFunc(set);
329                 };
330             }
331         }
332 
333         // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for
334         // details about why we're doing this.
335 
336         // Deliberately not inside the generic type. We only want to check this once.
337         private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func();
338 
CheckCanConvertEnumFuncToInt32Func()339         private static bool CheckCanConvertEnumFuncToInt32Func()
340         {
341             try
342             {
343                 // Try to do the conversion using reflection, so we can see whether it's supported.
344                 MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod));
345                 // If this passes, we're in a reasonable runtime.
346                 method.CreateDelegate(typeof(Func<int>));
347                 return true;
348             }
349             catch (ArgumentException)
350             {
351                 return false;
352             }
353         }
354 
355         public enum SampleEnum
356         {
357             X
358         }
359 
360         // Public to make the reflection simpler.
SampleEnumMethod()361         public static SampleEnum SampleEnumMethod() => SampleEnum.X;
362     }
363 }
364