//---------------------------------------------------------------------
//
// 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
}
}