Add unsafe modifier migration code fixer#131002
Conversation
|
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. |
|
Tagging subscribers to this area: @agocke, @dotnet/illink |
There was a problem hiding this comment.
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) andUnsafeModifierMigrationCodeFixProviderto report/fix unsafe modifier normalization whenEnableUnsafeMigrationis enabled. - Adds supporting shared diagnostic IDs/categories/strings and exposes
EnableUnsafeMigrationas 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. |
There was a problem hiding this comment.
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.HelpLinkUriis a nullable string andDiagnosticDescriptors.GetDiagnosticDescriptor(...)passeshelpLinkUrithrough directly. For these new diagnosticsDiagnosticIdExtensions.GetHelpUrireturnsnull, sodescriptor.HelpLinkUriwill benullandAssert.Empty(descriptor.HelpLinkUri)will fail. Use a null check (orstring.IsNullOrEmpty) instead ofAssert.Empty.
DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetDiagnosticDescriptor(diagnosticId);
Assert.Equal("Unsafe", descriptor.Category);
Assert.Empty(descriptor.HelpLinkUri);
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
d9ba0af to
3ca02e6
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
|
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. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9b5b020c-fb1b-459b-a49e-36b77cb62d6c
This comment was marked as spam.
This comment was marked as spam.
|
PTAL @jjonescz @333fred |
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. |
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 Sometimes they don't have the |
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:
New analyzers/code-fixers added in this PR:
[fixer]
AddUnsafeToExternCodeFixProviderfixesCS9389(see 'Diagnostics IDs' below) by marking anexternmemberunsafeby default. Developers can replace it withsafeafter auditing the interop boundary.[fixer]
RemoveInvalidUnsafeCodeFixProviderfixesCS9377and unsafe-specificCS0106diagnostics by removing the compiler-reported meaningless or invalidunsafemodifier (for example, from a type declaration).[analyzer]
UnsafeMemberMissingSafetyDocumentationAnalyzer(IL5005) reports unsafe members without a<safety>XML comment.[fixer]
RemoveUndocumentedUnsafeCodeFixProviderfixesIL5005by removing an undocumentedunsafemodifier 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 keepunsafefor 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 keepunsafe: removing it would recreateCS9392, and choosingsaferequires an explicit developer audit.AddUnsafeToFieldCodeFixProviderremains responsible for defaulting unclassified fields tounsafe.[analyzer]
PointerSignatureRequiresUnsafeAnalyzer(IL5006) reports members with pointer or function-pointer signatures that are missingunsafe. A<safety>XML comment suppresses the diagnostic when the signature is intentionally safe, for exampleArgumentNullException.ThrowIfNull(void*). An existing explicitsafemodifier also suppresses the diagnostic.[fixer]
AddUnsafeToPointerSignatureCodeFixProviderfixesIL5006by adding the missingunsafemodifier.[fixer]
AddUnsafeToFieldCodeFixProviderfixesCS9392by addingunsafeto 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 applyunsafeorsafeto the parameter declaration.Diagnostics IDs
Just for reference
CS9389[Roslyn] - Anexternmember must be explicitly markedunsafeorsafe.CS9377[Roslyn] - Theunsafemodifier has no effect at this location under the updated memory safety rules.CS0106[Roslyn] - Theunsafemodifier is not valid for this item. This PR's fixer only activates whenunsafeis the invalid modifier.CS9360[Roslyn] - An unsafe operation may only be used in an unsafe context.CS9361[Roslyn] - Astackallocexpression without an initializer inside[SkipLocalsInit]may only be used in an unsafe context.CS9362[Roslyn] - A member markedunsafemust 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 anunsafeconstructor satisfies anew()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 beunsafe, or neither may beunsafe.CS9390[Roslyn] - Both partial member declarations must be markedsafe, or neither may be markedsafe.CS9392[Roslyn] - A field in an explicit or extended-layout type must be markedunsafeorsafe.CS9388[Roslyn] - Thesafemodifier is only valid on non-unsafeexternmembers 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 missingunsafe, unless a<safety>comment documents why it is safe (for example, when unsafe-v1 code relied onunsafeon the containing type).For follow ups
Implement a code fixer for unsafe-context diagnostics
CS9360,CS9361,CS9362,CS9363, andCS9376. It should introduceunsafe { /* SAFETY: Audit */ }blocks orunsafe(/* 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, andCS9366, partial modifier mismatchesCS0764andCS9390, 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 asextern,safecannot be applied to it, and its generated implementation is not compiled under the updated memory-safety rules. See safe keyword on LibraryImport roslyn#84555.