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?