ltrzesniewski / InlineIL.Fody

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

Creating labels in otherwise unreachable code fails #27

Closed Brokemia closed 2 years ago

Brokemia commented 2 years ago

If I try to use IL.MarkLabel() inside an area that would normally be unreachable code, any attempts to branch to that label can't find it. Here's a small example:

public int Example(bool test) {
    if(test) {
        IL.Emit.Br("Label");
    }
    return 0;
    IL.MarkLabel("Label");
    return 1;
}

This gives the following error: error : Fody/InlineIL: Undefined label: 'Label' (in System.Int32 DecompileMe.Class1::Example(System.Boolean) at instruction IL_0000: br IL_0000)

I have a suspicion this isn't easily fixable, but I'm making this issue anyway in case I'm wrong

ltrzesniewski commented 2 years ago

Thanks for raising this issue, but unfortunately, there's not much I can do to fix the root cause.

The compiler throws away unreachable code, and by the time InlineIL sees the compiler output, the code is gone. See this example, the decompiled result is what InlineIL gets to see:

public int Example(bool test)
{
    if (test)
    {
        Br("Label");
    }
    return 0;
}

You can work around this by replacing return 0; with IL.Push(0); IL.Emit.Ret();, although I suppose you only posted a simplified example here.

I could probably implement some complicated mess such as:

public int Example(bool test) {
    if (IL.IgnoreThisBranch()) { // <-- this block would get deleted by InlineIL
        goto fakeLabel;
    }
    if (test) {
        IL.Emit.Br("Label");
    }
    return 0;
    fakeLabel:
    IL.MarkLabel("Label");
    return 1;
}

But I think that would be too convoluted and difficult to read. Working around this by emitting return 0; as IL is the best approach IMO.

Brokemia commented 2 years ago

Thanks for the quick reply! I actually did try doing returns in the way you suggested, but then I ran into the same issue but with the continue keyword, which doesn't translate as easily into IL (I'm working on doing that, but my use case is for code generation so it's a bit harder than if I was writing the code myself).

ltrzesniewski commented 2 years ago

continue can be replaced by a branch to the end of the loop, i.e:

while (foo)
{
    if (bar)
    {
        // continue;
        IL.Emit.Br("endOfLoop");
    }

    // ...

    IL.MarkLabel("endOfLoop");
}

Although you won't be able to exit out of a try block that way (you'd have to emit leave instead of br for instance).