Search code examples
.netcil

Why is this .NET IL null check not working as expected?


I'm writing some custom IL and need something equivalent to return SomeStaticField != null;. This was my natural conclusion:

volatile.ldsfld ...SomeStaticField //volatile is needed here for unrelated reasons
ldnull
ceq
not
ret

However, this doesn't seem to work. I confirmed that SomeStaticField is null, but yet this function will end up returning true. I know C# uses branches for such a construct, and I could use it too, but it baffles me as to why this wouldn't have the expected behavior

A complete and verifiable example (as a library):

.assembly extern /*23000001*/ mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly 'BareMetal'
{
  .custom instance void class [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::'.ctor'() =  (
                01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01       ) // ceptionThrows.

  .hash algorithm 0x00008004
  .ver  1:0:0:0
}
.module BareMetal.dll 


.namespace Earlz.BareMetal
{
  .class public auto ansi abstract sealed beforefieldinit BareMetal
        extends [mscorlib]System.Object
  {
    .method public static hidebysig
           default bool FooTest ()  cil managed
    {
        .maxstack 2
        ldnull
        ldnull
        ceq
        not
        ret
    }

  }
}

Solution

  • not computes the bitwise complement of its operand: ~1 is -2, which, being non-zero, is true. The canonical way of negating a boolean is ldc.i4.0 ; ceq.

    And, as @HansPassant pointed out, the canonical way of doing a != null comparison directly is cgt.un -- all valid references are "greater than zero" when interpreted as unsigned values. That such comparisons are safe is explicitly documented in ECMA-335, section I.12.1.5:

    In particular, object references can be:

    ...

    1. Created as a null reference (ldnull)

    ...

    Managed pointers have several additional base operations.

    ...

    1. Unsigned comparison and conditional branches based on two managed pointers (bge.un, bge.un.s, bgt.un, bgt.un.s, ble.un, ble.un.s, blt.un, blt.un.s, cgt.un, clt.un).