Beispiel #1
0
        private static void EmitProxy(ProxyBuilderContext context, ILGenerator il, Type targetType, Type sourceType)
        {
            if (sourceType == targetType)
            {
                // Do nothing.
                return;
            }
            else if (targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()))
            {
                il.Emit(OpCodes.Castclass, targetType);
                return;
            }

            // If we get here, then we actually need a proxy.
            var key = new Tuple <Type, Type>(sourceType, targetType);

            ConstructorInfo      constructor = null;
            ProxyTypeCacheResult cacheResult;
            VerificationResult   verificationResult;

            if (context.Cache.TryGetValue(key, out cacheResult))
            {
                Debug.Assert(!cacheResult.IsError);
                Debug.Assert(cacheResult.Constructor != null);

                // This means we've got a fully-built (published) type.
                constructor = cacheResult.Constructor;
            }
            else if (context.Visited.TryGetValue(key, out verificationResult))
            {
                if (verificationResult.Constructor != null)
                {
                    constructor = verificationResult.Constructor;
                }
            }

            Debug.Assert(constructor != null);

            // Create the proxy.
            il.Emit(OpCodes.Newobj, constructor);
        }
Beispiel #2
0
        private static bool VerifyProxySupport(ProxyBuilderContext context, Tuple <Type, Type> key)
        {
            var sourceType = key.Item1;
            var targetType = key.Item2;

            if (context.Visited.ContainsKey(key))
            {
                // We've already seen this combination and so far so good.
                return(true);
            }

            ProxyTypeCacheResult cacheResult;

            if (context.Cache.TryGetValue(key, out cacheResult))
            {
                // If we get here we've got a published conversion or error, so we can stop searching.
                return(!cacheResult.IsError);
            }

            if (targetType == sourceType || targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()))
            {
                // If we find a trivial conversion, then that will work.
                return(true);
            }

            if (!targetType.GetTypeInfo().IsInterface)
            {
                var message = Resources.FormatConverter_TypeMustBeInterface(targetType.FullName, sourceType.FullName);
                context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                return(false);
            }

            // This is a combination we haven't seen before, and it *might* support proxy generation, so let's
            // start trying.
            var verificationResult = new VerificationResult();

            context.Visited.Add(key, verificationResult);

            // We support conversions from IList<T> -> IReadOnlyList<U> and IReadOnlyList<T> -> IReadOnlyList<U>
            if (targetType.GetTypeInfo().IsGenericType&&
                targetType.GetTypeInfo().GetGenericTypeDefinition() == typeof(IReadOnlyList <>))
            {
                var sourceInterfaceType = GetGenericImplementation(sourceType, typeof(IList <>));
                if (sourceInterfaceType != null)
                {
                    var targetElementType = targetType.GetTypeInfo().GenericTypeArguments[0];
                    var sourceElementType = sourceInterfaceType.GetTypeInfo().GenericTypeArguments[0];

                    var elementKey = new Tuple <Type, Type>(sourceElementType, targetElementType);
                    if (!VerifyProxySupport(context, elementKey))
                    {
                        var error = context.Cache[elementKey];
                        Debug.Assert(error != null && error.IsError);
                        context.Cache[key] = error;
                        return(false);
                    }

                    VerificationResult elementResult;
                    context.Visited.TryGetValue(elementKey, out elementResult);

                    var proxyType = elementResult?.Type?.GetTypeInfo() ?? (TypeInfo)elementResult?.TypeBuilder;
                    if (proxyType == null)
                    {
                        // No proxy needed for elements.
                        verificationResult.Type        = typeof(ProxyList <,>).MakeGenericType(elementKey.Item1, elementKey.Item2);
                        verificationResult.Constructor = verificationResult.Type.GetTypeInfo().DeclaredConstructors.First();
                    }
                    else
                    {
                        // We need to proxy each of the elements. Let's generate a type.
                        GenerateProxyTypeForList(elementKey.Item1, elementKey.Item2, proxyType.AsType(), verificationResult);
                    }

                    return(true);
                }
            }

            // This doesn't match any of our interface conversions, so we'll codegen a proxy.
            var propertyMappings = new List <KeyValuePair <PropertyInfo, PropertyInfo> >();

            var sourceProperties = sourceType.GetRuntimeProperties();
            var targetProperties = targetType
                                   .GetTypeInfo()
                                   .ImplementedInterfaces
                                   .SelectMany(i => i.GetRuntimeProperties())
                                   .Concat(targetType.GetRuntimeProperties());

            foreach (var targetProperty in targetProperties)
            {
                if (!targetProperty.CanRead)
                {
                    var message = Resources.FormatConverter_PropertyMustHaveGetter(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return(false);
                }

                if (targetProperty.CanWrite)
                {
                    var message = Resources.FormatConverter_PropertyMustNotHaveSetter(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return(false);
                }

                if (targetProperty.GetIndexParameters()?.Length > 0)
                {
                    var message = Resources.FormatConverter_PropertyMustNotHaveIndexParameters(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return(false);
                }

                // To allow for flexible versioning, we want to allow missing properties in the source.
                //
                // For now we'll just store null, and later generate a stub getter that returns default(T).
                var sourceProperty = sourceProperties.Where(p => p.Name == targetProperty.Name).FirstOrDefault();
                if (sourceProperty != null &&
                    sourceProperty.CanRead &&
                    sourceProperty.GetMethod?.IsPublic == true)
                {
                    var propertyKey = new Tuple <Type, Type>(sourceProperty.PropertyType, targetProperty.PropertyType);
                    if (!VerifyProxySupport(context, propertyKey))
                    {
                        // There's an error here, so bubble it up and cache it.
                        var error = context.Cache[propertyKey];
                        Debug.Assert(error != null && error.IsError);

                        context.Cache[key] = ProxyTypeCacheResult.FromError(key, error.Error);
                        return(false);
                    }

                    propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, sourceProperty));
                }
                else
                {
                    propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, null));
                }
            }

            verificationResult.Mappings = propertyMappings;
            GenerateProxyTypeFromProperties(sourceType, targetType, verificationResult);

            return(true);
        }
Beispiel #3
0
        private static void AddProperties(
            ProxyBuilderContext context,
            TypeBuilder typeBuilder,
            IEnumerable <KeyValuePair <PropertyInfo, PropertyInfo> > properties)
        {
            foreach (var property in properties)
            {
                var targetProperty = property.Key;
                var sourceProperty = property.Value;

                var propertyBuilder = typeBuilder.DefineProperty(
                    targetProperty.Name,
                    PropertyAttributes.None,
                    targetProperty.PropertyType,
                    EmptyTypes);

                var methodBuilder = typeBuilder.DefineMethod(
                    targetProperty.GetMethod.Name,
                    targetProperty.GetMethod.Attributes & ~MethodAttributes.Abstract,
                    targetProperty.GetMethod.CallingConvention,
                    targetProperty.GetMethod.ReturnType,
                    EmptyTypes);
                propertyBuilder.SetGetMethod(methodBuilder);
                typeBuilder.DefineMethodOverride(methodBuilder, targetProperty.GetMethod);

                var il = methodBuilder.GetILGenerator();
                if (sourceProperty == null)
                {
                    // Return a default(T) value.
                    il.DeclareLocal(targetProperty.PropertyType);

                    il.Emit(OpCodes.Ldloca_S, 0);
                    il.Emit(OpCodes.Initobj, targetProperty.PropertyType);

                    il.Emit(OpCodes.Ldloc_S, 0);
                    il.Emit(OpCodes.Ret);
                    continue;
                }

                il.DeclareLocal(targetProperty.PropertyType);
                il.DeclareLocal(sourceProperty.PropertyType);

                // Init variables with default(T)
                il.Emit(OpCodes.Ldloca_S, 0);
                il.Emit(OpCodes.Initobj, targetProperty.PropertyType);

                il.Emit(OpCodes.Ldloca_S, 1);
                il.Emit(OpCodes.Initobj, sourceProperty.PropertyType);

                // Push 'this' and get the underlying instance.
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld,
                        typeBuilder.BaseType.GetRuntimeFields().First(
                            f => string.Equals(f.Name, "Instance", StringComparison.Ordinal) &&
                            !f.IsStatic &&
                            f.IsPublic));

                // Call the source property.
                il.EmitCall(OpCodes.Callvirt, sourceProperty.GetMethod, null);
                il.Emit(OpCodes.Stloc_S, 1);

                var @return = il.DefineLabel();

                // BrFalse can't handle all value types (Decimal for example), so we skip it for value types
                // since we're using it as a null-check anyway.
                if (!sourceProperty.PropertyType.GetTypeInfo().IsValueType)
                {
                    // At this point the value on the stack is the return value of the the source-type property.
                    // If it returned null, we want to just jump to return.
                    il.Emit(OpCodes.Ldloc_S, 1);
                    il.Emit(OpCodes.Brfalse, @return);
                }

                // Create a proxy for the value returned by source property (if necessary).
                il.Emit(OpCodes.Ldloc_S, 1);
                EmitProxy(context, il, targetProperty.PropertyType, sourceProperty.PropertyType);
                il.Emit(OpCodes.Stloc_S, 0);

                il.MarkLabel(@return);
                il.Emit(OpCodes.Ldloc_S, 0);
                il.Emit(OpCodes.Ret);
            }
        }
