Create a DynamicMethod to set the readonly field of any type

My goal is to create a runtime delegate that can set any field (including readonly) in any reference type to a user-defined value. Unfortunately, my current implementation throws VerificationExceptionat runtime when assemblies containing types indicate an attribute [AllowPartiallyTrustedCallers].

AssemblyOne:

[assembly: AllowPartiallyTrustedCallers]
public class TypeOne
{
    public TypeOne(TypeTwo typeTwoField)
    {
        this.TypeTwoField = typeTwoField;
    }

    public TypeTwo TypeTwoField { get; }
}

AssemblyTwo:

[assembly: AllowPartiallyTrustedCallers]
public class TypeTwo
{
    public TypeTwo(int i)
    {
        this.Int = i;
    }

    public int Int { get; }
}

Main:

using System;
using System.Reflection;
using System.Reflection.Emit;
using AssemblyOne;
using AssemblyTwo;

namespace Main
{
    class Program
    {
        public class MyType
        {
            public MyType(TypeOne typeOneField)
            {
                this.TypeOneField = typeOneField;
            }

            public TypeOne TypeOneField { get; }
        }

        static void Main(string[] args)
        {
            var fieldInfo = typeof(TypeOne)
                .GetTypeInfo()
                .GetField(
                    "<TypeTwoField>k__BackingField",
                    BindingFlags.Instance | BindingFlags.NonPublic |
                    BindingFlags.Public);
            var setter = (Action<TypeOne, TypeTwo>) GetReferenceSetter(fieldInfo);
            var myType = new MyType(new TypeOne(new TypeTwo(1)));

            // Throws VerificationException
            setter(myType.TypeOneField, new TypeTwo(2));
        }

        public static Delegate GetReferenceSetter(FieldInfo field)
        {
            var delegateType = typeof(Action<,>)
                .MakeGenericType(field.DeclaringType, field.FieldType);

            var method = new DynamicMethod(
                field.Name + "Set",
                null,
                new[] {field.DeclaringType, field.FieldType},
                field.DeclaringType,
                skipVisibility: true);

            var emitter = method.GetILGenerator();
            emitter.Emit(OpCodes.Ldarg_0);
            emitter.Emit(OpCodes.Ldarg_1);
            emitter.Emit(OpCodes.Stfld, field);
            emitter.Emit(OpCodes.Ret);

            return method.CreateDelegate(delegateType);
        }
    }
}

So it MyTypehas TypeOne, which has readonly TypeTwo. In this case, DynamicMethodreturns a VerificationExceptionat run time.

Is it possible to create a delegate that works for any type of declaration of type + that you throw on it? If so, how?

I understand that fields readonlyshould never be set after construction, but the purpose of this is deserialization and deep copying.

+4
2

. , AssemblyBuilder , .

, , , TypedReference.

public static unsafe void SetValue<T>(object inst, FieldInfo fi, T val)
{
    var mi = typeof(TypedReference).GetMethod("InternalMakeTypedReference", BindingFlags.NonPublic | BindingFlags.Static);
    var sig = MethodSignature.FromMethodInfo(mi);
    var del = ReflectionTools.NewCustomDelegateType(sig.ReturnType, sig.ParameterTypes);
    var inv = mi.CreateDelegate(del);
    TypedReference tr;
    var ptr = Pointer.Box(&tr, typeof(void*));
    inv.DynamicInvoke(ptr, inst, new[]{fi.FieldHandle.Value}, fi.FieldType);
    __refvalue(tr, T) = val;
}

FromMethodInfo NewCustomDelegateType SharpUtils InternalMakeTypedReference, . :

private unsafe static extern void InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType);

, ( - ). - RuntimeType. , DynamicInvoke , .

, , , , :

[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1, SkipVerificationInFullTrust=true)]

, . .

+1

, .

/// <summary>
    /// create a getter delegate for a static field.
    /// </summary>
    /// <typeparam name="TField"></typeparam>
    /// <param name="staticType">the type that contains the field. </param>
    /// <param name="fieldName">the field that you want to get the value.</param>
    /// <returns>the getter delegate.</returns>
    public static Func<TField> CreateStaticFieldGetter<TField>(Type staticType, string fieldName)
    {
        var fieldInfo = staticType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
        var body = Expression.Field(null, fieldInfo);
        Expression<Func<TField>> lambda = Expression.Lambda<Func<TField>>(body);
        return lambda.Compile();
    }


    public static Action<TField> CreateStaticFieldSetter<TField>(Type staticType, string fieldName)
    {
        ParameterExpression p1 = Expression.Parameter(typeof(TField), "p1");
        var fieldInfo = staticType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
        var leftExpr = Expression.Field(null, fieldInfo);
        var body = Expression.Assign(leftExpr, p1);
        Expression<Action<TField>> lambda = Expression.Lambda<Action<TField>>(body, p1);
        return lambda.Compile();
    }



    public static Func<TTarget, TField> CreateInstanceFieldGetter<TTarget, TField>(string fieldName)
    {
        ParameterExpression p1 = Expression.Parameter(typeof(TTarget), "p1");
        var fieldInfo = typeof(TTarget).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        var body = Expression.Field(Expression.Convert(p1, typeof(TTarget)), fieldInfo);
        Expression<Func<TTarget, TField>> lambda = Expression.Lambda<Func<TTarget, TField>>(body, p1);
        return lambda.Compile();
    }



    public static Action<TTarget, TProperty> CreateInstanceFieldSetter<TTarget, TProperty>(string filedName)
    {
        ParameterExpression p1 = Expression.Parameter(typeof(TTarget), "p1");
        ParameterExpression p2 = Expression.Parameter(typeof(TProperty), "p2");

        var member = typeof(TTarget).GetField(filedName, BindingFlags.NonPublic | BindingFlags.Instance);
        var m1 = Expression.MakeMemberAccess(Expression.Convert(p1, typeof(TTarget)), member);
        BinaryExpression body = Expression.Assign(m1, p2);

        Expression<Action<TTarget, TProperty>> lambda = Expression.Lambda<Action<TTarget, TProperty>>(body, p1, p2);
        return lambda.Compile();
    }
0

Source: https://habr.com/ru/post/1675748/


All Articles