Skip to content

Add unsafe modifier migration code fixer#131002

Open
EgorBo wants to merge 4 commits into
mainfrom
unsafe-migrator-4
Open

Add unsafe modifier migration code fixer#131002
EgorBo wants to merge 4 commits into
mainfrom
unsafe-migrator-4

Conversation

@EgorBo

@EgorBo EgorBo commented Jul 17, 2026

Copy link
Copy Markdown
Member

This PR adds new analyzers and codefixers (not yet shipping) that assist in migrating to the new unsafe-v2 rules (unsafe evolution). The goals are:

  • Analyzers/code-fixers should be idempotent as migrations to the new rules could be incremental.
  • Code-fixers should rely on the existing Roslyn analyzers as much as possible.

New analyzers/code-fixers added in this PR:

  1. [fixer] AddUnsafeToExternCodeFixProvider fixes CS9389 (see 'Diagnostics IDs' below) by marking an extern member unsafe by default. Developers can replace it with safe after auditing the interop boundary.

  2. [fixer] RemoveInvalidUnsafeCodeFixProvider fixes CS9377 and unsafe-specific CS0106 diagnostics by removing the compiler-reported meaningless or invalid unsafe modifier (for example, from a type declaration).

  3. [analyzer] UnsafeMemberMissingSafetyDocumentationAnalyzer (IL5005) reports unsafe members without a <safety> XML comment.

  4. [fixer] RemoveUndocumentedUnsafeCodeFixProvider fixes IL5005 by removing an undocumented unsafe modifier when it is assumed to be an unsafe-v1 lexical scope rather than an intentional caller-unsafe contract. Members with pointer or function-pointer signatures keep unsafe for backward compatibility because such members were effectively caller-unsafe under unsafe-v1 (and in >90% cases they end up dereference their unmanaged pointers anyway). Field-like members in explicit or extended-layout types keep unsafe: removing it would recreate CS9392, and choosing safe requires an explicit developer audit. AddUnsafeToFieldCodeFixProvider remains responsible for defaulting unclassified fields to unsafe.

  5. [analyzer] PointerSignatureRequiresUnsafeAnalyzer (IL5006) reports members with pointer or function-pointer signatures that are missing unsafe. A <safety> XML comment suppresses the diagnostic when the signature is intentionally safe, for example ArgumentNullException.ThrowIfNull(void*). An existing explicit safe modifier also suppresses the diagnostic.

  6. [fixer] AddUnsafeToPointerSignatureCodeFixProvider fixes IL5006 by adding the missing unsafe modifier.

  7. [fixer] AddUnsafeToFieldCodeFixProvider fixes CS9392 by adding unsafe to instance fields, field-backed properties, and field-like events in explicit or extended-layout types. Primary-constructor parameters are intentionally not changed because C# cannot apply unsafe or safe to the parameter declaration.

Diagnostics IDs

Just for reference

  • CS9389 [Roslyn] - An extern member must be explicitly marked unsafe or safe.
  • CS9377 [Roslyn] - The unsafe modifier has no effect at this location under the updated memory safety rules.
  • CS0106 [Roslyn] - The unsafe modifier is not valid for this item. This PR's fixer only activates when unsafe is the invalid modifier.
  • CS9360 [Roslyn] - An unsafe operation may only be used in an unsafe context.
  • CS9361 [Roslyn] - A stackalloc expression without an initializer inside [SkipLocalsInit] may only be used in an unsafe context.
  • CS9362 [Roslyn] - A member marked unsafe must be used in an unsafe context.
  • CS9363 [Roslyn] - A member with pointers in its signature must be used in an unsafe context.
  • CS9376 [Roslyn] - An unsafe context is required when an unsafe constructor satisfies a new() constraint.
  • CS9364 [Roslyn] - An unsafe member cannot override a safe member.
  • CS9365 [Roslyn] - An unsafe member cannot implicitly implement a safe member.
  • CS9366 [Roslyn] - An unsafe member cannot explicitly implement a safe member.
  • CS0764 [Roslyn] - Both partial member declarations must be unsafe, or neither may be unsafe.
  • CS9390 [Roslyn] - Both partial member declarations must be marked safe, or neither may be marked safe.
  • CS9392 [Roslyn] - A field in an explicit or extended-layout type must be marked unsafe or safe.
  • CS9388 [Roslyn] - The safe modifier is only valid on non-unsafe extern members or field-like members of explicit or extended-layout types.
  • IL5005 [This PR] - An unsafe member has no <safety> XML documentation.
  • IL5006 [This PR] - A member with a pointer or function-pointer signature is missing unsafe, unless a <safety> comment documents why it is safe (for example, when unsafe-v1 code relied on unsafe on the containing type).

