Tracking issue for the analyzers and code fixers that help migrate a code base to the updated memory safety rules (unsafe-v2).
Design goals carried over from #131002:
- Tooling must be idempotent, since migration happens incrementally.
- Code fixers should lean on existing Roslyn diagnostics wherever possible, and only add an
ILxxxx diagnostic where the language deliberately does not require something.
- Everything here is currently non-shipping (
#if DEBUG) and disabled by default.
Legend: 🔍 analyzer, 🔧 code fixer, ⚙️ source generator.
1. Declaring contracts on members
2. Unsafe contexts at call sites
Highest volume by far, since every consumer of a newly-unsafe API breaks.
3. Contract consistency
4. Cleaning up legacy unsafe
5. Safety documentation
IL5005 covers signatures, but nothing covers bodies and nothing covers safe. The speclet recommends Rust-style // SAFETY comments, and LDM 2026-05-27 left "compiler or analyzer?" open.
6. Source generator output
The common root cause for the unfixed generators below is ContainingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier and WriteToWithUnsafeModifier, which stamp unsafe onto generated type declarations. Under unsafe-v2 that is an error, and it no longer establishes an unsafe context for the members inside, so generated bodies that dereference pointers need explicit unsafe blocks instead.
Verified clean under updated-memory-safety-rules, no work needed: RegexGenerator (emits [SkipLocalsInit] but no stackalloc, so the tightened stackalloc rule does not apply), JsonSourceGenerator, and the Microsoft.Extensions.* and System.Private.CoreLib generators.
Blocked / external
- dotnet/roslyn#84602: allow
safe on non-extern members. Until it merges, safe cannot be spelled on a [LibraryImport] whose generated implementation is a wrapper.
- dotnet/roslyn#84616: allow
await in an unsafe context. Gates how aggressively the call-site fixer can use unsafe blocks rather than unsafe(...) expressions in async code.
- dotnet/roslyn#82546: public API for the memory-safety-rules version. Until then, tooling detects the opt-in through the
updated-memory-safety-rules feature flag.
Diagnostic ID map
| ID |
Owner |
Meaning |
CS9360-CS9363, CS9376 |
Roslyn |
An unsafe context is required at this use site |
CS9364-CS9366 |
Roslyn |
An unsafe member cannot override or implement a safe member |
CS9377, CS0106 |
Roslyn |
unsafe is meaningless or invalid here |
CS9388, CS9389, CS9392 |
Roslyn |
Explicit safe/unsafe required, or safe used in an invalid position |
CS0764, CS9390 |
Roslyn |
Partial declarations disagree on unsafe or safe |
SYSLIB1064 |
This effort |
[LibraryImport] method must be marked safe or unsafe |
IL5005 |
This effort |
unsafe member has no <safety> documentation |
IL5006 |
This effort |
Pointer signature is missing unsafe |
IL5007 |
This effort |
[LibraryImport] has no explicit contract |
IL5008 |
proposed |
Unsafe contract not propagated to override or implementation |
IL5009 |
proposed |
unsafe block has no // SAFETY: comment |
IL5010 |
proposed |
Explicit safe modifier has no justification |
Tracking issue for the analyzers and code fixers that help migrate a code base to the updated memory safety rules (unsafe-v2).
Design goals carried over from #131002:
ILxxxxdiagnostic where the language deliberately does not require something.#if DEBUG) and disabled by default.Legend: 🔍 analyzer, 🔧 code fixer, ⚙️ source generator.
1. Declaring contracts on members
AddUnsafeToExternCodeFixProvider: fixesCS9389by marking anexternmemberunsafe. (Add unsafe modifier migration code fixer #131002)AddUnsafeToFieldCodeFixProvider: fixesCS9392for fields, field-backed properties and field-like events in explicit/extended-layout types. (Add unsafe modifier migration code fixer #131002)PointerSignatureRequiresUnsafeAnalyzer(IL5006) + 🔧AddUnsafeToPointerSignatureCodeFixProvider: pointer and function-pointer signatures that lost the caller-unsafe contract they had under unsafe-v1. (Add unsafe modifier migration code fixer #131002)LibraryImportRequiresExplicitSafetyAnalyzer(IL5007) + 🔧AddUnsafeToLibraryImportCodeFixProvider:[LibraryImport]methods with no explicit contract. (Support unsafe evolution in LibraryImportGenerator #131245)2. Unsafe contexts at call sites
Highest volume by far, since every consumer of a newly-
unsafeAPI breaks.IntroduceUnsafeContextCodeFixProvider: fixesCS9360,CS9361,CS9362,CS9363andCS9376by wrapping the statement inunsafe { /* SAFETY: Audit */ }, or the expression inunsafe(/* SAFETY: Audit */ ...)where a block is not valid syntax or would change meaning.3. Contract consistency
SynchronizeUnsafeContractCodeFixProvider: fixesCS9364,CS9365andCS9366(derived isunsafe, base is safe) with two actions: mark the base memberunsafewhen it is in source, or dropunsafefrom the derived member. Add unsafe contract consistency code fixes #131454MatchPartialSafetyModifierCodeFixProvider: fixesCS0764andCS9390by copying the safety modifier to the other part. Relevant because source generators author one half of those partials. Add unsafe contract consistency code fixes #131454UnsafeContractNotPropagatedAnalyzer(IL5008): an override or interface implementation left unannotated while the member it overrides or implements isunsafe. Needs its own diagnostic because narrowing to safe is legal, so the compiler is silent and migration silently drops the obligation.PropagateUnsafeContractCodeFixProvider: fixesIL5008by addingunsafe.4. Cleaning up legacy
unsafeRemoveInvalidUnsafeCodeFixProvider: fixesCS9377and unsafe-specificCS0106by removing anunsafemodifier that is now meaningless or invalid, for example on a type declaration. (Add unsafe modifier migration code fixer #131002)UnsafeMemberMissingSafetyDocumentationAnalyzer(IL5005) + 🔧RemoveUndocumentedUnsafeCodeFixProvider: removes an undocumentedunsafemodifier when it was an unsafe-v1 lexical scope rather than an intentional caller-unsafe contract. (Add unsafe modifier migration code fixer #131002)5. Safety documentation
IL5005covers signatures, but nothing covers bodies and nothing coverssafe. The speclet recommends Rust-style// SAFETYcomments, and LDM 2026-05-27 left "compiler or analyzer?" open.UnsafeBlockMissingSafetyCommentAnalyzer(IL5009): anunsafe { }block orunsafe(...)expression with no// SAFETY:comment.AddSafetyCommentCodeFixProvider: fixesIL5009by inserting a// SAFETY: TODOstub, matching the/* SAFETY: Audit */convention already used by the other fixers.SafeModifierMissingJustificationAnalyzer(IL5010): an explicitsafemodifier with no<safety>documentation. This is the symmetric hole toIL5005, sincesafeis a hand-written assertion the compiler cannot verify. It applies whereversafecan appear:externmembers and[LibraryImport]methods, fields and field-backed members in[StructLayout(LayoutKind.Explicit)]and[ExtendedLayout]types (wheresafeasserts the overlap cannot be used to type-pun, for example aliasing a reference with an integer), and any other declaration once dotnet/roslyn#84602 allowssafeas a no-op. No fixer, a human must write the justification.6. Source generator output
The common root cause for the unfixed generators below is
ContainingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifierandWriteToWithUnsafeModifier, which stampunsafeonto generated type declarations. Under unsafe-v2 that is an error, and it no longer establishes an unsafe context for the members inside, so generated bodies that dereference pointers need explicitunsafeblocks instead.LibraryImportGenerator: requires an explicit contract on the user-facing method (SYSLIB1064), keeps the hidden__PInvokecaller-unsafe, and wraps generated bodies in explicitunsafeblocks instead of relying on a type-level modifier. (Support unsafe evolution in LibraryImportGenerator #131245)ComInterfaceGeneratorandComClassGenerator: both use the type-level helper above.[GeneratedComInterface]and[GeneratedComClass]are public API shipping since .NET 8, so this is real user impact. Needs the same treatment as[LibraryImport]. Note that the generated members mostly live infile-scoped types the user cannot name, so this is largely a codegen fix rather than an analyzer one; the open design question is whether[GeneratedComInterface]methods should also require an explicitsafe/unsafecontract the way[LibraryImport]methods now do.JSImportGeneratorandJSExportGenerator: both use the type-level helper, andJSExportGeneratoradditionally stampsunsafedirectly onto a generatedClassDeclaration. Neither ever emits anunsafeblock, so every generated body relies on the type or method modifier for its unsafe context while takingJSMarshalerArgument*parameters.[JSImport]and[JSExport]are public API shipping since .NET 7.VtableIndexStubGenerator: shares the same helper.[VirtualMethodIndex]is a test asset rather than public API, so there is no external impact, but fixing the helper covers it anyway.Verified clean under
updated-memory-safety-rules, no work needed:RegexGenerator(emits[SkipLocalsInit]but nostackalloc, so the tightenedstackallocrule does not apply),JsonSourceGenerator, and theMicrosoft.Extensions.*andSystem.Private.CoreLibgenerators.Blocked / external
safeon non-externmembers. Until it merges,safecannot be spelled on a[LibraryImport]whose generated implementation is a wrapper.awaitin anunsafecontext. Gates how aggressively the call-site fixer can useunsafeblocks rather thanunsafe(...)expressions in async code.updated-memory-safety-rulesfeature flag.Diagnostic ID map
CS9360-CS9363,CS9376CS9364-CS9366unsafemember cannot override or implement a safe memberCS9377,CS0106unsafeis meaningless or invalid hereCS9388,CS9389,CS9392safe/unsaferequired, orsafeused in an invalid positionCS0764,CS9390unsafeorsafeSYSLIB1064[LibraryImport]method must be markedsafeorunsafeIL5005unsafemember has no<safety>documentationIL5006unsafeIL5007[LibraryImport]has no explicit contractIL5008IL5009unsafeblock has no// SAFETY:commentIL5010safemodifier has no justification