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