minor fix for bug 9520:
[mono.git] / mcs / class / System.ComponentModel.Composition.4.5 / src / ComponentModel / System / ComponentModel / Composition / MetadataViewGenerator.cs
1 // -----------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 // -----------------------------------------------------------------------
4 using System;
5 using System.Collections.Generic;
6 using System.ComponentModel;
7 using System.Globalization;
8 using System.Linq;
9 using System.Reflection;
10 using System.Threading;
11 using Microsoft.Internal;
12 using System.Reflection.Emit;
13 using System.Collections;
14 using System.Security;
15
16 namespace System.ComponentModel.Composition
17 {
18     // // Assume TMetadataView is
19     // //interface Foo
20     // //{
21     // //    public typeRecord1 Record1 { get; }
22     // //    public typeRecord2 Record2 { get; }
23     // //    public typeRecord3 Record3 { get; }
24     // //    public typeRecord4 Record4 { get; }
25     // //}
26     // // The class to be generated will look approximately like:
27     // public class __Foo__MedataViewProxy : TMetadataView
28     // {
29     //     public __Foo__MedataViewProxy (IDictionary<string, object> metadata)
30     //     {
31     //         if(metadata == null)
32     //         {
33     //             throw InvalidArgumentException("metadata");
34     //         }
35     //         try
36     //         {
37     //              Record1 = (typeRecord1)Record1;
38     //              Record2 = (typeRecord1)Record2;
39     //              Record3 = (typeRecord1)Record3;
40     //              Record4 = (typeRecord1)Record4;
41     //          }
42     //          catch(InvalidCastException ice)
43     //          {
44     //              //Annotate exception .Data with diagnostic info
45     //          }
46     //          catch(NulLReferenceException ice)
47     //          {
48     //              //Annotate exception .Data with diagnostic info
49     //          }
50     //     }
51     //     // Interface
52     //     public typeRecord1 Record1 { get; }
53     //     public typeRecord2 Record2 { get; }
54     //     public typeRecord3 Record3 { get; }
55     //     public typeRecord4 Record4 { get; }
56     // }
57     internal static class MetadataViewGenerator
58     {
59         public const string MetadataViewType       = "MetadataViewType";
60         public const string MetadataItemKey        = "MetadataItemKey";
61         public const string MetadataItemTargetType = "MetadataItemTargetType";
62         public const string MetadataItemSourceType = "MetadataItemSourceType";
63         public const string MetadataItemValue      = "MetadataItemValue";
64
65         private static Lock _lock = new Lock();
66         private static Dictionary<Type, Type> _proxies = new Dictionary<Type, Type>();
67         private static AssemblyName ProxyAssemblyName = new AssemblyName(string.Format(CultureInfo.InvariantCulture, "MetadataViewProxies_{0}", Guid.NewGuid()));
68 #if FEATURE_CAS_APTCA
69         private static ModuleBuilder    criticalProxyModuleBuilder;
70 #endif //FEATURE_CAS_APTCA
71         private static ModuleBuilder    transparentProxyModuleBuilder;
72
73         private static Type[] CtorArgumentTypes = new Type[] { typeof(IDictionary<string, object>) };
74         private static MethodInfo _mdvDictionaryTryGet = CtorArgumentTypes[0].GetMethod("TryGetValue");
75         private static readonly MethodInfo ObjectGetType = typeof(object).GetMethod("GetType", Type.EmptyTypes);
76 #if FEATURE_CAS_APTCA
77         private static CustomAttributeBuilder _securityCriticalBuilder = 
78             new CustomAttributeBuilder(typeof(SecurityCriticalAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
79 #endif //FEATURE_CAS_APTCA
80
81         private static AssemblyBuilder CreateProxyAssemblyBuilder(ConstructorInfo constructorInfo)
82         {
83 #if FEATURE_CAS_APTCA
84             object[] args = new object[0];
85             CustomAttributeBuilder accessAttribute = new CustomAttributeBuilder(constructorInfo, args);
86             CustomAttributeBuilder[] attributes = { accessAttribute };
87             return AppDomain.CurrentDomain.DefineDynamicAssembly(ProxyAssemblyName, AssemblyBuilderAccess.Run, attributes, SecurityContextSource.CurrentAppDomain);
88 #else
89             return AppDomain.CurrentDomain.DefineDynamicAssembly(ProxyAssemblyName, AssemblyBuilderAccess.Run);
90 #endif //FEATURE_CAS_APTCA
91         }
92
93         // Must be called with _lock held
94         private static ModuleBuilder GetProxyModuleBuilder(bool requiresCritical)
95         {
96 #if FEATURE_CAS_APTCA
97             if(requiresCritical)
98             {
99                 // Needed a critical modulebuilder so find or make it
100                 if (criticalProxyModuleBuilder == null)
101                 {
102                     var assemblyBuilder = CreateProxyAssemblyBuilder(typeof(AllowPartiallyTrustedCallersAttribute).GetConstructor(Type.EmptyTypes));
103                     criticalProxyModuleBuilder = assemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule");
104                 }
105                 return criticalProxyModuleBuilder;
106             }
107 #endif //FEATURE_CAS_APTCA
108             if (transparentProxyModuleBuilder == null)
109             {
110                 // make a new assemblybuilder and modulebuilder
111                 var assemblyBuilder = CreateProxyAssemblyBuilder(typeof(SecurityTransparentAttribute).GetConstructor(Type.EmptyTypes));
112                 transparentProxyModuleBuilder = assemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule");
113             }
114
115             return transparentProxyModuleBuilder;
116         }
117
118         public static Type GenerateView(Type viewType)
119         {
120             Assumes.NotNull(viewType);
121             Assumes.IsTrue(viewType.IsInterface);
122
123             Type proxyType;
124             bool foundProxy;
125
126             using (new ReadLock(_lock))
127             {
128                 foundProxy = _proxies.TryGetValue(viewType, out proxyType);
129             }
130
131             // No factory exists
132             if(!foundProxy)
133             {
134                 // Try again under a write lock if still none generate the proxy
135                 Type generatedProxyType = GenerateInterfaceViewProxyType(viewType);
136                 Assumes.NotNull(generatedProxyType);
137
138                 using (new WriteLock(_lock))
139                 {
140                     if (!_proxies.TryGetValue(viewType, out proxyType))
141                     {
142                         proxyType = generatedProxyType;
143                         _proxies.Add(viewType, proxyType);
144                     }
145                 }
146             }
147             return proxyType;
148         }
149
150         private static void GenerateLocalAssignmentFromDefaultAttribute(this ILGenerator IL, DefaultValueAttribute[] attrs, LocalBuilder local)
151         {
152             if (attrs.Length > 0)
153             {
154                 DefaultValueAttribute defaultAttribute = attrs[0];
155                 IL.LoadValue(defaultAttribute.Value);
156                 if ((defaultAttribute.Value != null) && (defaultAttribute.Value.GetType().IsValueType))
157                 {
158                     IL.Emit(OpCodes.Box, defaultAttribute.Value.GetType());
159                 }
160                 IL.Emit(OpCodes.Stloc, local);
161             }
162         }
163
164         private static void GenerateFieldAssignmentFromLocalValue(this ILGenerator IL, LocalBuilder local, FieldBuilder field)
165         {
166             IL.Emit(OpCodes.Ldarg_0);
167             IL.Emit(OpCodes.Ldloc, local);
168             IL.Emit(field.FieldType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, field.FieldType);
169             IL.Emit(OpCodes.Stfld, field);
170         }
171
172         private static void GenerateLocalAssignmentFromFlag(this ILGenerator IL, LocalBuilder local, bool flag)
173         {
174             IL.Emit(flag ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
175             IL.Emit(OpCodes.Stloc, local);
176         }
177
178         // This must be called with _readerWriterLock held for Write
179         private static Type GenerateInterfaceViewProxyType(Type viewType)
180         {
181             // View type is an interface let's cook an implementation
182             Type proxyType;
183             TypeBuilder proxyTypeBuilder;
184             Type[] interfaces = { viewType };
185             bool requiresCritical = false;
186 #if FEATURE_CAS_APTCA
187             requiresCritical = !viewType.IsSecurityTransparent;
188 #endif //FEATURE_CAS_APTCA
189
190             var proxyModuleBuilder = GetProxyModuleBuilder(requiresCritical);
191             proxyTypeBuilder = proxyModuleBuilder.DefineType(
192                 string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", viewType.FullName, Guid.NewGuid()),
193                 TypeAttributes.Public,
194                 typeof(object), 
195                 interfaces);
196 #if FEATURE_CAS_APTCA
197             if (requiresCritical)
198             {
199                 proxyTypeBuilder.SetCustomAttribute(_securityCriticalBuilder);
200             }
201 #endif //FEATURE_CAS_APTCA
202             // Implement Constructor
203             ILGenerator proxyCtorIL = proxyTypeBuilder.CreateGeneratorForPublicConstructor(CtorArgumentTypes);
204             LocalBuilder exception = proxyCtorIL.DeclareLocal(typeof(Exception));
205             LocalBuilder exceptionData = proxyCtorIL.DeclareLocal(typeof(IDictionary));
206             LocalBuilder sourceType = proxyCtorIL.DeclareLocal(typeof(Type));
207             LocalBuilder value = proxyCtorIL.DeclareLocal(typeof(object));
208             LocalBuilder usesExportedMD = proxyCtorIL.DeclareLocal(typeof(bool));
209
210             Label tryConstructView = proxyCtorIL.BeginExceptionBlock();
211
212             // Implement interface properties
213             foreach (PropertyInfo propertyInfo in viewType.GetAllProperties())
214             {
215                 string fieldName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}", propertyInfo.Name, Guid.NewGuid());
216
217                 // Cache names and type for exception
218                 string propertyName = string.Format(CultureInfo.InvariantCulture, "{0}", propertyInfo.Name);
219
220                 Type[] propertyTypeArguments = new Type[] { propertyInfo.PropertyType };
221                 Type[] optionalModifiers = null;
222                 Type[] requiredModifiers = null;
223
224 #if FEATURE_ADVANCEDREFLECTION
225                 // PropertyInfo does not support GetOptionalCustomModifiers and GetRequiredCustomModifiers on Silverlight
226                 optionalModifiers = propertyInfo.GetOptionalCustomModifiers();
227                 requiredModifiers = propertyInfo.GetRequiredCustomModifiers();
228                 Array.Reverse(optionalModifiers);
229                 Array.Reverse(requiredModifiers);
230 #endif //FEATURE_ADVANCEDREFLECTION
231
232                 // Generate field
233                 FieldBuilder proxyFieldBuilder = proxyTypeBuilder.DefineField(
234                     fieldName,
235                     propertyInfo.PropertyType,
236                     FieldAttributes.Private);
237
238                 // Generate property
239                 PropertyBuilder proxyPropertyBuilder = proxyTypeBuilder.DefineProperty(
240                     propertyName,
241                     PropertyAttributes.None,
242                     propertyInfo.PropertyType,
243                     propertyTypeArguments);
244
245                 // Generate constructor code for retrieving the metadata value and setting the field
246                 Label tryCastValue = proxyCtorIL.BeginExceptionBlock();
247                 Label innerTryCastValue;
248
249                 DefaultValueAttribute[] attrs = propertyInfo.GetAttributes<DefaultValueAttribute>(false);
250                 if(attrs.Length > 0)
251                 {
252                     innerTryCastValue = proxyCtorIL.BeginExceptionBlock();
253                 }
254
255                 // In constructor set the backing field with the value from the dictionary
256                 Label doneGettingDefaultValue = proxyCtorIL.DefineLabel();
257                 GenerateLocalAssignmentFromFlag(proxyCtorIL, usesExportedMD, true);
258
259                 proxyCtorIL.Emit(OpCodes.Ldarg_1);
260                 proxyCtorIL.Emit(OpCodes.Ldstr, propertyInfo.Name);
261                 proxyCtorIL.Emit(OpCodes.Ldloca, value);
262                 proxyCtorIL.Emit(OpCodes.Callvirt, _mdvDictionaryTryGet);
263                 proxyCtorIL.Emit(OpCodes.Brtrue, doneGettingDefaultValue);
264
265                 proxyCtorIL.GenerateLocalAssignmentFromFlag(usesExportedMD, false);
266                 proxyCtorIL.GenerateLocalAssignmentFromDefaultAttribute(attrs, value);
267
268                 proxyCtorIL.MarkLabel(doneGettingDefaultValue);
269                 proxyCtorIL.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder);
270                 proxyCtorIL.Emit(OpCodes.Leave, tryCastValue);
271
272                 // catch blocks for innerTryCastValue start here
273                 if (attrs.Length > 0)
274                 {
275                     proxyCtorIL.BeginCatchBlock(typeof(InvalidCastException));
276                     {
277                         Label notUsesExportedMd = proxyCtorIL.DefineLabel();
278                         proxyCtorIL.Emit(OpCodes.Ldloc, usesExportedMD);
279                         proxyCtorIL.Emit(OpCodes.Brtrue, notUsesExportedMd);
280                         proxyCtorIL.Emit(OpCodes.Rethrow);
281                         proxyCtorIL.MarkLabel(notUsesExportedMd);
282                         proxyCtorIL.GenerateLocalAssignmentFromDefaultAttribute(attrs, value);
283                         proxyCtorIL.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder);
284                     }
285                     proxyCtorIL.EndExceptionBlock();
286                 }
287
288                 // catch blocks for tryCast start here
289                 proxyCtorIL.BeginCatchBlock(typeof(NullReferenceException));
290                 {
291                     proxyCtorIL.Emit(OpCodes.Stloc, exception);
292
293                     proxyCtorIL.GetExceptionDataAndStoreInLocal(exception, exceptionData);
294                     proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName);
295                     proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType);
296                     proxyCtorIL.Emit(OpCodes.Rethrow);
297                 }
298
299                 proxyCtorIL.BeginCatchBlock(typeof(InvalidCastException));
300                 {
301                     proxyCtorIL.Emit(OpCodes.Stloc, exception);
302
303                     proxyCtorIL.GetExceptionDataAndStoreInLocal(exception, exceptionData);
304                     proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName);
305                     proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType);
306                     proxyCtorIL.Emit(OpCodes.Rethrow);
307                 }
308
309                 proxyCtorIL.EndExceptionBlock();
310
311                 if (propertyInfo.CanWrite)
312                 {
313                     // The MetadataView '{0}' is invalid because property '{1}' has a property set method.
314                     throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
315                         Strings.InvalidSetterOnMetadataField,
316                         viewType,
317                         propertyName));
318                 }
319                 if (propertyInfo.CanRead)
320                 {
321                     // Generate "get" method implementation.
322                     MethodBuilder getMethodBuilder = proxyTypeBuilder.DefineMethod(
323                         string.Format(CultureInfo.InvariantCulture, "get_{0}", propertyName),
324                         MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final,
325                         CallingConventions.HasThis,
326                         propertyInfo.PropertyType,
327                         requiredModifiers,
328                         optionalModifiers,
329                         Type.EmptyTypes, null, null);
330
331                     proxyTypeBuilder.DefineMethodOverride(getMethodBuilder, propertyInfo.GetGetMethod());
332 #if FEATURE_CAS_APTCA
333                     if(!viewType.IsSecurityTransparent)
334                     {
335                         getMethodBuilder.SetCustomAttribute(_securityCriticalBuilder);
336                     }
337 #endif //FEATURE_CAS_APTCA
338                     ILGenerator getMethodIL = getMethodBuilder.GetILGenerator();
339                     getMethodIL.Emit(OpCodes.Ldarg_0);
340                     getMethodIL.Emit(OpCodes.Ldfld, proxyFieldBuilder);
341                     getMethodIL.Emit(OpCodes.Ret);
342
343                     proxyPropertyBuilder.SetGetMethod(getMethodBuilder);
344                 }
345             }
346
347             proxyCtorIL.Emit(OpCodes.Leave, tryConstructView);
348
349             // catch blocks for constructView start here
350             proxyCtorIL.BeginCatchBlock(typeof(NullReferenceException));
351             {
352                 proxyCtorIL.Emit(OpCodes.Stloc, exception);
353
354                 proxyCtorIL.GetExceptionDataAndStoreInLocal(exception, exceptionData);
355                 proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataViewType, viewType);
356                 proxyCtorIL.Emit(OpCodes.Rethrow);
357             }
358             proxyCtorIL.BeginCatchBlock(typeof(InvalidCastException));
359             {
360                 proxyCtorIL.Emit(OpCodes.Stloc, exception);
361
362                 proxyCtorIL.GetExceptionDataAndStoreInLocal(exception, exceptionData);
363                 proxyCtorIL.Emit(OpCodes.Ldloc, value);
364                 proxyCtorIL.Emit(OpCodes.Call, ObjectGetType);
365                 proxyCtorIL.Emit(OpCodes.Stloc, sourceType);
366                 proxyCtorIL.AddItemToLocalDictionary(exceptionData, MetadataViewType, viewType);
367                 proxyCtorIL.AddLocalToLocalDictionary(exceptionData, MetadataItemSourceType, sourceType);
368                 proxyCtorIL.AddLocalToLocalDictionary(exceptionData, MetadataItemValue, value);
369                 proxyCtorIL.Emit(OpCodes.Rethrow);
370             }
371             proxyCtorIL.EndExceptionBlock();
372
373             // Finished implementing interface and constructor
374             proxyCtorIL.Emit(OpCodes.Ret);
375             proxyType = proxyTypeBuilder.CreateType();
376
377             return proxyType;
378         }
379
380     }
381 }