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
33using Google.Protobuf.Compatibility;
34using System;
35using System.Reflection;
36
37namespace 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    {
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
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>
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        {
141            Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method);
142            Action<IMessage> CreateActionIMessage(MethodInfo method);
143            Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method);
144            Action<IMessage, object> CreateActionIMessageObject(MethodInfo method);
145            Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method);
146        }
147
148        internal interface IExtensionReflectionHelper
149        {
150            object GetExtension(IMessage message);
151            void SetExtension(IMessage message, object value);
152            bool HasExtension(IMessage message);
153            void ClearExtension(IMessage message);
154        }
155
156        private interface IExtensionSetReflector
157        {
158            Func<IMessage, bool> CreateIsInitializedCaller();
159        }
160
161        private class ReflectionHelper<T1, T2> : IReflectionHelper
162        {
163
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
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
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
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
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
213            public ExtensionReflectionHelper(Extension extension)
214            {
215                this.extension = extension;
216            }
217
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
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
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
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        {
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
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.
361        public static SampleEnum SampleEnumMethod() => SampleEnum.X;
362    }
363}
364