Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep a TypedReference alive out of method block without returning it

I want to premise that this question's purpose is checking if there's at least one way, even if through the most unsafe hack, to keep a reference to a non-blittable value type. I am aware that such a design type is comparable to a crime; I wouldn't use it in any practical case, other than learning. So please accept reading heretic unsafe code for now.

We know that a reference to a blittable type can be stored and increased this way:

unsafe class Foo
{
    void* _ptr;

    public void Fix(ref int value)
    {
        fixed (void* ptr = &value) _ptr = ptr;
    }

    public void Increment()
    {
        var pointer = (int*) _ptr;
        (*pointer)++;
    }
}

In terms of safety, the above class is comparable to a jump in the void (no pun intended), but it works, as already mentioned here. If a variable allocated on the stack is passed to it and then the caller method's scope terminates, you're likely to run into a bug or an explicit access violation error. However, if you execute a program like this:

static class Program
{
    static int _fieldValue = 42;

    public static void Main(string[] args)
    {
        var foo = new Foo();
        foo.Fix(ref _fieldValue);
        foo.Increment();
    }
}

The class won't be disposed until the relative application domain is unloaded, and so applies for the field. I honestly don't know if fields in the high-frequency heap can be reallocated but I personally think not. But let's put aside safety even more now (if even possible). After reading this and this questions I was wondering if there was a way to create a similar approach for non-blittable static types, so I made this program, which actually works. Read the comments to see what it does.

static class Program
{
    static Action _event;

    public static void Main(string[] args)
    {
        MakerefTest(ref _event);
        //The invocation list is empty again
        var isEmpty = _event == null;
    }

    static void MakerefTest(ref Action multicast)
    {
        Action handler = () => Console.WriteLine("Hello world.");
        //Assigning a handler to the delegate
        multicast += handler;
        //Executing the delegate's invocation list successfully
        if (multicast != null) multicast();
        //Encapsulating the reference in a TypedReference
        var tr = __makeref(multicast);
        //Removing the handler
        __refvalue(tr, Action) -= handler;
    }
}

The actual problem/opportunity:

We know that the compiler won't let us store a value passed by ref, but the __makeref keyword, as much undocumented and unadvised, offers the possibility of encapsulating and restoring a reference to blittable types. However, the return value of __makeref, TypedReference, is well protected. You can't store it in a field, you can't box it, you can't create an array of it, you can't use it in anonymous methods or lambdas. All that I managed to do was modifying the above code as follows:

static void* _ptr;

static void MakerefTest(ref Action multicast)
{
    Action handler = () => Console.WriteLine("Hello world.");
    multicast += handler;
    if (multicast != null) multicast();
    var tr = __makeref(multicast);
    //Storing the address of the TypedReference (which is on the stack!)
    //inside of _ptr;
    _ptr = (void*) &tr;
    //Getting the TypedReference back from the pointer:
    var restoredTr = *(TypedReference*) _ptr;
    __refvalue(restoredTr, Action) -= handler;
}

The above code works just as well and looks even worse than before but for the sake of knowledge, I wanted to do more with it, so I wrote the following:

unsafe class Horror
{
    void* _ptr;

    static void Handler()
    {
        Console.WriteLine("Hello world.");
    }

    public void Fix(ref Action action)
    {
        action += Handler;
        var tr = __makeref(action);
        _ptr = (void*) &tr;
    }

    public void Clear()
    {
        var tr = *(TypedReference*) _ptr;
        __refvalue(tr, Action) -= Handler;
    }
}

The Horror class is a combination of the Foo class and the above method, but as you'll surely notice, it has one big problem. In the method Fix, the TypedReference tr is declared, its address is copied inside of the generic pointer _ptr, then the method ends and tr no longer exists. When the Clear method is called, the "new" tr is corrupted because _ptr points to an area of the stack which is no longer a TypedReference. So here comes the question:

