Add unsafe contract consistency code fixes - #131454
Open
EgorBo wants to merge 2 commits into
Open
Conversation
Adds two code fixers to the unsafe-v2 migration tooling, both built on diagnostics Roslyn already reports. SynchronizeUnsafeContractCodeFixProvider fixes CS9364, CS9365 and CS9366, reported when an unsafe member overrides or implements a safe member. It offers narrowing the derived member, using safe rather than removal when the member is extern so the fix does not trade the error for CS9389, and marking the base member unsafe when that member is declared in source. MatchPartialSafetyModifierCodeFixProvider fixes CS0764 and CS9390 by copying the safety modifier onto the partial declaration that lacks it. No fix is offered when the parts declare opposite contracts, since that would place safe and unsafe on the same declaration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06c64005-a797-4517-9227-d1706eb5bca5
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @agocke, @dotnet/illink |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds new (DEBUG-only) Roslyn code fix providers to the ILLink unsafe-v2 migration tooling to resolve compiler-reported safety-contract inconsistencies in overrides/interface implementations and partial members, plus associated helper updates, resources, and tests.
Changes:
- Add
SynchronizeUnsafeContractCodeFixProviderto fix CS9364/CS9365/CS9366 by either narrowing the derived member or marking the base memberunsafewhen editable. - Add
MatchPartialSafetyModifierCodeFixProviderto fix CS0764/CS9390 by propagatingunsafe/safeacross partial member declarations (when an editable counterpart exists). - Extend shared syntax/modifier helper infrastructure and add targeted xUnit coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/SynchronizeUnsafeContractCodeFixTests.cs | Adds tests for CS9364/5/6 fixes, including extern and partial-base scenarios. |
| src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/MatchPartialSafetyModifierCodeFixTests.cs | Adds tests for CS0764/CS9390 partial safety-modifier propagation behavior. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationSyntaxHelpers.cs | Exposes SafeKeywordKind to allow code fixes to reason about safe on older Roslyn builds. |
| src/tools/illink/src/ILLink.CodeFix/UnsafeModifierCodeFixHelpers.cs | Generalizes modifier editing to support both unsafe and safe, and adds unsafe→safe replacement helper. |
| src/tools/illink/src/ILLink.CodeFix/SynchronizeUnsafeContractCodeFixProvider.cs | New code fix provider for derived/base contract mismatches (CS9364-6). |
| src/tools/illink/src/ILLink.CodeFix/MatchPartialSafetyModifierCodeFixProvider.cs | New code fix provider for partial declaration modifier mismatches (CS0764/CS9390). |
| src/tools/illink/src/ILLink.CodeFix/Resources.resx | Adds localized titles for the new code actions. |
Comment on lines
+143
to
+146
| private static SyntaxNode? FindPartialDeclaration(SyntaxNode node) => | ||
| node.AncestorsAndSelf().FirstOrDefault(static ancestor => ancestor is MethodDeclarationSyntax | ||
| or PropertyDeclarationSyntax | ||
| or EventDeclarationSyntax); |
18 tasks
FindPartialDeclaration only matched methods, properties and events with accessors, so CS0764 and CS9390 on a partial indexer or constructor found no declaration and no fix was offered. Match the base declaration kinds instead, and normalize a field-like event's variable declarator to the declaration that carries the modifier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06c64005-a797-4517-9227-d1706eb5bca5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds code fixers for the contract consistency part of the unsafe-v2 migration tooling (tracking issue §3), continuing #131002. Both fixers are built entirely on diagnostics Roslyn already reports, so no new diagnostic ID is introduced. Nothing changes under unsafe-v1.
Background
Under the updated memory safety rules
unsafeon a member is part of its public contract, so the compiler rejects a member that isunsafewhen the member it overrides or implements is not (callers holding a base reference would never see the added obligation). It likewise requires both halves of a partial member to agree. Neither error has a fix today, and both are common during migration: marking a memberunsafeimmediately breaks its overrides, and source generators author one half of a partial.Changes in this PR
🔧
SynchronizeUnsafeContractCodeFixProviderfixesCS9364,CS9365andCS9366(see 'Diagnostics IDs' below) with two actions, since the compiler cannot tell which side is wrong:unsafewas an unsafe-v1 lexical scope. For anexternmember this replacesunsafewithsaferather than removing it, because anexternmember must keep an explicit marker.unsafe, which is correct when the contract was under-annotated. Only offered when that member is declared in source.🔧
MatchPartialSafetyModifierCodeFixProviderfixesCS0764andCS9390by copying the safety modifier onto the declaration that lacks it. The modifier is always propagated rather than removed: forunsafethat preserves the caller obligation, and forsaferemoving it would reintroduceCS9389on anexternmember.Both diagnostics for partials are reported at
implementation.GetFirstLocation()regardless of which part carries the modifier, so the fixer frequently has to edit a different document than the one the diagnostic is in.Notable cases handled
externoverrides. Removingunsafefrompublic unsafe extern override object M();only tradesCS9364forCS9389, andAddUnsafeToExternCodeFixProviderfrom Add unsafe modifier migration code fixer #131002 would then add it straight back. The fix offerssafeinstead. This shape is real, e.g.RuntimeFieldInfo.UnsafeGetValue.DeclaringSyntaxReferencesonly covers its own half, so annotating the base has to walk both parts or the fix tradesCS9364forCS0764. Edits are batched per document so no node is re-resolved against a tree whose spans have already shifted.safeand the otherunsafe, the compiler reports bothCS0764andCS9390, and naively satisfying each would put both modifiers on one declaration (CS9388). No fix is offered, since resolving it requires deciding which contract is correct.CS9364is reported once per accessor whileunsafemay sit on the containing property, so the fixer walks outward to the declaration that actually carries the modifier.Diagnostics IDs
Just for reference
CS9364[Roslyn] - Anunsafemember cannot override a safe member.CS9365[Roslyn] - Anunsafemember cannot implicitly implement a safe member.CS9366[Roslyn] - Anunsafemember cannot explicitly implement a safe member.CS0764[Roslyn] - Both partial member declarations must beunsafe, or neither may beunsafe.CS9390[Roslyn] - Both partial member declarations must be markedsafe, or neither may be markedsafe.CS9388[Roslyn] - Thesafemodifier is only valid on non-unsafeexternmembers or field-like members of explicit or extended-layout types.CS9389[Roslyn] - Anexternmember must be explicitly markedunsafeorsafe.Known limitations / follow ups
unsafe" action is only offered when the base is in source. Roslyn filtersRequiresUnsafeAttributeout ofISymbol.GetAttributes(), so a contract coming from a referenced assembly is invisible to analyzers.unsafe, is not covered here. That is legal and the compiler is silent, so it needs its own analyzer (IL5008in the tracking issue) and is deliberately left out of this PR.MatchPartialSafetyModifierCodeFixProviderskips parts that live in generated code, since there is no editable document for them.Testing
ILLink.RoslynAnalyzer.Tests: 1223 passed. 12 new tests cover both narrowing and base-annotating actions across methods, properties, and implicit and explicit interface implementations, plus regressions for theextern, partial-base and conflicting-contract cases above.Note
This description and the changes in this PR were generated with GitHub Copilot.