Beispiel #4
0
        public static Type GetProxyType(ProxyTypeCache cache, Type targetType, Type sourceType)
        {
            if (targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()))
            {
                return(null);
            }

            var key = new Tuple <Type, Type>(sourceType, targetType);

            ProxyTypeCacheResult result;

            if (!cache.TryGetValue(key, out result))
            {
                var context = new ProxyBuilderContext(cache, targetType, sourceType);

                // Check that all required types are proxy-able - this will create the TypeBuilder, Constructor,
                // and property mappings.
                //
                // We need to create the TypeBuilder and Constructor up front to deal with cycles that can occur
                // when generating the proxy properties.
                if (!VerifyProxySupport(context, context.Key))
                {
                    var error = cache[key];
                    Debug.Assert(error != null && error.IsError);
                    throw new InvalidOperationException(error.Error);
                }

                Debug.Assert(context.Visited.ContainsKey(context.Key));

                // Now that we've generated all of the constructors for the proxies, we can generate the properties.
                foreach (var verificationResult in context.Visited)
                {
                    if (verificationResult.Value.Mappings != null)
                    {
                        AddProperties(
                            context,
                            verificationResult.Value.TypeBuilder,
                            verificationResult.Value.Mappings);
                    }
                }

                // Now generate the type
                foreach (var verificationResult in context.Visited)
                {
                    if (verificationResult.Value.TypeBuilder != null)
                    {
                        verificationResult.Value.Type = verificationResult.Value.TypeBuilder.CreateTypeInfo().AsType();
                    }
                }

                // We only want to publish the results after all of the proxies are totally generated.
                foreach (var verificationResult in context.Visited)
                {
                    cache[verificationResult.Key] = ProxyTypeCacheResult.FromType(
                        verificationResult.Key,
                        verificationResult.Value.Type,
                        verificationResult.Value.Constructor);
                }

                return(context.Visited[context.Key].Type);
            }
            else if (result.IsError)
            {
                throw new InvalidOperationException(result.Error);
            }
            else if (result.Type == null)
            {
                // This is an identity convertion
                return(null);
            }
            else
            {
                return(result.Type);
            }
        }
        private static void AddProperties(
            ProxyBuilderContext context,
            TypeBuilder typeBuilder,
            IEnumerable<KeyValuePair<PropertyInfo, PropertyInfo>> properties)
        {
            foreach (var property in properties)
            {
                var targetProperty = property.Key;
                var sourceProperty = property.Value;

                var propertyBuilder = typeBuilder.DefineProperty(
                    targetProperty.Name,
                    PropertyAttributes.None,
                    property.Key.PropertyType,
                    Type.EmptyTypes);

                var methodBuilder = typeBuilder.DefineMethod(
                    targetProperty.GetMethod.Name,
                    targetProperty.GetMethod.Attributes & ~MethodAttributes.Abstract,
                    targetProperty.GetMethod.CallingConvention,
                    targetProperty.GetMethod.ReturnType,
                    Type.EmptyTypes);
                propertyBuilder.SetGetMethod(methodBuilder);
                typeBuilder.DefineMethodOverride(methodBuilder, targetProperty.GetMethod);

                var il = methodBuilder.GetILGenerator();
                if (sourceProperty == null)
                {
                    il.DeclareLocal(targetProperty.PropertyType);

                    // Return a default(T) value.
                    il.Emit(OpCodes.Ldloca_S, 0);
                    il.Emit(OpCodes.Initobj, targetProperty.PropertyType);

                    il.Emit(OpCodes.Ldloc_S, 0);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    // Push 'this' and get the underlying instance.
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld,
                        typeBuilder.BaseType.GetField(
                            "Instance",
                            BindingFlags.Instance | BindingFlags.Public));

                    // Call the source property.
                    il.EmitCall(OpCodes.Callvirt, sourceProperty.GetMethod, null);

                    // Create a proxy for the value returned by source property (if necessary).
                    EmitProxy(context, il, targetProperty.PropertyType, sourceProperty.PropertyType);
                    il.Emit(OpCodes.Ret);
                }
            }
        }
        private static bool VerifyProxySupport(ProxyBuilderContext context, Tuple<Type, Type> key)
        {
            var sourceType = key.Item1;
            var targetType = key.Item2;

            if (context.Visited.ContainsKey(key))
            {
                // We've already seen this combination and so far so good.
                return true;
            }

            ProxyTypeCacheResult cacheResult;
            if (context.Cache.TryGetValue(key, out cacheResult))
            {
                // If we get here we've got a published conversion or error, so we can stop searching.
                return !cacheResult.IsError;
            }

            if (targetType == sourceType || targetType.IsAssignableFrom(sourceType))
            {
                // If we find a trivial conversion, then that will work.
                return true;
            }

            if (!targetType.GetTypeInfo().IsInterface)
            {
                var message = Resources.FormatConverter_TypeMustBeInterface(targetType.FullName, sourceType.FullName);
                context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                return false;
            }

            // This is a combination we haven't seen before, and it *might* support proxy generation, so let's
            // start trying.
            var verificationResult = new VerificationResult();
            context.Visited.Add(key, verificationResult);

            // We support conversions from IList<T> -> IReadOnlyList<U> and IReadOnlyList<T> -> IReadOnlyList<U>
            if (targetType.GetTypeInfo().IsGenericType &&
                targetType.GetTypeInfo().GetGenericTypeDefinition() == typeof(IReadOnlyList<>))
            {
                var sourceInterfaceType = GetGenericImplementation(sourceType, typeof(IList<>));
                if (sourceInterfaceType != null)
                {
                    var targetElementType = targetType.GetGenericArguments()[0];
                    var sourceElementType = sourceInterfaceType.GetGenericArguments()[0];

                    var elementKey = new Tuple<Type, Type>(sourceElementType, targetElementType);
                    if (!VerifyProxySupport(context, elementKey))
                    {
                        var error = context.Cache[elementKey];
                        Debug.Assert(error != null && error.IsError);
                        context.Cache[key] = error;
                        return false;
                    }

                    VerificationResult elementResult;
                    context.Visited.TryGetValue(elementKey, out elementResult);

                    var proxyType = elementResult?.Type?.GetTypeInfo() ?? (TypeInfo)elementResult?.TypeBuilder;
                    if (proxyType == null)
                    {
                        // No proxy needed for elements.
                        verificationResult.Type = typeof(ProxyList<,>).MakeGenericType(elementKey.Item1, elementKey.Item2);
                        verificationResult.Constructor = verificationResult.Type.GetConstructors()[0];
                    }
                    else
                    {
                        // We need to proxy each of the elements. Let's generate a type.
                        GenerateProxyTypeForList(elementKey.Item1, elementKey.Item2, proxyType.AsType(), verificationResult);
                    }

                    return true;
                }
            }

            // This doesn't match any of our interface conversions, so we'll codegen a proxy.
            var propertyMappings = new List<KeyValuePair<PropertyInfo, PropertyInfo>>();

            var sourceProperties = sourceType.GetRuntimeProperties();
            foreach (var targetProperty in targetType.GetRuntimeProperties())
            {
                if (!targetProperty.CanRead)
                {
                    var message = Resources.FormatConverter_PropertyMustHaveGetter(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return false;
                }

                if (targetProperty.CanWrite)
                {
                    var message = Resources.FormatConverter_PropertyMustNotHaveSetter(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return false;
                }

                if (targetProperty.GetIndexParameters()?.Length > 0)
                {
                    var message = Resources.FormatConverter_PropertyMustNotHaveIndexParameters(
                        targetProperty.Name,
                        targetType.FullName);
                    context.Cache[key] = ProxyTypeCacheResult.FromError(key, message);

                    return false;
                }

                // To allow for flexible versioning, we want to allow missing properties in the source.
                //
                // For now we'll just store null, and later generate a stub getter that returns default(T).
                var sourceProperty = sourceProperties.Where(p => p.Name == targetProperty.Name).FirstOrDefault();
                if (sourceProperty != null &&
                    sourceProperty.CanRead &&
                    sourceProperty.GetMethod?.IsPublic == true)
                {
                    var propertyKey = new Tuple<Type, Type>(sourceProperty.PropertyType, targetProperty.PropertyType);
                    if (!VerifyProxySupport(context, propertyKey))
                    {
                        // There's an error here, so bubble it up and cache it.
                        var error = context.Cache[propertyKey];
                        Debug.Assert(error != null && error.IsError);

                        context.Cache[key] = ProxyTypeCacheResult.FromError(key, error.Error);
                        return false;
                    }

                    propertyMappings.Add(new KeyValuePair<PropertyInfo, PropertyInfo>(targetProperty, sourceProperty));
                }
                else
                {
                    propertyMappings.Add(new KeyValuePair<PropertyInfo, PropertyInfo>(targetProperty, null));
                }
            }

            verificationResult.Mappings = propertyMappings;
            GenerateProxyTypeFromProperties(sourceType, targetType, verificationResult);

            return true;
        }
        public static Type GetProxyType(ProxyTypeCache cache, Type targetType, Type sourceType)
        {
            if (targetType.IsAssignableFrom(sourceType))
            {
                return null;
            }

            var key = new Tuple<Type, Type>(sourceType, targetType);

            ProxyTypeCacheResult result;
            if (!cache.TryGetValue(key, out result))
            {
                var context = new ProxyBuilderContext(cache, targetType, sourceType);

                // Check that all required types are proxy-able - this will create the TypeBuilder, Constructor,
                // and property mappings.
                //
                // We need to create the TypeBuilder and Constructor up front to deal with cycles that can occur
                // when generating the proxy properties.
                if (!VerifyProxySupport(context, context.Key))
                {
                    var error = cache[key];
                    Debug.Assert(error != null && error.IsError);
                    throw new InvalidOperationException(error.Error);
                }

                Debug.Assert(context.Visited.ContainsKey(context.Key));

                // Now that we've generated all of the constructors for the proxies, we can generate the properties.
                foreach (var verificationResult in context.Visited)
                {
                    if (verificationResult.Value.Mappings != null)
                    {
                        AddProperties(
                            context,
                            verificationResult.Value.TypeBuilder,
                            verificationResult.Value.Mappings);
                    }
                }

                // Now generate the type
                foreach (var verificationResult in context.Visited)
                {
                    if (verificationResult.Value.TypeBuilder != null)
                    {
                        verificationResult.Value.Type = verificationResult.Value.TypeBuilder.CreateTypeInfo().AsType();
                    }
                }

                // We only want to publish the results after all of the proxies are totally generated.
                foreach (var verificationResult in context.Visited)
                {
                    cache[verificationResult.Key] = ProxyTypeCacheResult.FromType(
                        verificationResult.Key,
                        verificationResult.Value.Type,
                        verificationResult.Value.Constructor);
                }

                return context.Visited[context.Key].Type;
            }
            else if (result.IsError)
            {
                throw new InvalidOperationException(result.Error);
            }
            else if (result.Type == null)
            {
                // This is an identity convertion
                return null;
            }
            else
            {
                return result.Type;
            }
        }
        private static void EmitProxy(ProxyBuilderContext context, ILGenerator il, Type targetType, Type sourceType)
        {
            if (sourceType == targetType)
            {
                // Do nothing.
                return;
            }
            else if (targetType.IsAssignableFrom(sourceType))
            {
                il.Emit(OpCodes.Castclass, targetType);
                return;
            }

            // If we get here, then we actually need a proxy.
            var key = new Tuple<Type, Type>(sourceType, targetType);

            ConstructorInfo constructor = null;
            ProxyTypeCacheResult cacheResult;
            VerificationResult verificationResult;
            if (context.Cache.TryGetValue(key, out cacheResult))
            {
                Debug.Assert(!cacheResult.IsError);
                Debug.Assert(cacheResult.Constructor != null);

                // This means we've got a fully-built (published) type.
                constructor = cacheResult.Constructor;
            }
            else if (context.Visited.TryGetValue(key, out verificationResult))
            {
                if (verificationResult.Constructor != null)
                {
                    constructor = verificationResult.Constructor;
                }
            }

            Debug.Assert(constructor != null);

            // Create the proxy.
            il.Emit(OpCodes.Newobj, constructor);
        }