Uh oh!
There was an error while loading. Please reload this page.
Add unsafe evolution migration code fixers - #130611
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3ccf7c6-8a32-4c31-9e31-12f0f1d2a026
There was a problem hiding this comment.
Pull request overview
This PR adds a DEBUG-only “unsafe evolution” migration analyzer + code fixers to ILLink, introducing two new IL* diagnostics (IL5005/IL5006) to (1) remove legacy/invalid unsafe modifiers and (2) migrate unsafe usages by adding explicit unsafe contexts and safety placeholders. It also wires up additional MSBuild properties and live-ILLink behavior to enable the migration experience, and adds/extends Roslyn analyzer/codefix tests.
Changes:
- Add
UnsafeMigrationAnalyzer+UnsafeMigrationAnalysisto detect modifier removals, required declaration updates, and unsafe-operation sites (behind#if DEBUG). - Add two new code fix providers (modifier removal + usage migration) and supporting document/AST rewrite helpers (behind
#if DEBUG). - Add new diagnostic strings/IDs and update build/targets/tests to enable and validate the migration workflow.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs | Adds test helpers and extensive test coverage for IL5005/IL5006 codefix behavior. |
| src/tools/illink/src/ILLink.Shared/SharedStrings.resx | Adds user-facing title/message strings for the new unsafe migration diagnostics. |
| src/tools/illink/src/ILLink.Shared/DiagnosticId.cs | Adds DEBUG-only diagnostic IDs and introduces an “Unsafe” category range mapping. |
| src/tools/illink/src/ILLink.Shared/DiagnosticCategory.cs | Adds DEBUG-only Unsafe category constant. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationAnalyzer.cs | New analyzer that reports file-level migration diagnostics when migration is enabled. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationAnalysis.cs | New analysis engine for modifier removals, contract/declaration updates, and unsafe operation locations. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs | Adds DEBUG-only MSBuild property names used to toggle unsafe migration behavior. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props | Exposes additional compiler-visible MSBuild properties used by the migration flow. |
| src/tools/illink/src/ILLink.CodeFix/UnsafeUsageMigrationCodeFixProvider.cs | New code fix provider that migrates unsafe usage sites (wrap blocks/expressions, etc.). |
| src/tools/illink/src/ILLink.CodeFix/UnsafeUsageDocumentFixer.cs | New document fixer implementing the migration transformations and heuristics. |
| src/tools/illink/src/ILLink.CodeFix/UnsafeModifierMigrationCodeFixProvider.cs | New code fix provider that removes legacy unsafe modifiers when eligible. |
| src/tools/illink/src/ILLink.CodeFix/UnsafeCodeFixHelpers.cs | New shared helpers for adding unsafe contexts and safety documentation. |
| src/tools/illink/src/ILLink.CodeFix/Resources.resx | Adds code fix titles for the new migration fixers. |
| eng/liveILLink.targets | Updates live-ILLink wiring to support enabling unsafe migration and design-time analyzer referencing. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3ccf7c6-8a32-4c31-9e31-12f0f1d2a026
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3ccf7c6-8a32-4c31-9e31-12f0f1d2a026
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3ccf7c6-8a32-4c31-9e31-12f0f1d2a026
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Parse unsafe(...) with explicit preview + updated-memory-safety-rules options - Use SyntaxFactory elastic newline trivia instead of hardcoded CRLF literals - Remove redundant single-arm switch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 826b613c-e6ee-4cc6-a6a7-b06038520aad
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The IL5006 document fixer could emit code that fails to compile:
- unsafe(...) expressions at statement/void/assignment-target positions (only valid as a value sub-expression) now fall back to unsafe { } blocks
- out-var/pattern-var declarations (Foo(out int y);) are forward-declared before the unsafe block so they stay in scope
- out-vars in a local declaration initializer (int x = Foo(out int y);) fall back to the unsafe(...) expression form, keeping the variable in scope
- only interior (not leading/trailing) directives block wrapping a statement
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 826b613c-e6ee-4cc6-a6a7-b06038520aadUh oh!
There was an error while loading. Please reload this page.
- Split multi-variable event field declarations with mixed safe/unsafe contracts so 'unsafe' is applied only to the variables that require it (avoids CS9365 where a safe interface event would be made illegally unsafe)
- Drop the statement-wrap branch that ignored requiresExpression, so using/await/directive statements that need an unsafe(...) expression fall back to whole-body wrapping instead of an unsafe { } block that would change scope
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 826b613c-e6ee-4cc6-a6a7-b06038520aadUh oh!
There was an error while loading. Please reload this page.
When splitting a multi-variable event field, keep each variable node's trivia (e.g. inline comments like 'event Action E1 /* keep */, E2;') instead of stripping it; only the outer declaration trivia is normalized. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 826b613c-e6ee-4cc6-a6a7-b06038520aad
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ee40608f-4e94-47e1-8763-843d916bab40
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "74d7482707561a9b391043d905abee5cc4c733c9",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "53a3837195f7d8e7d714306fab5679562b2261d2",
"last_reviewed_commit": "74d7482707561a9b391043d905abee5cc4c733c9",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "53a3837195f7d8e7d714306fab5679562b2261d2",
"last_recorded_worker_run_id": "29686415576",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "74d7482707561a9b391043d905abee5cc4c733c9",
"review_id": 4730737351
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: The change implements automated migration tooling (analyzer IL5005/IL5006 plus code-fix providers) for the unsafe-evolution C# language proposal, letting the runtime repo mechanically strip legacy/invalid unsafe modifiers and add required unsafe blocks, expressions, and <safety> documentation. This is a real, well-motivated need: migrating a large codebase to the new memory-safety rules by hand would be impractical and error-prone.
Approach: The approach is sound and appropriately scoped. All new code is wrapped in #if DEBUG and gated behind the opt-in EnableUnsafeMigration MSBuild property, so it adds no shipping public API surface and cannot affect production builds or normal analyzer runs. It reuses the existing ILLink analyzer/code-fix infrastructure (diagnostic IDs, categories, resx strings, CSharpCodeFixVerifier) rather than inventing parallel machinery, and the IL5005/5006 IDs and Unsafe category are correctly slotted into DiagnosticId/DiagnosticCategory without disturbing the existing AOT range mapping. The logic handles a broad matrix of C# constructs (partial members, interfaces, accessors, stackalloc, async, top-level statements, scoped refs), which the extensive test file exercises.
Summary: <safety>/skiplocalsinit handling, and confirm the tool's output on representative source is what the proposal intends.
Detailed Findings
✅ Scope & Risk — Correctly isolated tooling change
Every new type and every touched enum/category/resx addition is under #if DEBUG and further gated by the EnableUnsafeMigration compiler-visible property (verified in Microsoft.NET.ILLink.Analyzers.props, MSBuildPropertyOptionNames.cs, and both code-fix providers' RegisterCodeFixesAsync). No ref/ assembly or shipping public API is introduced, so the API-approval gate does not apply. Production analyzer behavior is unchanged.
✅ Codebase Fit — Consistent with existing ILLink patterns
Diagnostic IDs (5005/5006), the new Unsafe category, and the resx entries follow existing conventions. The GetDiagnosticCategory switch inserts the two new IDs before the >= 3050 and <= 6000 => AOT arm so they resolve to Unsafe rather than being swallowed by the AOT range — this ordering is correct and covered by the UnsafeMigrationDiagnostics_HaveUnsafeCategoryWithoutHelpLinks test.
✅ Test Quality — Broad behavioral coverage
The new RequiresUnsafeCodeFixTests.cs adds ~44 focused test methods covering modifier removal/retention, whole-body vs. statement vs. expression wrapping, forward declarations, ref locals, async/yield exclusions, and the migration-disabled case. Using CSharpCodeFixVerifier with before/after sources is the right harness for code-fix validation. Note: I could not build or run these tests in this environment; the human reviewer should confirm they pass in CI.
💡 Brittle syntax-node detection (inline)
One low-severity, non-blocking maintainability note is left inline in UnsafeMigrationAnalysis.cs regarding the GetType().Name == "UnsafeExpressionSyntax" string check used to recognize the preview unsafe(...) expression node.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 192.5 AIC · ⌖ 12.1 AIC · ⊞ 10K
| private static bool IsInUnsafeContext(SyntaxNode node) | ||
| => node.AncestorsAndSelf().Any(static ancestor => | ||
| ancestor is UnsafeStatementSyntax || | ||
| ancestor.GetType().Name == "UnsafeExpressionSyntax" || |
There was a problem hiding this comment.
💡 This uses a stringly-typed check (ancestor.GetType().Name == "UnsafeExpressionSyntax") to detect the preview unsafe(...) expression node, presumably because the strongly-typed syntax type isn't available in the referenced Roslyn version. That's a reasonable pragmatic workaround, but it is brittle: it silently breaks if the node type is renamed, and it won't match derived/wrapped forms. Consider adding a short comment explaining why the reflection-by-name check is used and, if feasible, guarding it with a SyntaxKind check so the intent survives a Roslyn bump. Non-blocking given this is DEBUG-only tooling.
EgorBo
commented
Jul 20, 2026
decided to split into many small analyzers/fixers and the first batch is in #131002 |
Spec: https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md
What it does:
unsafemodifiers.<safety>comment. It's a sign that thisunsafewas from unsafe-v1.unsafewhen required by interop, partial members, overrides, or interface contracts.unsafeand<safety>placeholders to unannotated interop declarations.How to run it from the repo root:
I was using the latest daily SDK in
C:\prj\dotnet-latest, not sure the bootstrapped one contains all the new C# features.