TypedReference to ref T throws BadImageFormatException

322 views Asked by At

I have the following example code:

string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
Int32.TryParse(a, out __refvalue(tr, int));

It is supposed to put 1 into b. The problem is, it throws a BadImageFormatException: Bad class token. The problem is, of course, the out __refvalue(tr, int) expression. __refvalue gets compiled to the refanyval opcode, which is supposed to return the address stored in the typed reference. The out (the same applies to ref) keyword is then supposed to cast it to ref int and pass the (unchanged) reference to the TryParse method.

The problem is found in the IL:

.locals init (string V_0, int32 V_1, typedref V_2)
IL_0000:  ldstr      "1"
IL_0005:  stloc.0
IL_0006:  ldc.i4.0
IL_0007:  stloc.1
IL_0008:  ldloca.s   V_1
IL_000a:  mkrefany   [mscorlib]System.Int32
IL_000f:  stloc.2
IL_0010:  ldloc.0
IL_0011:  ldloc.2
IL_0012:  refanyval  0 
IL_0017:  call       bool [mscorlib]System.Int32::TryParse(string, int32&)
IL_001c:  pop

The problem is now obvious - refanyval 0. The opcode is supposed to take a type argument, so 0 is completely out of place, it should be refanyval [mscorlib]System.Int32.

Is this a bug in the compiler? Is there any way to bypass this bug? Thanks for your views.

Edit: So I have built a nice method that generates IL allowing conversion between TypedReference and ref T:

public static class ReferenceHelper
{
    public delegate TResult OutDelegate<TArg,TResult>(out TArg variable);
    public delegate TResult RefDelegate<TArg,TResult>(ref TArg variable);
    public delegate void OutDelegate<TArg>(out TArg variable);
    public delegate void RefDelegate<TArg>(ref TArg variable);

    static readonly AssemblyBuilder ab;
    static readonly ModuleBuilder mob;
    static readonly Type TypedReferenceType = typeof(TypedReference);

    static ReferenceHelper()
    {
        ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ReferenceHelperAssembly"), AssemblyBuilderAccess.Run);
        mob = ab.DefineDynamicModule("ReferenceHelperAssembly.dll");
    }

    public static TResult PassReference<TArg,TResult>(TypedReference tref, RefDelegate<TArg,TResult> del)
    {
        return ReferenceBuilder<TArg,TResult>.PassRef(tref, del);
    }

    public static void PassReference<TArg>(TypedReference tref, RefDelegate<TArg> del)
    {
        ReferenceBuilder<TArg>.PassRef(tref, del);
    }

    public static TResult PassReference<TArg,TResult>(TypedReference tref, OutDelegate<TArg,TResult> del)
    {
        return PassReference<TArg,TResult>(tref, delegate(ref TArg arg){return del(out arg);});
    }

    public static void PassReference<TArg>(TypedReference tref, OutDelegate<TArg> del)
    {
        PassReference<TArg>(tref, delegate(ref TArg arg){del(out arg);});
    }

    static int mcounter = 0;
    private static Type BuildPassRef(Type deltype, Type argType, Type resultType)
    {
        TypeBuilder tb = mob.DefineType("ReferenceHelperType"+(mcounter++), TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract);
        MethodBuilder mb = tb.DefineMethod(
            "PassRef",
            MethodAttributes.Public | MethodAttributes.Static,
            resultType,
            new[]{TypedReferenceType, deltype}
        );
        var il = mb.GetILGenerator();
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Refanyval, argType);
        il.Emit(OpCodes.Callvirt, deltype.GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        return tb.CreateType();
    }

    private static class ReferenceBuilder<TArg,TResult>
    {
        public delegate TResult PassRefDelegate(TypedReference tref, RefDelegate<TArg,TResult> del);
        public static readonly PassRefDelegate PassRef;

        static ReferenceBuilder()
        {
            Type t = BuildPassRef(typeof(RefDelegate<TArg,TResult>), typeof(TArg), typeof(TResult));
            PassRef = (PassRefDelegate)t.GetMethod("PassRef").CreateDelegate(typeof(PassRefDelegate));
        }
    }

    private static class ReferenceBuilder<TArg>
    {
        public delegate void PassRefDelegate(TypedReference tref, RefDelegate<TArg> del);
        public static readonly PassRefDelegate PassRef;

        static ReferenceBuilder()
        {
            Type t = BuildPassRef(typeof(RefDelegate<TArg>), typeof(TArg), typeof(void));
            PassRef = (PassRefDelegate)t.GetMethod("PassRef").CreateDelegate(typeof(PassRefDelegate));
        }
    }
}

Usage

string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
ReferenceHelper.PassReference(tr, delegate(out int val){return Int32.TryParse(a, out val);});
1

There are 1 answers

0
Jeroen Mostert On BEST ANSWER

The problem is out -- the compiler doesn't know how to compile out __refvalue properly (or ref __refvalue, for that matter), which is not surprising, since the feature __refvalue supports (varargs) doesn't need to combine with out. This is a bug in the sense that the compiler isn't supposed to emit invalid code; this is not a bug in the sense that if you use undocumented keywords, you're on your own (and MS is not interested in fixing bugs that are the result of using them, if the Connect tickets are any indication).

Any workaround will involve not using an out parameter, but whether this is possible depends on your scenario (you could write a wrapper around Int32.TryParse that returns a tuple instead or boxes the argument, for example, but of course this is a simplified instance). An even better workaround is not to use undocumented keywords -- you may have to bite the bullet and do dynamic IL generation (and then I can't imagine you need mkrefany/refanyval).