Unboxing Void

Previously we have show how we can obtain an instance of System.Void, despite some effort by the .NET team to disallow that. However the API we used returned an object which led to the boxing of the void instance. Having a box of nothing is not the same as having nothing, so it seems like we must take an alternative route so that we can finally hold an unboxed void.

As discussed last time, unboxed value types usually exist in the following memory locations:

  • On the stack
  • As part of other types
  • As elements in an array

The first 2 are not possible in C# as the compiler disallows us from referencing System.Void, citing error CS0673. However C# is just a higher level language which compiles into CIL (Common Intermediate Language). Is the restriction on referencing System.Void just a high-level restriction which is ignored at lower levels?

Since I am not too well-versed in CIL, my strategy is to write programs in C#, then disassemble them into IL using ildasm lib.dll /OUT=lib.il. I will then use a text editor to make changes to the IL then re-assemble the DLL back using ilasm /DLL lib.il. Using ildasm and ilasm is as easy as launching the Developer Command Prompt.

We start with the following program.

struct Temp { }
static void Main()
{
    var myObject = new Temp();
    Console.WriteLine(myObject);
}

Our goal is to modify the IL such that instead of creating a new Temp(), we create a new System.Void. Using the process explained earlier, we write the following CIL.

.method private hidebysig static void  Main() cil managed
{
    .entrypoint
    // Code size       25 (0x19)
    .maxstack  1
    .locals init (valuetype [System.Runtime]System.Void V_0)
    IL_0000:  ldloca.s   V_0
    IL_0002:  initobj    [System.Runtime]System.Void
    IL_0008:  ldloc.0
    IL_0009:  box        [System.Runtime]System.Void
    IL_000e:  call       instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
    IL_0013:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0018:  ret
} // end of method Program::Main

One ildasm later and voila; "Operation completed successfully". We run the program and...

Unhandled exception. System.InvalidProgramException: Common Language Runtime detected an invalid program.
at AbusingVoid.Program.Main(String[] args)

So it seems that C# is just being nice and stopping us from creating invalid programs. It seems like a long shot, but we can try a similar operation with System.Void as a field within a class.

Void as a field

The following C# program has a class Holder which contains a single field of type Temp. The object is constructed then sent off for all it's members to be printed to the console.

struct Temp { }
class Holder { public Temp value; }
static void Main()
{
    var myObject = new Holder();
    PrintObject(myObject);
}

static void PrintObject(Holder h)
{
    var members = h.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
    foreach (var member in members)
    {
        var value = member.GetValue(h);
        Console.WriteLine($"{member.Name}: {value}");
    }
}

This time we will change the class definition of Holder to the following.

.class auto ansi nested private beforefieldinit Holder
        extends [System.Runtime]System.Object
{
    .field public valuetype [System.Runtime]System.Void 'value'
    
    // [ Constructor code removed for brevity ]

} // end of class Holder

We execute the program and...

Hello!
value: System.Void

Fantastic! It seems like we have a class which contains a field of type System.Void. We can't use the field directly through code as the CLR throws System.InvalidProgramException as seen earlier, but we can inspect attributes of the resulting class.

It is important to draw a distinction between the Holder class and a boxed void; the "header" of a box is mostly a pointer to the System.Type which is being boxed (in our case, typeof(void)), while the void in Holder does not have this header - instead, it is the Holder class, which can contain many other fields, which has the reference to typeof(Holder). This also means we can add more fields to Holder, some of the of type Void. What would this look like? We explore this next time.


Other posts you might like


Join the Discussion

You must be signed in to comment.

No user information will be stored on our site until you comment.