Why setting a read only field with dynamic method causes error in this class?
Asked Answered
G

1

7

One can store in a read only field of a class using strfld op code in dynamic method if it has its owner set to that class and JIT checks are turned off. An example is here. This approach, however, failed to work with the class that comes from F#, namely FSharpOption. Please analyse an example below:

using Microsoft.FSharp.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

#if true
using MyType = Microsoft.FSharp.Core.FSharpOption<string>;
#else
using MyType = System.Tuple<string>;
#endif

namespace ConsoleApplication27
{
    class Program
    {
        static void Main(string[] args)
        {
            var something = new MyType("test");

            var dynMethod = new DynamicMethod("ChangeField", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), new [] { typeof(MyType) }, typeof(MyType), true);
            var generator = dynMethod.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldstr, "success");
            generator.Emit(OpCodes.Stfld, typeof(MyType).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)[0]);
            generator.Emit(OpCodes.Ret);

            var method = (Action<MyType>) dynMethod.CreateDelegate(typeof(Action<MyType>));
            method(something);
            Console.WriteLine(typeof(MyType).GetProperties()[0].GetGetMethod().Invoke(something, new object[0]));
        }
    }
}

First of all, you have to reference FSharp.Core library to run it. Then, by changing #if true to #if false you can switch between writing a read only field fo Tuple and FSharpOption. For the former it works perfectly even though both have similar structure, that is a single read only field accessible via property. For the latter it causes verification failure. Why is that so?

Goldi answered 13/8, 2015 at 13:44 Comment(9)
Does changing the trust level change behavior?Forewent
@leppie: I don't really know how to do it, support.microsoft.com/en-us/kb/815147 is not helpful for me, since I don't have 'Microsoft .NET Framework Configuration' item at all.Goldi
Sorry, I get thrown off by that exception too ;p Nothing to do with visibility, see my answer :)Forewent
BTW, this works fine when referencing FSharp.Core v2.x . As soon as v4.x is referenced, the exception shows up. It is not logical to me why it should not work. Looking at the IL, they seem the same, but I will look more.Forewent
@leppie: yup. Moreover, setting the field's value using SetValue works perfectly as well.Goldi
@Goldi Isn't that associated with the possibility that F# could potentialy get rid of option instance (leaving only underlying value) as part of some optimization mechanism?Astrophysics
@Horusiath that would be on F# compiler level, not on ILPeanut
Something is strange with the FSharpOption, when decompiling it using dotPeek, the value field is grayed out in the member list and does not show up in the decompiled sources. unlike Tupe<t> where the backing field does show up..Peanut
@RogerAlsing: It is because it is marked with [CompilerGenerated], uncheck 'Hide compiler-generated code'Forewent
P
2

Very very late response. This is very odd. By replacing the MyType with the module of the field type. it all starts to work

var dynMethod = new DynamicMethod("ChangeField", MethodAttributes.Public | MethodAttributes.Static,
            CallingConventions.Standard, typeof(void), new[] {typeof(MyType)}, typeof(MyType), true);

Becomes:

var dynMethod = new DynamicMethod("ChangeField", MethodAttributes.Public | MethodAttributes.Static,
            CallingConventions.Standard, typeof(void), new[] {typeof(MyType)}, typeof(string).Module, true);

That is:

 typeof(MyType), true);

becomes

 typeof(string).Module, true);

The type of the field being set is string so we take the module of that. I would love to hear a better explanation on why this is.

The plot thickens

it turns out, that typeof(string).module makes it work for any type. even if I define my own type like this:

using System;
using System.Reflection;
using System.Reflection.Emit;
#if true
using MyType = Microsoft.FSharp.Core.FSharpOption<string>;
#else
using MyType = System.Tuple<ConsoleApplication27.Poco>;
#endif

namespace ConsoleApplication27
{
    public class Poco
    {

    }
    class Program
    {
        static void Main(string[] args)
        {
            var something = new MyType(null);

            var dynMethod = new DynamicMethod("ChangeField", MethodAttributes.Public | MethodAttributes.Static,
                CallingConventions.Standard, typeof(void), new[] {typeof(MyType)}, typeof(string).Module, true);
            var generator = dynMethod.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldnull);
            generator.Emit(OpCodes.Stfld,
                typeof(MyType).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)[0]);
            generator.Emit(OpCodes.Ret);

            var method = (Action<MyType>) dynMethod.CreateDelegate(typeof(Action<MyType>));
            method(something);
            Console.WriteLine(typeof(MyType).GetProperties()[0].GetGetMethod().Invoke(something, new object[0]));
        }
    }
}

Now nothing makes sense. the FSharpOption type lives in the F# module. The Poco class lives in my program module. and still it all works when the module passed is the corelib module. But not if I pass any of the two (above) that would make sense..

Peanut answered 30/7, 2016 at 8:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.