|
//---------------------------------------------------------------------
// <copyright file="LightweightCodeGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
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;
/// <summary>
/// CodeGenerator class: use lightweight code gen to dynamically generate code to get/set properties.
/// </summary>
internal static class LightweightCodeGenerator
{
/// <summary>For an OSpace ComplexType returns the delegate to construct the clr instance.</summary>
internal static Delegate GetConstructorDelegateForType(ClrComplexType clrType)
{
return (clrType.Constructor ?? (clrType.Constructor = CreateConstructor(clrType.ClrType)));
}
/// <summary>For an OSpace EntityType returns the delegate to construct the clr instance.</summary>
internal static Delegate GetConstructorDelegateForType(ClrEntityType clrType)
{
return (clrType.Constructor ?? (clrType.Constructor = CreateConstructor(clrType.ClrType)));
}
/// <summary>for an OSpace property, get the property value from a clr instance</summary>
internal static object GetValue(EdmProperty property, object target)
{
Func<object, object> getter = GetGetterDelegateForProperty(property);
Debug.Assert(null != getter, "null getter");
return getter(target);
}
internal static Func<object,object> GetGetterDelegateForProperty(EdmProperty property)
{
return property.ValueGetter ?? (property.ValueGetter = CreatePropertyGetter(property.EntityDeclaringType, property.PropertyGetterHandle));
}
/// <summary>for an OSpace property, set the property value on a clr instance</summary>
/// <exception cref="System.Data.ConstraintException">
/// If <paramref name="value"/> is null for a non nullable property.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Invalid cast of <paramref name="value"/> to property type.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// From generated enties via StructuralObject.SetValidValue.
/// </exception>
/// <permission cref="System.Security.Permissions.ReflectionPermission">
/// If the property setter is not public or declaring class is not public.
/// </permission>
/// <permission cref="System.Security.NamedPermissionSet">
/// Demand for FullTrust if the property setter or declaring class has a <see cref="System.Security.Permissions.SecurityAction.LinkDemand"/>
/// </permission>
internal static void SetValue(EdmProperty property, object target, object value)
{
Action<object, object> setter = GetSetterDelegateForProperty(property);
setter(target, value);
}
/// <summary>For an OSpace property, gets the delegate to set the property value on a clr instance.</summary>
internal static Action<object, object> GetSetterDelegateForProperty(EdmProperty property)
{
Action<object, object> 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;
}
/// <summary>
/// Gets the related end instance for the source AssociationEndMember by creating a DynamicMethod to
/// call GetRelatedCollection or GetRelatedReference
/// </summary>
internal static RelatedEnd GetRelatedEnd(RelationshipManager sourceRelationshipManager, AssociationEndMember sourceMember, AssociationEndMember targetMember, RelatedEnd existingRelatedEnd)
{
Func<RelationshipManager, RelatedEnd, RelatedEnd> 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<object, object> 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<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}
#endregion
#region get the delegate
/// <summary>Gets a parameterless constructor for the specified type.</summary>
/// <param name="type">Type to get constructor for.</param>
/// <returns>Parameterless constructor for the specified type.</returns>
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;
}
/// <summary>
/// generate a delegate equivalent to
/// private object Constructor() { return new XClass(); }
/// </summary>
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<object>));
}
/// <summary>
/// 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<X> y = target.PropertyX; return ((y.HasValue) ? y.Value : null); }
/// </summary>
private static Func<object, object> 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<object, object>)method.CreateDelegate(typeof(Func<object, object>));
}
/// <summary>
/// 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
/// }
/// </summary>
/// <exception cref="System.InvalidOperationException">
/// 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 <see cref="System.Security.Permissions.StrongNameIdentityPermissionAttribute"/>.
/// </exception>
private static Action<object, object> 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<PropertyType>
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<Object,Type,String,String>)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<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}
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();
}
}
/// <summary>Determines if the specified method requires permission demands to be invoked safely.</summary>
/// <param name="mi">Method instance to check.</param>
/// <returns>true if the specified method requires permission demands to be invoked safely, false otherwise.</returns>
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)));
}
/// <summary>
/// Create delegate used to invoke either the GetRelatedReference or GetRelatedCollection generic method on the RelationshipManager.
/// </summary>
/// <param name="sourceMember">source end of the relationship for the requested navigation</param>
/// <param name="targetMember">target end of the relationship for the requested navigation</param>
/// <returns>Delegate that can be used to invoke the corresponding method.</returns>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static Func<RelationshipManager, RelatedEnd, RelatedEnd> 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<RelationshipManager, RelatedEnd, RelatedEnd>)getRelatedEndDelegate;
}
private static Func<RelationshipManager, RelatedEnd, RelatedEnd> CreateGetRelatedEndMethod<TSource, TTarget>(AssociationEndMember sourceMember, AssociationEndMember targetMember, NavigationPropertyAccessor sourceAccessor, NavigationPropertyAccessor targetAccessor)
where TSource : class
where TTarget : class
{
Func<RelationshipManager, RelatedEnd, RelatedEnd> 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<TSource, TTarget>(sourceMember.DeclaringType.FullName,
sourceMember.Name,
targetMember.Name,
sourceAccessor,
targetAccessor,
sourceMember.RelationshipMultiplicity,
relatedEnd);
break;
}
case RelationshipMultiplicity.Many:
{
getRelatedEnd = (manager, relatedEnd) =>
manager.GetRelatedCollection<TSource, TTarget>(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
}
}
|