Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mutate a boxed struct using IL

Imagine we have a mutable struct (yes, don't start):

public struct MutableStruct
{
    public int Foo { get; set; }
    public override string ToString()
    {
        return Foo.ToString();
    }
}

Using reflection, we can take a boxed instance of this struct and mutate it inside the box:

// this is basically what we want to emulate
object obj = new MutableStruct { Foo = 123 };
obj.GetType().GetProperty("Foo").SetValue(obj, 456);
System.Console.WriteLine(obj); // "456"

What I would like to do is to write some IL that can do the same as this - but faster. I'm a meta-programming junkie ;p

It is trivial to unbox-any the value and mutate the value using regular IL - but you can't just call box it afterwards because that will create a different box. I'm guessing that what we would need to do here is copy it over the existing box. I have investigated ldobj / stobj, but those don't seem to do the job (unless I'm missing something).

So: does a mechanism to do this exist? Or must I limit myself to reflection to perform in-place updates of boxed structs ?

Or in other words: what ... evil goes here... ?

var method = new DynamicMethod("evil", null,
    new[] { typeof(object), typeof(object) });
var il = method.GetILGenerator();
// ... evil goes here...
il.Emit(OpCodes.Ret);

Action<object, object> action = (Action<object, object>)
    method.CreateDelegate(typeof(Action<object, object>));
action(obj, 789);
System.Console.WriteLine(obj); // "789"
like image 374
Marc Gravell Avatar asked Sep 21 '13 21:09

Marc Gravell


4 Answers

Well, that was fun.

Using Ldflda and Stind_* seems to work. Actually, it's mostly Unbox (see history for version that works with Ldflda and Stind_*).

Here's what I hacked together in LinqPad to prove it out.

public struct MutableStruct
{
    public int Foo { get; set; }

    public override string ToString()
    {
        return Foo.ToString();
    }
}

void Main()
{
    var foo = typeof(MutableStruct).GetProperty("Foo");
    var setFoo = foo.SetMethod;

    var dynMtd = new DynamicMethod("Evil", typeof(void), new [] { typeof(object), typeof(int) });
    var il = dynMtd.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);                       // object
    il.Emit(OpCodes.Unbox, typeof(MutableStruct));  // MutableStruct&
    il.Emit(OpCodes.Ldarg_1);                       // MutableStruct& int
    il.Emit(OpCodes.Call, setFoo);                  // --empty--
    il.Emit(OpCodes.Ret);                           // --empty--

    var del = (Action<object, int>)dynMtd.CreateDelegate(typeof(Action<object, int>));

    var mut = new MutableStruct { Foo = 123 };

    var boxed= (object)mut;

    del(boxed, 456);

    var unboxed = (MutableStruct)boxed;
    // unboxed.Foo = 456, mut.Foo = 123
}
like image 121
Kevin Montrose Avatar answered Nov 20 '22 07:11

Kevin Montrose


Here you go:

Just use unsafe :)

static void Main(string[] args)
{
  object foo = new MutableStruct {Foo = 123};
  Console.WriteLine(foo);
  Bar(foo);
  Console.WriteLine(foo);
}

static unsafe void Bar(object foo)
{
  GCHandle h = GCHandle.Alloc(foo, GCHandleType.Pinned);

  MutableStruct* fp = (MutableStruct*)(void*)  h.AddrOfPinnedObject();

  fp->Foo = 789;
}

IL implementation is left as an exercise to the reader.

Update:

Based on the Kevin's answer, here is a minimal working example:

ldarg.0
unbox      MutableStruct
ldarg.1
call       instance void MutableStruct::set_Foo(int32)
ret
like image 32
leppie Avatar answered Nov 20 '22 09:11

leppie


You can do this even easier. Try this under .NET 4.5 where we have dynamic.

struct Test
{
    public Int32 Number { get; set; }


    public override string ToString()
    {
        return this.Number.ToString();
    }
}


class Program
{
    static void Main( string[] args )
    {
        Object test = new Test();

        dynamic proxy = test;

        proxy.Number = 1;

        Console.WriteLine( test );
        Console.ReadLine();
    }
}

I know it's not reflection but still fun though.

like image 2
Ivan Zlatanov Avatar answered Nov 20 '22 07:11

Ivan Zlatanov


Even without unsafe code, pure C#:

using System;

internal interface I {
  void Increment();
}

struct S : I {
  public readonly int Value;
  public S(int value) { Value = value; }

  public void Increment() {
    this = new S(Value + 1); // pure evil :O
  }

  public override string ToString() {
    return Value.ToString();
  }
}

class Program {
  static void Main() {
    object s = new S(123);
    ((I) s).Increment();
    Console.WriteLine(s); // prints 124
  }
}

In C#, this reference inside value types instance methods actually is ref-parameter (or out-parameter in value type constructor, and that is why this can't be captured into closures, just like ref/out parameters in any methods) and can be modified.

When struct instance method is invoked on unboxed value, this assignment will effectively replace value at the call site. When instance method is invoked on boxed instance (via virtual call or interface call like in the example above), ref-parameter is pointed to the value inside the box object, so it is possible to modify boxed value.

like image 1
controlflow Avatar answered Nov 20 '22 08:11

controlflow