Changes in ILGenerator in .NET 8
- Posted in:
- .NET
I was porting our application to .NET 8 and I found that some 3rd party library throwing InvalidProgramException with the message “Common Language Runtime detected an invalid program”. It is not something that you see often and I started investigating.
The code looks like this:
…
ilgenerator.Emit(OpCodes.Brtrue_S, (sbyte)(OpCodes.Newobj.Size + 4 + OpCodes.Throw.Size));
ilgenerator.ThrowException(typeof(ArgumentOutOfRangeException));
…
That 3rd party library trying to generate IL code that checks a boolean condition and if it is false then throw an exception and otherwise skip that block and continue execution. I reviewed the code it tries to generate and I didn’t find anything suspicious.
At that moment, I had an assumption that the IL code was not valid and newer version of .NET is more strict and does not allow that invalid code. I reviewed that code again and everything looks correct. In the next step, I checked that the code actually executes with .NET 6 and it does. This is an important step as well, because for .NET this code may not be executed at all.
Firstly I copied that code to the new C# console app and reproduced the problem. This is a very important step because even if I can edit the compiled assembly with dnSpy, it is much slower to edit and much slower to try different things because our application is quite big.
Then I checked it in .NET 7 and it also doesn’t work. Sometimes it helps to know where a breaking change is so I can use the proper version when searching the internet. But the search didn’t yield any results.
In the next step, I was trying to create a similar program in C# as that program trying to generate and check the generated IL code. It looks almost identical. Then I changed my test code to be exactly the same but it still does not work.
Then I decided to check binary opcodes for my IL code and code in C#. They also look identical except for references to the methods which are supposed to be different. And at that moment I changed my assumption to a new one. The code is correct but it looks like I need to use the ILGenerator differently.
Finally, I found a newer version of that library that worked and that code looks differently
Label label = ilgenerator.DefineLabel();
…
ilgenerator.Emit(OpCodes.Brtrue_S, label);
ilgenerator.ThrowException(typeof(ArgumentOutOfRangeException));
ilgenerator.MarkLabel(label);
…
After that, I applied these changes to my code and viola it works fine. Then I decided to output the content of the m_ILStream field from the ILGenerator for the version with .NET 6 and the new version with .NET 8. And the difference was in only one byte. In .NET 6 it would be 6 after brtrue.s instruction and in .NET 8 it would be zero. To me, it looks like it would resolved during the compilation phase.
I believe that Microsoft decided to fix a potential security issue when an application can place an incorrect offset and jump to the wrong place and violate the safety of runtime. And as a result, they are not allowed to use manual offset.
I hope it helps someone.
Comments