//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- namespace System.Data.Objects { using System; using System.Data.Common.Utils; using System.Data.Metadata.Edm; using System.Data.Objects.DataClasses; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; /// /// CodeGenerator class: use lightweight code gen to dynamically generate code to get/set properties. /// internal static class LightweightCodeGenerator { /// For an OSpace ComplexType returns the delegate to construct the clr instance. internal static Delegate GetConstructorDelegateForType(ClrComplexType clrType) { return (clrType.Constructor ?? (clrType.Constructor = CreateConstructor(clrType.ClrType))); } /// For an OSpace EntityType returns the delegate to construct the clr instance. internal static Delegate GetConstructorDelegateForType(ClrEntityType clrType) { return (clrType.Constructor ?? (clrType.Constructor = CreateConstructor(clrType.ClrType))); } /// for an OSpace property, get the property value from a clr instance internal static object GetValue(EdmProperty property, object target) { Func getter = GetGetterDelegateForProperty(property); Debug.Assert(null != getter, "null getter"); return getter(target); } internal static Func GetGetterDelegateForProperty(EdmProperty property) { return property.ValueGetter ?? (property.ValueGetter = CreatePropertyGetter(property.EntityDeclaringType, property.PropertyGetterHandle)); } /// for an OSpace property, set the property value on a clr instance /// /// If is null for a non nullable property. /// /// /// Invalid cast of to property type. /// /// /// From generated enties via StructuralObject.SetValidValue. /// /// /// If the property setter is not public or declaring class is not public. /// /// /// Demand for FullTrust if the property setter or declaring class has a /// internal static void SetValue(EdmProperty property, object target, object value) { Action setter = GetSetterDelegateForProperty(property); setter(target, value); } /// For an OSpace property, gets the delegate to set the property value on a clr instance. internal static Action GetSetterDelegateForProperty(EdmProperty property) { Action setter = property.ValueSetter; if (null == setter) { setter = CreatePropertySetter(property.EntityDeclaringType, property.PropertySetterHandle, property.Nullable); property.ValueSetter = setter; } Debug.Assert(null != setter, "null setter"); return setter; } /// /// Gets the related end instance for the source AssociationEndMember by creating a DynamicMethod to /// call GetRelatedCollection or GetRelatedReference /// internal static RelatedEnd GetRelatedEnd(RelationshipManager sourceRelationshipManager, AssociationEndMember sourceMember, AssociationEndMember targetMember, RelatedEnd existingRelatedEnd) { Func getRelatedEnd = sourceMember.GetRelatedEnd; if (null == getRelatedEnd) { getRelatedEnd = CreateGetRelatedEndMethod(sourceMember, targetMember); sourceMember.GetRelatedEnd = getRelatedEnd; } Debug.Assert(null != getRelatedEnd, "null getRelatedEnd"); return getRelatedEnd(sourceRelationshipManager, existingRelatedEnd); } #region Navigation Property internal static Action CreateNavigationPropertySetter(Type declaringType, PropertyInfo navigationProperty) { MethodInfo mi = navigationProperty.GetSetMethod(true); Type realType = navigationProperty.PropertyType; if (null == mi) { ThrowPropertyNoSetter(); } if (mi.IsStatic) { ThrowPropertyIsStatic(); } if (mi.DeclaringType.IsValueType) { ThrowPropertyDeclaringTypeIsValueType(); } // the setter always skips visibility so that we can call our internal method to handle errors // because CreateDynamicMethod asserts ReflectionPermission, method is "elevated" and must be treated carefully DynamicMethod method = CreateDynamicMethod(mi.Name, typeof(void), new Type[] { typeof(object), typeof(object) }); ILGenerator gen = method.GetILGenerator(); GenerateNecessaryPermissionDemands(gen, mi); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, declaringType); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Castclass, navigationProperty.PropertyType); gen.Emit(OpCodes.Callvirt, mi); // .Property = gen.Emit(OpCodes.Ret); return (Action)method.CreateDelegate(typeof(Action)); } #endregion #region get the delegate /// Gets a parameterless constructor for the specified type. /// Type to get constructor for. /// Parameterless constructor for the specified type. internal static ConstructorInfo GetConstructorForType(Type type) { System.Diagnostics.Debug.Assert(type != null); ConstructorInfo ci = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, System.Type.EmptyTypes, null); if (null == ci) { ThrowConstructorNoParameterless(type); } return ci; } /// /// generate a delegate equivalent to /// private object Constructor() { return new XClass(); } /// internal static Delegate CreateConstructor(Type type) { ConstructorInfo ci = GetConstructorForType(type); // because CreateDynamicMethod asserts ReflectionPermission, method is "elevated" and must be treated carefully DynamicMethod method = CreateDynamicMethod(ci.DeclaringType.Name, typeof(object), Type.EmptyTypes); ILGenerator gen = method.GetILGenerator(); GenerateNecessaryPermissionDemands(gen, ci); gen.Emit(OpCodes.Newobj, ci); gen.Emit(OpCodes.Ret); return method.CreateDelegate(typeof(Func)); } /// /// generate a delegate equivalent to /// private object MemberGetter(object target) { return target.PropertyX; } /// or if the property is Nullable<> generate a delegate equivalent to /// private object MemberGetter(object target) { Nullable y = target.PropertyX; return ((y.HasValue) ? y.Value : null); } /// private static Func CreatePropertyGetter(RuntimeTypeHandle entityDeclaringType, RuntimeMethodHandle rmh) { if (default(RuntimeMethodHandle).Equals(rmh)) { ThrowPropertyNoGetter(); } Debug.Assert(!default(RuntimeTypeHandle).Equals(entityDeclaringType), "Type handle of entity should always be known."); var mi = (MethodInfo)MethodBase.GetMethodFromHandle(rmh, entityDeclaringType); if (mi.IsStatic) { ThrowPropertyIsStatic(); } if (mi.DeclaringType.IsValueType) { ThrowPropertyDeclaringTypeIsValueType(); } if (0 != mi.GetParameters().Length) { ThrowPropertyIsIndexed(); } Type realType = mi.ReturnType; if ((null == realType) || (typeof(void) == realType)) { ThrowPropertyUnsupportedForm(); } if (realType.IsPointer) { ThrowPropertyUnsupportedType(); } // because CreateDynamicMethod asserts ReflectionPermission, method is "elevated" and must be treated carefully DynamicMethod method = CreateDynamicMethod(mi.Name, typeof(object), new Type[] { typeof(object) }); ILGenerator gen = method.GetILGenerator(); GenerateNecessaryPermissionDemands(gen, mi); // the 'this' target pointer gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, mi.DeclaringType); gen.Emit(mi.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, mi); if (realType.IsValueType) { Type elementType; if (realType.IsGenericType && (typeof(Nullable<>) == realType.GetGenericTypeDefinition())) { elementType = realType.GetGenericArguments()[0]; Label lableFalse = gen.DefineLabel(); LocalBuilder local = gen.DeclareLocal(realType); gen.Emit(OpCodes.Stloc_S, local); gen.Emit(OpCodes.Ldloca_S, local); gen.Emit(OpCodes.Call, realType.GetMethod("get_HasValue")); gen.Emit(OpCodes.Brfalse_S, lableFalse); gen.Emit(OpCodes.Ldloca_S, local); gen.Emit(OpCodes.Call, realType.GetMethod("get_Value")); gen.Emit(OpCodes.Box, elementType = realType.GetGenericArguments()[0]); gen.Emit(OpCodes.Ret); gen.MarkLabel(lableFalse); gen.Emit(OpCodes.Ldnull); } else { // need to box to return value as object elementType = realType; gen.Emit(OpCodes.Box, elementType); } } gen.Emit(OpCodes.Ret); return (Func)method.CreateDelegate(typeof(Func)); } /// /// generate a delegate equivalent to /// /// // if Property is Nullable value type /// private void MemberSetter(object target, object value) { /// if (AllwNull && (null == value)) { /// ((TargetType)target).PropertyName = default(PropertyType?); /// return; /// } /// if (value is PropertyType) { /// ((TargetType)target).PropertyName = new (PropertyType?)((PropertyType)value); /// return; /// } /// ThrowInvalidValue(value, TargetType.Name, PropertyName); /// return /// } /// /// // when PropertyType is a value type /// private void MemberSetter(object target, object value) { /// if (value is PropertyType) { /// ((TargetType)target).PropertyName = (PropertyType)value; /// return; /// } /// ThrowInvalidValue(value, TargetType.Name, PropertyName); /// return /// } /// /// // when PropertyType is a reference type /// private void MemberSetter(object target, object value) { /// if ((AllwNull && (null == value)) || (value is PropertyType)) { /// ((TargetType)target).PropertyName = ((PropertyType)value); /// return; /// } /// ThrowInvalidValue(value, TargetType.Name, PropertyName); /// return /// } /// /// /// If the method is missing or static or has indexed parameters. /// Or if the delcaring type is a value type. /// Or if the parameter type is a pointer. /// Or if the method or declaring class has a . /// private static Action CreatePropertySetter(RuntimeTypeHandle entityDeclaringType, RuntimeMethodHandle rmh, bool allowNull) { MethodInfo mi; Type realType; ValidateSetterProperty(entityDeclaringType, rmh, out mi, out realType); // the setter always skips visibility so that we can call our internal method to handle errors // because CreateDynamicMethod asserts ReflectionPermission, method is "elevated" and must be treated carefully DynamicMethod method = CreateDynamicMethod(mi.Name, typeof(void), new Type[] { typeof(object), typeof(object) }); ILGenerator gen = method.GetILGenerator(); GenerateNecessaryPermissionDemands(gen, mi); Type elementType = realType; Label labelContinueNull = gen.DefineLabel(); Label labelContinueValue = gen.DefineLabel(); Label labelInvalidValue = gen.DefineLabel(); if (realType.IsValueType) { if (realType.IsGenericType && (typeof(Nullable<>) == realType.GetGenericTypeDefinition())) { elementType = realType.GetGenericArguments()[0]; } else { // force allowNull false for non-nullable value types allowNull = false; } } // ((TargetType)instance) gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, mi.DeclaringType); // if (value is elementType) { gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Isinst, elementType); if (allowNull) { // reference type or nullable type gen.Emit(OpCodes.Ldarg_1); if (elementType == realType) { gen.Emit(OpCodes.Brfalse_S, labelContinueNull); // if (null == } else { gen.Emit(OpCodes.Brtrue, labelContinueValue); gen.Emit(OpCodes.Pop); // pop Isinst LocalBuilder local = gen.DeclareLocal(realType); gen.Emit(OpCodes.Ldloca_S, local); // load valuetype& gen.Emit(OpCodes.Initobj, realType); // init & gen.Emit(OpCodes.Ldloc_0); // load valuetype gen.Emit(OpCodes.Br_S, labelContinueNull); gen.MarkLabel(labelContinueValue); } } gen.Emit(OpCodes.Dup); gen.Emit(OpCodes.Brfalse_S, labelInvalidValue); // (arg1 is Inst) if (elementType.IsValueType) { gen.Emit(OpCodes.Unbox_Any, elementType); // ((PropertyType)value) if (elementType != realType) { // new Nullable gen.Emit(OpCodes.Newobj, realType.GetConstructor(new Type[] { elementType })); } } gen.MarkLabel(labelContinueNull); gen.Emit(mi.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, mi); // .Property = gen.Emit(OpCodes.Ret); // ThrowInvalidValue(value, typeof(PropertyType), DeclaringType.Name, PropertyName gen.MarkLabel(labelInvalidValue); gen.Emit(OpCodes.Pop); // pop Ldarg_0 gen.Emit(OpCodes.Pop); // pop IsInst' gen.Emit(OpCodes.Ldarg_1); // determine if InvalidCast or NullReference gen.Emit(OpCodes.Ldtoken, elementType); gen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Static | BindingFlags.Public)); gen.Emit(OpCodes.Ldstr, mi.DeclaringType.Name); gen.Emit(OpCodes.Ldstr, mi.Name.Substring(4)); // substring to strip "set_" Debug.Assert(null != (Action)EntityUtil.ThrowSetInvalidValue, "missing method ThrowSetInvalidValue(object,Type,string,string)"); gen.Emit(OpCodes.Call, typeof(EntityUtil).GetMethod("ThrowSetInvalidValue", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(object),typeof(Type),typeof(string),typeof(string)},null)); gen.Emit(OpCodes.Ret); return (Action)method.CreateDelegate(typeof(Action)); } internal static void ValidateSetterProperty(RuntimeTypeHandle entityDeclaringType, RuntimeMethodHandle setterMethodHandle, out MethodInfo setterMethodInfo, out Type realType) { if (default(RuntimeMethodHandle).Equals(setterMethodHandle)) { ThrowPropertyNoSetter(); } Debug.Assert(!default(RuntimeTypeHandle).Equals(entityDeclaringType), "Type handle of entity should always be known."); setterMethodInfo = (MethodInfo)MethodBase.GetMethodFromHandle(setterMethodHandle, entityDeclaringType); if (setterMethodInfo.IsStatic) { ThrowPropertyIsStatic(); } if (setterMethodInfo.DeclaringType.IsValueType) { ThrowPropertyDeclaringTypeIsValueType(); } ParameterInfo[] parameters = setterMethodInfo.GetParameters(); if ((null == parameters) || (1 != parameters.Length)) { // if no parameters (i.e. not a set_Property method), will still throw this message ThrowPropertyIsIndexed(); } realType = setterMethodInfo.ReturnType; if ((null != realType) && (typeof(void) != realType)) { ThrowPropertyUnsupportedForm(); } realType = parameters[0].ParameterType; if (realType.IsPointer) { ThrowPropertyUnsupportedType(); } } /// Determines if the specified method requires permission demands to be invoked safely. /// Method instance to check. /// true if the specified method requires permission demands to be invoked safely, false otherwise. internal static bool RequiresPermissionDemands(MethodBase mi) { System.Diagnostics.Debug.Assert(mi != null); return !IsPublic(mi); } private static void GenerateNecessaryPermissionDemands(ILGenerator gen, MethodBase mi) { if (!IsPublic(mi)) { gen.Emit(OpCodes.Ldsfld, typeof(LightweightCodeGenerator).GetField("MemberAccessReflectionPermission", BindingFlags.Static | BindingFlags.NonPublic)); gen.Emit(OpCodes.Callvirt, typeof(ReflectionPermission).GetMethod("Demand")); } } internal static bool IsPublic(MethodBase method) { return (method.IsPublic && IsPublic(method.DeclaringType)); } internal static bool IsPublic(Type type) { return ((null == type) || (type.IsPublic && IsPublic(type.DeclaringType))); } /// /// Create delegate used to invoke either the GetRelatedReference or GetRelatedCollection generic method on the RelationshipManager. /// /// source end of the relationship for the requested navigation /// target end of the relationship for the requested navigation /// Delegate that can be used to invoke the corresponding method. [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static Func CreateGetRelatedEndMethod(AssociationEndMember sourceMember, AssociationEndMember targetMember) { Debug.Assert(sourceMember.DeclaringType == targetMember.DeclaringType, "Source and Target members must be in the same DeclaringType"); EntityType sourceEntityType = MetadataHelper.GetEntityTypeForEnd(sourceMember); EntityType targetEntityType = MetadataHelper.GetEntityTypeForEnd(targetMember); NavigationPropertyAccessor sourceAccessor = MetadataHelper.GetNavigationPropertyAccessor(targetEntityType, targetMember, sourceMember); NavigationPropertyAccessor targetAccessor = MetadataHelper.GetNavigationPropertyAccessor(sourceEntityType, sourceMember, targetMember); MethodInfo genericCreateRelatedEndMethod = typeof(LightweightCodeGenerator).GetMethod("CreateGetRelatedEndMethod", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(AssociationEndMember), typeof(AssociationEndMember), typeof(NavigationPropertyAccessor), typeof(NavigationPropertyAccessor) }, null); Debug.Assert(genericCreateRelatedEndMethod != null, "Could not find method LightweightCodeGenerator.CreateGetRelatedEndMethod"); MethodInfo createRelatedEndMethod = genericCreateRelatedEndMethod.MakeGenericMethod(sourceEntityType.ClrType, targetEntityType.ClrType); object getRelatedEndDelegate = createRelatedEndMethod.Invoke(null, new object[] { sourceMember, targetMember, sourceAccessor, targetAccessor }); return (Func)getRelatedEndDelegate; } private static Func CreateGetRelatedEndMethod(AssociationEndMember sourceMember, AssociationEndMember targetMember, NavigationPropertyAccessor sourceAccessor, NavigationPropertyAccessor targetAccessor) where TSource : class where TTarget : class { Func getRelatedEnd; // Get the appropriate method, either collection or reference depending on the target multiplicity switch (targetMember.RelationshipMultiplicity) { case RelationshipMultiplicity.ZeroOrOne: case RelationshipMultiplicity.One: { getRelatedEnd = (manager, relatedEnd) => manager.GetRelatedReference(sourceMember.DeclaringType.FullName, sourceMember.Name, targetMember.Name, sourceAccessor, targetAccessor, sourceMember.RelationshipMultiplicity, relatedEnd); break; } case RelationshipMultiplicity.Many: { getRelatedEnd = (manager, relatedEnd) => manager.GetRelatedCollection(sourceMember.DeclaringType.FullName, sourceMember.Name, targetMember.Name, sourceAccessor, targetAccessor, sourceMember.RelationshipMultiplicity, relatedEnd); break; } default: throw EntityUtil.InvalidEnumerationValue(typeof(RelationshipMultiplicity), (int)targetMember.RelationshipMultiplicity); } return getRelatedEnd; } private static void ThrowConstructorNoParameterless(Type type) { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_ConstructorNoParameterless(type.FullName)); } private static void ThrowPropertyDeclaringTypeIsValueType() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyDeclaringTypeIsValueType); } private static void ThrowPropertyUnsupportedForm() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyUnsupportedForm); } private static void ThrowPropertyUnsupportedType() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyUnsupportedType); } private static void ThrowPropertyStrongNameIdentity() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyStrongNameIdentity); } private static void ThrowPropertyIsIndexed() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyIsIndexed); } private static void ThrowPropertyIsStatic() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyIsStatic); } private static void ThrowPropertyNoGetter() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyNoGetter); } private static void ThrowPropertyNoSetter() { throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.CodeGen_PropertyNoSetter); } #endregion #region Lightweight code generation internal static readonly ReflectionPermission MemberAccessReflectionPermission = new ReflectionPermission(ReflectionPermissionFlag.MemberAccess); internal static bool HasMemberAccessReflectionPermission() { try { MemberAccessReflectionPermission.Demand(); return true; } catch (SecurityException) { return false; } } // we could cache more, like 'new Type[] { ... }' and 'typeof(object)' // but pruned as much as possible for the workingset helps, even little things // Assert MemberAccess to skip visibility check & ReflectionEmit so we can generate the method (make calls to EF internals). [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2128")] [System.Security.SecuritySafeCritical] [ReflectionPermission(SecurityAction.Assert, MemberAccess = true)] internal static DynamicMethod CreateDynamicMethod(string name, Type returnType, Type[] parameterTypes) { // Create a transparent dynamic method (Module not specified) to ensure we do not satisfy any link demands // in method callees. return new DynamicMethod(name, returnType, parameterTypes, true); } #endregion } }