Is there any way to fool the compiler into keeping a TypedReference instance alive for an undetermined amount of time?

Any way to achieve the desired result will be considered good, even if it involves ugly, unsafe, slow code. A class implementing the following interface would be ideal:

interface IRefStorage<T> : IDisposable
{
    void Store(ref T value);
    //IDisposable.Dispose should release the reference
}

Please don't judge the question as generic discussion because its purpose is to see if, after all, there is a way to store references to blittable types, as wicked as it may be.

One last remark, I'm aware of the possibilities to bind fields through FieldInfo, but it seemed to me that the latter method didn't support types deriving from Delegate very much.

A possible solution (bounty result)

I would've marked AbdElRaheim's answer as chosen as soon as he edited his post to include the solution which he provided in his comment, but I suppose it wasn't very clear. Either way, among the techniques he provided, the one summed up in the following class (which I modified slightly) seemed the most "reliable" (ironic to use that term, since we're talking about exploiting a hack):

unsafe class Horror : IDisposable
{
    void* _ptr;

    static void Handler()
    {
        Console.WriteLine("Hello world.");
    }

    public void Fix(ref Action action)
    {
        action += Handler;
        TypedReference tr = __makeref(action);
        var mem = Marshal.AllocHGlobal(sizeof (TypedReference)); //magic
        var refPtr = (TypedReference*) mem.ToPointer();
        _ptr = refPtr;
        *refPtr = tr;
    }

    public void Dispose()
    {
        var tr = *(TypedReference*)_ptr;
        __refvalue(tr, Action) -= Handler;
        Marshal.FreeHGlobal((IntPtr)_ptr);
    }
}

What Fix does is, starting from the line marked as "magic" in the comment:

  1. Allocates memory in the process -- In the unmanaged part of it.
  2. Declares refPtr as a pointer to a TypedReference and sets it value to the pointer of the memory region allocated above. This is done, instead of using _ptr directly, because a field with type TypedReference* would throw an exception.
  3. Implicitly casts refPtr to void* and assigns the pointer to _ptr.
  4. Sets tr as the value pointed by refPtr and consequently _ptr.

He also offered another solution, the one he originally wrote as an answer, but it seemed less reliable than the one above. On the other hand, there was also another solution provided by Peter Wishart, but it required accurate synchronization and each Horror instance would've "wasted" a thread. I'll take the chance to repeat that the above approach is in no way intended for real world usage, it was just an academic question. I hope it will be helpful for anyone reading this question.

like image 878
Mir Avatar asked Jan 05 '13 02:01

Mir


1 Answers

You can also achieve this without using unmanaged memory, by creating a "fake" type that resembles typed reference in its structure:

unsafe class Horror
{
    FakeTypedRef ftr;

    static void Handler()
    {
        Console.WriteLine("Hello void.");
    }

    public void Fix(ref Action action)
    {
        action += Handler;
        TypedReference tr = __makeref(action);
        ftr = *(FakeTypedRef*)(&tr);
    }

    public void Clear()
    {
        fixed(FakeTypedRef* ptr = &ftr)
        {
            var tr = *(TypedReference*)ptr;
            __refvalue(tr, Action) -= Handler;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    struct FakeTypedRef
    {
        public IntPtr Value;
        public IntPtr Type;
    }
}

Important Edit: I strongly advise against passing any reference around as a pointer. The GC is allowed to freely move objects on the managed heap as it sees fit, and there is no guarantee that the pointer will stay valid not even after it is returned from the method. You may not see the immediate effect of this due to debug, but you are unleashing all sorts of problems by this.

If you really need to handle it as a pointer (and there may be some legitimate reasons), you need to emit a custom CIL with a pinned reference. It could even be initialized by extracting the pointer from a TypedReference, but it guarantees that the location will not change. Pass it to a lambda method then.

like image 112
IS4 Avatar answered Nov 15 '22 12:11

IS4