For follow ups

  • Implement a code fixer for unsafe-context diagnostics CS9360, CS9361, CS9362, CS9363, and CS9376. It should introduce unsafe { /* SAFETY: Audit */ } blocks or unsafe(/* SAFETY: Audit */ ...) expressions.

  • Synchronize intentional caller-unsafe contracts across overrides, interface implementations, and partial declarations. This includes Roslyn's unsafe-to-safe mismatch diagnostics CS9364, CS9365, and CS9366, partial modifier mismatches CS0764 and CS9390, and the opposite migration case where an unsafe interface/base contract should be propagated to an implementation that is currently unannotated.

  • Figure out what to do with LibraryImport. Roslyn currently does not treat it as extern, safe cannot be applied to it, and its generated implementation is not compiled under the updated memory-safety rules. See safe keyword on LibraryImport roslyn#84555.

Copilot AI review requested due to automatic review settings July 17, 2026 21:59
@EgorBo
EgorBo requested a review from sbomer as a code owner July 17, 2026 21:59
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 22:00 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 22:00 — with GitHub Actions Inactive
@github-actions github-actions Bot added the area-Tools-ILLink .NET linker development as well as trimming analyzers label Jul 17, 2026
@dotnet-policy-service dotnet-policy-service Bot added the linkable-framework Issues associated with delivering a linker friendly framework label Jul 17, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@EgorBo
EgorBo had a problem deploying to copilot-pat-pool July 17, 2026 22:01 — with GitHub Actions Failure
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/illink
See info in area-owners.md if you want to be subscribed.

@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 22:02 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 22:02 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 22:02 — with GitHub Actions Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds initial “unsafe modifier migration” support to the ILLink analyzer/codefix tooling (DEBUG-only), wiring a new IL5005-style diagnostic and a code fix that normalizes unsafe modifiers based on signature/docs, plus build plumbing to enable the feature via EnableUnsafeMigration.

Changes:

  • Introduces UnsafeMigrationAnalyzer (IL5005) and UnsafeModifierMigrationCodeFixProvider to report/fix unsafe modifier normalization when EnableUnsafeMigration is enabled.
  • Adds supporting shared diagnostic IDs/categories/strings and exposes EnableUnsafeMigration as a compiler-visible MSBuild property.
  • Extends the existing analyzer/codefix test suite with targeted tests for the new diagnostic and fixer.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs Adds verifier helpers and new tests for the unsafe modifier migration diagnostic and code fix.
