ltrzesniewski / InlineIL.Fody

Inject arbitrary IL code at compile time.
MIT License
240 stars 17 forks source link

IL.Push(expression) leads to invalid program #25

Closed kevinjwz closed 3 years ago

kevinjwz commented 3 years ago

.NET SDK version: 5.0.202 InlineIL.Fody version: 1.6.0 Command line options: dotnet build -c Release

Example:

struct MyStruct
{
    public int int32;
    public object obj;
}

static ref MyStruct M(int i) => throw new NotImplementedException();

static void Add()
{
    ref MyStruct a = ref M(-1);
    ref MyStruct b = ref M(0);
    Push(a.int32);
    Push(b.int32);
    Emit.Add();
    Pop(out int result);
    a.int32 = result;
}

Add compiles to:

.method private hidebysig static 
    void Add () cil managed 
{
    .maxstack 2
    .locals init (
        [0] valuetype MyStruct& a,
        [1] int32 result
    )

    IL_0000: ldc.i4.m1
    IL_0001: call      valuetype MyStruct& C::M(int32)
    IL_0006: stloc.0
    IL_0007: ldc.i4.0
    IL_0008: call      valuetype MyStruct& C::M(int32)
    IL_000D: ldloc.0
    IL_000E: ldfld     int32 MyStruct::'int32'
    IL_0013: ldfld     int32 MyStruct::'int32'
    IL_0018: add
    IL_0019: stloc.1
    IL_001A: ldloc.0
    IL_001B: ldloc.1
    IL_001C: stfld     int32 MyStruct::'int32'
    IL_0021: ret
} // end of method C::Add

Notice there are 2 ldfld instructions at 000E and 0013.

ltrzesniewski commented 3 years ago

Thanks for the bug report!

Due to the way the IL is generated by the compiler, InlineIL should have emitted an error on Push(b.int32), but the current validator didn't catch the issue.

ltrzesniewski commented 3 years ago

I pushed a v1.7.0-pre1 package to NuGet that fixes this, in the sense that an error will now be emitted for your code.

Since the new validator can emit errors for code that happened to work fine in previous versions, I added a IL.EnsureLocal method which should be useful in simpler cases, but it won't help for ref var unfortunately. IL.Push wasn't a great idea anyway.