Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a custom ILProcessor and make it auto-fix IL corruptions #2213

Merged
merged 6 commits into from
Aug 18, 2021

Conversation

vitek-karas
Copy link
Member

Cecil doesn't handle IL editing well. It doesn't patch scopes and branches. Added a linker-private IL processor which does this.

Cecil doesn't handle IL editing well. It doesn't patch scopes and branches. Added a linker-private IL processor which does this.
Copy link
Member

@sbomer sbomer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we considered upstreaming these improvements? It might be worth asking Jb about the reasons for not automatically updating branch/handler targets.

throw new ArgumentOutOfRangeException (nameof (instruction));

Instruction nextInstruction = Instructions.Count == index + 1 ? null : Instructions[index + 1];
Instruction previousInstruction = index == 0 ? (Instructions.Count == 1 ? null : Instructions[index + 1]) : Instructions[index - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the index+1 case for previousInstruction intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - if we're removing the first instruction, we have to re-point to "something", in this case the previous and next will be the same, but there's no other way really.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by this - if removing the last instruction, we are redirecting scope starts to null. Shouldn't removing the first instruction be symmetric, and redirect scope ends to null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I think the correct behavior here is actually to remove the exception handler if this happens - basically if we try to redirect the "Start" of the exception handler to null, we should remove it.
That said this will never happen in the linker I think, branch removal handles removing of exception handlers on its own. So I would rather leave this as is - and just point it to null. Again - purposefully getting into invalid state, but so be it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - removing the handler might be reasonable, but I think it's ok not to do too much "magic" here if you are getting into such weird states. Same for the case where we remove the last instruction and it's a branch target - in this case you just need to know what you're doing.

@vitek-karas
Copy link
Member Author

We will try the upstream, but we can't wait for upstream (even if we authored the fix, typically it takes a while to get it approved/merged).

throw new ArgumentOutOfRangeException (nameof (instruction));

Instruction nextInstruction = Instructions.Count == index + 1 ? null : Instructions[index + 1];
Instruction previousInstruction = index == 0 ? (Instructions.Count == 1 ? null : Instructions[index + 1]) : Instructions[index - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by this - if removing the last instruction, we are redirecting scope starts to null. Shouldn't removing the first instruction be symmetric, and redirect scope ends to null?

@vitek-karas
Copy link
Member Author

I was able to reproduce the failure from ArrayPool. So I added test cases in the TryFinally and TryCatch tests which repro the failure (they would put the .endcatch after the end of the method before the fix).

I also removed now unnecessary test attribute, since in my previous change the exception handlers are now validated within the instruction stream, so no need for a second attribute to validate these.

There are no thorough tests for the IL processor in the various corner cases, since we don't have a way in the linker to hit these currently. And I didn't want to try to add true unit tests - it didn't seem worth it.

I think this is now ready for review - since it provably fixes a real issue.
I was able to validate that with these fixes we don't corrupt the ArrayPool code anymore. I will try the full repro of that and update the PR here with the outcome.

@vitek-karas vitek-karas marked this pull request as ready for review August 17, 2021 21:48
@vitek-karas
Copy link
Member Author

@agocke We will want to take this for .NET 6 - not sure what you'll need for approval, please let me know.

@vitek-karas
Copy link
Member Author

I was able to verify that the repro case works with these changes - no mono AOT compiler failures. Will merge once CI passes.

@vitek-karas vitek-karas merged commit e9403e2 into dotnet:main Aug 18, 2021
@vitek-karas vitek-karas deleted the BetterILProcessor branch August 18, 2021 14:21
agocke pushed a commit to agocke/linker that referenced this pull request Aug 19, 2021
…otnet#2213)

Cecil doesn't handle IL editing well. It doesn't patch scopes and branches. Added a linker-private IL processor which does this.

Added tests for the IL corruption when removing branches around code which has exception handlers.

I also removed now unnecessary test attribute, since in my previous change the exception handlers are now validated within the instruction stream, so no need for a second attribute to validate these.

Co-authored-by: Sven Boemer <[email protected]>
(cherry picked from commit e9403e2)
agocke added a commit that referenced this pull request Aug 24, 2021
[release/6.0-rc1] Fix IL corruption (#2213)
agocke pushed a commit to dotnet/runtime that referenced this pull request Nov 16, 2022
…otnet/linker#2213)

Cecil doesn't handle IL editing well. It doesn't patch scopes and branches. Added a linker-private IL processor which does this.

Added tests for the IL corruption when removing branches around code which has exception handlers.

I also removed now unnecessary test attribute, since in my previous change the exception handlers are now validated within the instruction stream, so no need for a second attribute to validate these.

Co-authored-by: Sven Boemer <[email protected]>

Commit migrated from dotnet/linker@e9403e2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants