I have recently been playing around with writing MSIL and compiling it with ilasm, when I noticed that methods do require a ret instruction to return from the end of the method; For example I should be supposed to write code like this:
.method static void Main()
{
.entrypoint
ldstr "Hello World!"
call void [mscorlib]System.Console::WriteLine(string)
ret //I am returning properly
}
However, if I omit the ret, the code still runs and outputs "Hello World!" perfectly. At first I thought this could be specific to the entrypoint method, but ilasm happily compiles this code with neither warnings nor errors:
.assembly Program{}
.assembly extern mscorlib{}
.method static void Main()
{
.entrypoint
ldstr "Hello World!"
call void [mscorlib]System.Console::WriteLine(string)
ldstr "Foo returned: {0}!"
call int32 Foo()
box int32
call void [mscorlib]System.Console::WriteLine(string, object)
}
.method static int32 Foo()
{
ldstr "Hello from Foo!"
call void [mscorlib]System.Console::WriteLine(string)
ldstr "GoodBye!"
call void [mscorlib]System.Console::WriteLine(string)
ldc.i4 42
}
Notice that neither Main() nor Foo() have return statements. Foo() even has a return value! When this code is compiled and run I get the following output:
Hello World! Hello from Foo! GoodBye! Foo returned: 42!
The program also terminates normally. I then thought that perhaps ilasm was auto-inserting ret statements, but after looking at the program with ildasm the methods were identical to the code above i.e. without returns.
Curiously, when I attempted to decompile the method with DotPeek, it flat out refused replacing both method bodies with // ISSUE: unable to decompile the method.
If I add the ret statements and recompile DotPeek can decompile both methods without issue.
Can someone please explain what is going on here?
I think this is because the CLR sometimes accepts IL that's not valid, and only when it encounters IL that it actually can't execute, it throws
InvalidProgramException
. My guess is that this is done for performance reasons: verifying that the IL follows all the rules would be too slow.If you want to verify that your IL is valid, you should use PEVerify on it.
And when you have invalid code, it's not surprising that some tools like DotPeek can't handle it.