src/tools/illink/src/ILLink.Shared/SharedStrings.resx Adds title/message resources for the new unsafe migration diagnostic.
src/tools/illink/src/ILLink.Shared/DiagnosticId.cs Adds UnsafeModifierMigration (DEBUG-only) and maps it to a new diagnostic category.
src/tools/illink/src/ILLink.Shared/DiagnosticCategory.cs Adds Unsafe diagnostic category (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationAnalyzer.cs New analyzer that reports a single “file needs unsafe modifier migration” diagnostic when updates are detected.
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationAnalysis.cs New semantic analysis that computes which declarations should/shouldn’t have unsafe based on rules (docs/pointers/interop).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs Adds EnableUnsafeMigration MSBuild property name (DEBUG-only constant).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props Exposes EnableUnsafeMigration as a compiler-visible property.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierMigrationCodeFixProvider.cs New code fix provider to apply modifier normalization across a document when enabled.
src/tools/illink/src/ILLink.CodeFix/Resources.resx Adds the code fix title resource string.
eng/liveILLink.targets Wires EnableUnsafeMigration into LiveILLink, enabling unsafe analyzer and setting the updated-memory-safety-rules feature flag.

Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMigrationAnalysis.cs Outdated
Comment thread src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs Outdated
@EgorBo
EgorBo marked this pull request as draft July 17, 2026 22:06
Copilot AI review requested due to automatic review settings July 17, 2026 23:42
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 23:42 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 23:43 — with GitHub Actions Inactive
@EgorBo
EgorBo had a problem deploying to copilot-pat-pool July 17, 2026 23:43 — with GitHub Actions Failure
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 23:44 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 23:45 — with GitHub Actions Inactive
@EgorBo
EgorBo temporarily deployed to copilot-pat-pool July 17, 2026 23:45 — with GitHub Actions Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs:130

  • DiagnosticDescriptor.HelpLinkUri is a nullable string and DiagnosticDescriptors.GetDiagnosticDescriptor(...) passes helpLinkUri through directly. For these new diagnostics DiagnosticIdExtensions.GetHelpUri returns null, so descriptor.HelpLinkUri will be null and Assert.Empty(descriptor.HelpLinkUri) will fail. Use a null check (or string.IsNullOrEmpty) instead of Assert.Empty.
                DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetDiagnosticDescriptor(diagnosticId);
                Assert.Equal("Unsafe", descriptor.Category);
                Assert.Empty(descriptor.HelpLinkUri);
            }

Comment thread src/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
Copilot AI review requested due to automatic review settings July 20, 2026 11:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Comment thread src/tools/illink/src/ILLink.CodeFix/RemoveInvalidUnsafeCodeFixProvider.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
Copilot AI review requested due to automatic review settings July 20, 2026 11:18
@EgorBo
EgorBo marked this pull request as ready for review July 20, 2026 11:18
@azure-pipelines

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
Copilot AI review requested due to automatic review settings July 20, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
Copilot AI review requested due to automatic review settings July 20, 2026 11:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

@github-actions

This comment was marked as spam.

github-actions[bot]

This comment was marked as spam.

@EgorBo

EgorBo commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

PTAL @jjonescz @333fred
for general overview if you agree with the approach: @jkotas @tannergooding e.g. to use <safety> comment as an anchor for "is it from legacy unsafe-v1 or unsafe-v2", at least internally.

@EgorBo
EgorBo requested a review from jjonescz July 20, 2026 14:14
@tannergooding

tannergooding commented Jul 20, 2026

Copy link
Copy Markdown
Member

e.g. to use comment as an anchor for "is it from legacy unsafe-v1 or unsafe-v2", at least internally.

Just to clarify, by default we warn for:

[CLSCompliant(false)]
public static void ThrowIfNull([NotNull] void* argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
    if (argument is null)
    {
        Throw(paramName);
    }
}

We will suppress that warning via something like this, right?

/// <safety>...</safety>
[CLSCompliant(false)]
public static void ThrowIfNull([NotNull] void* argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)

That seems reasonable to me.

@EgorBo

EgorBo commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

Just to clarify, by default we warn for:

Yes, all methods with unmanaged pointers are typically already marked with unsafe and typically end up dereference those pointers (AndyG did the study). Also, even in unsafe-v1 they were effectively caller-unsafe (besides a few loopholes like void foo(int* x = null); foo();)

Sometimes they don't have the unsafe because containing class has it, so the unrelated code-fixer (RemoveInvalidUnsafeCodeFixProvider) removes it from class (and other palces unsafe shouldn't be on) and this fixer brings it back on such methods. In rare cases like ThrowIfNull it can be left safe and the <safety> comments shuts it. Optionally, it can be done with safe keyword if that is extended to be used everywhere where caller-unsafe is used today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants