Merge pull request #3386 from alexanderkyte/nunit_lite_return_status
[mono.git] / mcs / class / referencesource / System.Web / Util / FastPropertyAccessor.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="FastPropertyAccessor.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 using System;
8 using System.Reflection;
9 using System.Reflection.Emit;
10 using System.Threading;
11 using System.Collections;
12 using System.Security;
13 using System.Security.Permissions;
14
15 namespace System.Web.Util {
16     /*
17      * Property Accessor Generator class
18      *
19      * The purpose of this class is to generate some IL code on the fly that can efficiently
20      * access properties (and fields) of objects.  This is an alternative to using
21      * very slow reflection.
22      */
23
24     internal class FastPropertyAccessor {
25
26         private static object s_lockObject = new object();
27         private static FastPropertyAccessor s_accessorGenerator;
28         private static Hashtable s_accessorCache;
29         private static MethodInfo _getPropertyMethod;
30         private static MethodInfo _setPropertyMethod;
31         private static Type[] _getPropertyParameterList = new Type[] { typeof(object) };
32         private static Type[] _setPropertyParameterList = new Type[] { typeof(object), typeof(object) };
33         private static Type[] _interfacesToImplement;
34
35         private static int _uniqueId;   // Used to generate unique type ID's.
36
37         // Property getter/setter must be public for codegen to access it.
38         // Static properties are ignored, since this class only works on instances of objects.
39         // Need to use DeclaredOnly to avoid AmbiguousMatchException if a property with
40         // a different return type is hidden.
41         private const BindingFlags _declaredFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
42
43         private ModuleBuilder _dynamicModule = null;
44
45         static FastPropertyAccessor() {
46
47             // Get the SetProperty method, and make sure it has
48             // the correct signature.
49
50             _getPropertyMethod = typeof(IWebPropertyAccessor).GetMethod("GetProperty");
51             _setPropertyMethod = typeof(IWebPropertyAccessor).GetMethod("SetProperty");
52
53             // This will be needed later, when building the dynamic class.
54             _interfacesToImplement = new Type[1];
55             _interfacesToImplement[0] = typeof(IWebPropertyAccessor);
56         }
57
58         private static String GetUniqueCompilationName() {
59             return Guid.NewGuid().ToString().Replace('-', '_');
60         }
61
62         Type GetPropertyAccessorTypeWithAssert(Type type, string propertyName,
63             PropertyInfo propInfo, FieldInfo fieldInfo) {
64             // Create the dynamic assembly if needed.
65             Type accessorType;
66
67             MethodInfo getterMethodInfo = null;
68             MethodInfo setterMethodInfo = null;
69             Type propertyType;
70
71             if (propInfo != null) {
72                 // It'a a property
73                 getterMethodInfo = propInfo.GetGetMethod();
74                 setterMethodInfo = propInfo.GetSetMethod();
75
76                 propertyType = propInfo.PropertyType;
77             }
78             else {
79                 // If not, it must be a field
80                 propertyType = fieldInfo.FieldType;
81             }
82
83             if (_dynamicModule == null) {
84                 lock (this) {
85                     if (_dynamicModule == null) {
86
87                         // Use a unique name for each assembly.
88                         String name = GetUniqueCompilationName();
89
90                         AssemblyName assemblyName = new AssemblyName();
91                         assemblyName.Name = "A_" + name;
92
93                         // Create a new assembly.
94                         AssemblyBuilder newAssembly =
95                            Thread.GetDomain().DefineDynamicAssembly(assemblyName,
96                                                                     AssemblyBuilderAccess.Run,
97                                                                     null, //directory to persist assembly
98                                                                     true, //isSynchronized
99                                                                     null  //assembly attributes
100                                                                     );
101
102                         // Create a single module in the assembly.
103                         _dynamicModule = newAssembly.DefineDynamicModule("M_" + name);
104                     }
105                 }
106             }
107
108             // Give the factory a unique name.
109
110             String typeName = System.Web.UI.Util.MakeValidTypeNameFromString(type.Name) +
111                 "_" + propertyName + "_" + (_uniqueId++);
112
113             TypeBuilder accessorTypeBuilder = _dynamicModule.DefineType("T_" + typeName,
114                                                                        TypeAttributes.Public,
115                                                                        typeof(object),
116                                                                        _interfacesToImplement);
117
118             //
119             // Define the GetProperty method. It must be virtual to be an interface implementation.
120             //
121
122             MethodBuilder method = accessorTypeBuilder.DefineMethod("GetProperty",
123                                                                    MethodAttributes.Public |
124                                                                         MethodAttributes.Virtual,
125                                                                    typeof(Object),
126                                                                    _getPropertyParameterList);
127
128             // Generate IL. The generated IL corresponds to:
129             //  "return ((TargetType) target).Blah;"
130
131             ILGenerator il = method.GetILGenerator();
132             if (getterMethodInfo != null) {
133                 il.Emit(OpCodes.Ldarg_1);
134                 il.Emit(OpCodes.Castclass, type);
135
136                 // Generate the getter call based on whether it's a Property or Field
137                 if (propInfo != null)
138                     il.EmitCall(OpCodes.Callvirt, getterMethodInfo, null);
139                 else
140                     il.Emit(OpCodes.Ldfld, fieldInfo);
141
142                 il.Emit(OpCodes.Box, propertyType);
143                 il.Emit(OpCodes.Ret);
144
145                 // Specify that this method implements GetProperty from the inherited interface.
146                 accessorTypeBuilder.DefineMethodOverride(method, _getPropertyMethod);
147             }
148             else {
149                 // Generate IL. The generated IL corresponds to "throw new InvalidOperationException"
150                 ConstructorInfo cons = typeof(InvalidOperationException).GetConstructor(Type.EmptyTypes);
151                 il.Emit(OpCodes.Newobj, cons);
152                 il.Emit(OpCodes.Throw);
153             }
154
155
156             //
157             // Define the SetProperty method. It must be virtual to be an interface implementation.
158             //
159
160             method = accessorTypeBuilder.DefineMethod("SetProperty",
161                                                                    MethodAttributes.Public |
162                                                                         MethodAttributes.Virtual,
163                                                                    null,
164                                                                    _setPropertyParameterList);
165
166             il = method.GetILGenerator();
167
168             // Don't generate any code in the setter if it's a readonly property.
169             // We still need to have an implementation of SetProperty, but it does nothing.
170             if (fieldInfo != null || setterMethodInfo != null) {
171
172                 // Generate IL. The generated IL corresponds to:
173                 //  "((TargetType) target).Blah = (PropType) val;"
174
175                 il.Emit(OpCodes.Ldarg_1);
176                 il.Emit(OpCodes.Castclass, type);
177                 il.Emit(OpCodes.Ldarg_2);
178
179                 if (propertyType.IsPrimitive) {
180                     // Primitive type: deal with boxing
181                     il.Emit(OpCodes.Unbox, propertyType);
182
183                     // Emit the proper instruction for the type
184                     if (propertyType == typeof(sbyte)) {
185                         il.Emit(OpCodes.Ldind_I1);
186                     }
187                     else if (propertyType == typeof(byte)) {
188                         il.Emit(OpCodes.Ldind_U1);
189                     }
190                     else if (propertyType == typeof(short)) {
191                         il.Emit(OpCodes.Ldind_I2);
192                     }
193                     else if (propertyType == typeof(ushort)) {
194                         il.Emit(OpCodes.Ldind_U2);
195                     }
196                     else if (propertyType == typeof(uint)) {
197                         il.Emit(OpCodes.Ldind_U4);
198                     }
199                     else if (propertyType == typeof(int)) {
200                         il.Emit(OpCodes.Ldind_I4);
201                     }
202                     else if (propertyType == typeof(long)) {
203                         il.Emit(OpCodes.Ldind_I8);
204                     }
205                     else if (propertyType == typeof(ulong)) {
206                         il.Emit(OpCodes.Ldind_I8);  // Somehow, there is no Ldind_u8
207                     }
208                     else if (propertyType == typeof(bool)) {
209                         il.Emit(OpCodes.Ldind_I1);
210                     }
211                     else if (propertyType == typeof(char)) {
212                         il.Emit(OpCodes.Ldind_U2);
213                     }
214                     else if (propertyType == typeof(decimal)) {
215                         il.Emit(OpCodes.Ldobj, propertyType);
216                     }
217                     else if (propertyType == typeof(float)) {
218                         il.Emit(OpCodes.Ldind_R4);
219                     }
220                     else if (propertyType == typeof(double)) {
221                         il.Emit(OpCodes.Ldind_R8);
222                     }
223                     else {
224                         il.Emit(OpCodes.Ldobj, propertyType);
225                     }
226                 }
227                 else if (propertyType.IsValueType) {
228                     // Value type: deal with boxing
229                     il.Emit(OpCodes.Unbox, propertyType);
230                     il.Emit(OpCodes.Ldobj, propertyType);
231                 }
232                 else {
233                     // No boxing involved: just generate a standard cast
234                     il.Emit(OpCodes.Castclass, propertyType);
235                 }
236
237                 // Generate the assignment based on whether it's a Property or Field
238                 if (propInfo != null)
239                     il.EmitCall(OpCodes.Callvirt, setterMethodInfo, null);
240                 else
241                     il.Emit(OpCodes.Stfld, fieldInfo);
242             }
243
244             il.Emit(OpCodes.Ret);
245
246             // Specify that this method implements SetProperty from the inherited interface.
247             accessorTypeBuilder.DefineMethodOverride(method, _setPropertyMethod);
248
249             // Bake in the type.
250             accessorType = accessorTypeBuilder.CreateType();
251
252             return accessorType;
253         }
254
255         private static void GetPropertyInfo(Type type, string propertyName, out PropertyInfo propInfo, out FieldInfo fieldInfo, out Type declaringType) {
256         
257             // First, try to find a property with that name.  Type.GetProperty() without BindingFlags.Declared
258             // will throw AmbiguousMatchException if there is a hidden property with the same name and a
259             // different type (VSWhidbey 237437).  This method finds the property with the specified name
260             // on the most specific type.
261             propInfo = GetPropertyMostSpecific(type, propertyName);
262             fieldInfo = null;
263
264             if (propInfo != null) {
265                 // Get the most base Type where the property is first declared
266                 MethodInfo baseCheckMethodInfo = propInfo.GetGetMethod();
267                 if (baseCheckMethodInfo == null) {
268                     baseCheckMethodInfo = propInfo.GetSetMethod();
269                 }
270                 declaringType = baseCheckMethodInfo.GetBaseDefinition().DeclaringType;
271
272                 // DevDiv Bug 27734
273                 // Ignore the declaring type if it's generic
274                 if (declaringType.IsGenericType)
275                     declaringType = type;
276
277                 // If they're different, get a new PropertyInfo
278                 if (declaringType != type) {
279                     // We want the propertyInfo for the property specifically declared on the declaringType.
280                     // So pass in the correct BindingFlags to avoid an AmbiguousMatchException, which would
281                     // be thrown if the declaringType hides a property with the same name and a different type.
282                     // VSWhidbey 518034
283                     propInfo = declaringType.GetProperty(propertyName, _declaredFlags);
284                 }
285             }
286             else {
287                 // We couldn't find a property, so try a field
288                 // Type.GetField can not throw AmbiguousMatchException like Type.GetProperty above.
289                 fieldInfo = type.GetField(propertyName);
290
291                 // If we couldn't find a field either, give up
292                 if (fieldInfo == null)
293                     throw new ArgumentException();
294
295                 declaringType = fieldInfo.DeclaringType;
296             }
297         }
298
299         private static IWebPropertyAccessor GetPropertyAccessor(Type type, string propertyName) {
300
301             if (s_accessorGenerator == null || s_accessorCache == null) {
302                 lock (s_lockObject) {
303                     if (s_accessorGenerator == null || s_accessorCache == null) {
304                         s_accessorGenerator = new FastPropertyAccessor();
305                         s_accessorCache = new Hashtable();
306                     }
307                 }
308             }
309
310             // First, check if we have it cached
311
312             // Get a hash key based on the Type and the property name
313             int cacheKey = HashCodeCombiner.CombineHashCodes(
314                 type.GetHashCode(), propertyName.GetHashCode());
315
316             IWebPropertyAccessor accessor = (IWebPropertyAccessor)s_accessorCache[cacheKey];
317
318             // It was cached, so just return it
319             if (accessor != null)
320                 return accessor;
321
322             FieldInfo fieldInfo = null;
323             PropertyInfo propInfo = null;
324             Type declaringType;
325
326             GetPropertyInfo(type, propertyName, out propInfo, out fieldInfo, out declaringType);
327
328             // If the Type where the property/field is declared is not the same as the current
329             // Type, check if the declaring Type already has a cached accessor.  This limits
330             // the number of different accessors we need to create.  e.g. Every control has
331             // an ID property, but we'll end up only create one accessor for all of them.
332             int declaringTypeCacheKey = 0;
333             if (declaringType != type) {
334                 // Get a hash key based on the declaring Type and the property name
335                 declaringTypeCacheKey = HashCodeCombiner.CombineHashCodes(
336                     declaringType.GetHashCode(), propertyName.GetHashCode());
337
338                 accessor = (IWebPropertyAccessor) s_accessorCache[declaringTypeCacheKey];
339
340                 // We have a cached accessor for the declaring type, so use it
341                 if (accessor != null) {
342
343                     // Cache the declaring type's accessor as ourselves
344                     lock (s_accessorCache.SyncRoot) {
345                         s_accessorCache[cacheKey] = accessor;
346                     }
347
348                     return accessor;
349                 }
350             }
351
352             if (accessor == null) {
353                 Type propertyAccessorType;
354
355                 lock (s_accessorGenerator) {
356                     propertyAccessorType = s_accessorGenerator.GetPropertyAccessorTypeWithAssert(
357                         declaringType, propertyName, propInfo, fieldInfo);
358                 }
359
360                 // Create the type. This is the only place where Activator.CreateInstance is used,
361                 // reducing the calls to it from 1 per instance to 1 per type.
362                 accessor = (IWebPropertyAccessor) HttpRuntime.CreateNonPublicInstance(propertyAccessorType);
363             }
364
365             // Cache the accessor
366             lock (s_accessorCache.SyncRoot) {
367                 s_accessorCache[cacheKey] = accessor;
368
369                 if (declaringTypeCacheKey != 0)
370                     s_accessorCache[declaringTypeCacheKey] = accessor;
371             }
372
373             return accessor;
374         }
375
376         internal static object GetProperty(object target, string propName, bool inDesigner) {
377             if (!inDesigner) {
378                 IWebPropertyAccessor accessor = GetPropertyAccessor(target.GetType(), propName);
379                 return accessor.GetProperty(target);
380             }
381             else {
382                 // Dev10 bug 491386 - avoid CLR code path that causes an exception when designer uses two
383                 // assemblies of the same name at different locations
384                 FieldInfo fieldInfo = null;
385                 PropertyInfo propInfo = null;
386                 Type declaringType;
387                 GetPropertyInfo(target.GetType(), propName, out propInfo, out fieldInfo, out declaringType);
388                 if (propInfo != null) {
389                     return propInfo.GetValue(target, null);
390                 }
391                 else if (fieldInfo != null) {
392                     return fieldInfo.GetValue(target);
393                 }
394                 throw new ArgumentException();
395             }
396         }
397
398         // Finds the property with the specified name on the most specific type.
399         private static PropertyInfo GetPropertyMostSpecific(Type type, string name) {
400             PropertyInfo propInfo;
401             Type currentType = type;
402
403             while (currentType != null) {
404                 propInfo = currentType.GetProperty(name, _declaredFlags);
405                 if (propInfo != null) {
406                     return propInfo;
407                 }
408                 else {
409                     currentType = currentType.BaseType;
410                 }
411             }
412
413             return null;
414         }
415
416         internal static void SetProperty(object target, string propName, object val, bool inDesigner) {
417             if (!inDesigner) {
418                 IWebPropertyAccessor accessor = GetPropertyAccessor(target.GetType(), propName);
419                 accessor.SetProperty(target, val);
420             }
421             else {
422                 // Dev10 bug 491386 - avoid CLR code path that causes an exception when designer uses two
423                 // assemblies of the same name at different locations
424                 FieldInfo fieldInfo = null;
425                 PropertyInfo propInfo = null;
426                 Type declaringType = null;
427                 GetPropertyInfo(target.GetType(), propName, out propInfo, out fieldInfo, out declaringType);
428                 if (propInfo != null) {
429                     propInfo.SetValue(target, val, null);
430                 }
431                 else if (fieldInfo != null) {
432                     fieldInfo.SetValue(target, val);
433                 }
434                 else {
435                     throw new ArgumentException();
436                 }
437             }
438         }
439     }
440
441     public interface IWebPropertyAccessor {
442         object GetProperty(object target);
443         void SetProperty(object target, object value);
444     }
445 }