ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

ILLink: codefix for C# unsafe evolution - #128304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope
Closed

ILLink: codefix for C# unsafe evolution#128304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:unsafe-to-scope

Conversation

@EgorBo

@EgorBoEgorBo commented May 17, 2026

Copy link
Copy Markdown
Member

What

A migrator for the C# unsafe evolution feature (updated memory-safety rules), built as DEBUG-only ILLink analyzers + code fixers and driven by dotnet format. It mechanically moves existing code to the new rules by (1) removing unsafe where it no longer has meaning and (2) introducing minimalunsafe contexts where the new rules now require them.

Two diagnostics + fixers, both gated behind the EnableUnsafeAnalyzer MSBuild property and the updated-memory-safety-rules compiler feature, so they are inert in normal builds:

  • IL5005 – UnnecessaryUnsafeModifier — removes unsafe from class/struct/interface/record/delegate, from static constructors and destructors (unconditionally), and from other members when no unmanaged pointer appears in their signature. This also resolves the new "unsafe member cannot override a safe member" (CS9364) cases.
  • IL5006 – OperationRequiresUnsafeContext — wraps operations that now need an unsafe context in either an unsafe { } block (comment // SAFETY: Audit) or a minimal unsafe(expr) expression (comment /* SAFETY: Audit */).

Strategy

Removing unsafe (IL5005) — a modifier-only edit. Types/delegates/static-ctors/destructors are always removable; other members keep unsafe only if an unmanaged pointer is in the signature (the heuristic for "genuinely caller-unsafe"). extern members are left untouched.

Adding an unsafe context (IL5006) — prefers the minimal form:

  • unsafe(expr)expression by default, and always where a block is impossible or would change scope: await operands, catch filters, field/property/constructor initializers, lambda/query bodies, using/ref/scoped locals, out var/pattern variables, and across #if directives.
  • unsafe { }block only when the value is void or the operation sits at the very start of its statement (a bare unsafe(...) can't begin a statement). A void expression-bodied member (void M() => VoidCall();) is converted to a block body.
  • The engine both re-surfaces the compiler's own missing-context diagnostics (CS9360/CS9362/CS9363) and independently detects the stackallocSpan under SkipLocalsInit case (CS9361), which a code-analysis workspace's semantic model doesn't reliably report. It is idempotent (never double-wraps) and never shortens an existing scope.

Because removing a modifier exposes body operations, run IL5005 to a fixpoint first, then IL5006, each until --verify-no-changes is clean.

Two small workflow enablers are included so the migrator can actually run on inbox libraries:

  • global.json is bumped to an SDK whose Roslyn understands unsafe(...).
  • eng/references.targets gains an env-var-gated (UnsafeMigrationRefSwap) swap so dotnet format's MSBuildWorkspace can resolve inbox-library references from the prebuilt ref pack + CoreLib ref assembly (otherwise it fails to load them). Inert in normal builds.

Validation

  • All ILLink analyzer unit tests pass (1151), including new tests for both fixers and the tricky cases (stackalloc, out var scope preservation, void expression-bodied members).
  • Ran end-to-end on System.Text.Json (-f net11.0): converges to 0 remaining IL5005/IL5006, after which STJ compiles under the new rules with 0 errors / 0 warnings.

Steps to run it (System.Text.Json, net11.0)

Use an SDK whose Roslyn implements unsafe evolution (the one this PR's global.json points to) for every command.

  1. Build the analyzer + code fixer:
    dotnet build src/tools/illink/src/ILLink.CodeFix/ILLink.CodeFixProvider.csproj -c Debug
    
  2. Temporarily enable the new rules on STJ — add to the first <PropertyGroup> of src/libraries/System.Text.Json/src/System.Text.Json.csproj:
    <TargetFrameworksCondition="'$(UnsafeMigration)' == 'true'">$(NetCoreAppCurrent)</TargetFrameworks>
    <FeaturesCondition="'$(UnsafeMigration)' == 'true'">$(Features);updated-memory-safety-rules</Features>
    <LangVersionCondition="'$(UnsafeMigration)' == 'true'">preview</LangVersion>
    <EnableUnsafeAnalyzerCondition="'$(UnsafeMigration)' == 'true'">true</EnableUnsafeAnalyzer>
  3. Run the migrator (IL5005 to a fixpoint, then IL5006 — re-run until --verify-no-changes reports none):
    $env:UnsafeMigration="true"; $env:UnsafeMigrationRefSwap="true"
    1..3 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 --severity info --no-restore }
    1..8 | % { dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5006 --severity info --no-restore }
    dotnet format analyzers ./src/libraries/System.Text.Json/src/System.Text.Json.csproj --diagnostics IL5005 IL5006 --severity info --no-restore --verify-no-changes
    
  4. Verify it compiles under the new rules (against prebuilt dependencies, using the same Roslyn dotnet format used). Turn UnsafeMigrationRefSwapoff first — it exists only so dotnet format's MSBuildWorkspace can resolve references; if left set it corrupts a real build with duplicate System.Runtime/System.Private.CoreLib definitions (CS0433/CS0518). Keep UnsafeMigration=true so the feature stays on. RunAnalyzers=false avoids an unrelated CA1510 from the SDK analyzers:
    Remove-Item Env:\UnsafeMigrationRefSwap
    dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj -c Debug -t:Rebuild /p:BuildProjectReferences=false /p:UsingToolMicrosoftNetCompilers=false /p:RunAnalyzers=false
    

Note

This PR description and the code changes were generated by GitHub Copilot.

CopilotAI review requested due to automatic review settings May 17, 2026 20:12
@dotnet-policy-servicedotnet-policy-serviceBot added the linkable-framework Issues associated with delivering a linker friendly framework label May 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

CopilotAI 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

This PR adds a new ILLink Roslyn analyzer + code fix (IL5005) intended to move the unsafe member modifier into a method-/accessor-scoped unsafe { ... } block, and wires an MSBuild property to enable the analyzer. It also applies the fixer output broadly across System.Text.Json and some shared library code.

Changes:

  • Add IL5005 (UnsafeModifierOnMethod) analyzer and code fix provider, plus associated resource strings and MSBuild property plumbed through ILLink analyzer infrastructure.
  • Update build logic (eng/liveILLink.targets) and System.Text.Json project settings to enable the analyzer.
  • Mechanical rewrites across many BCL files replacing unsafe modifiers with method-body unsafe { ... } blocks and inserting // SAFETY-TODO comments.

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/tools/illink/src/ILLink.Shared/SharedStrings.resxAdds IL5005 title/message strings.
src/tools/illink/src/ILLink.Shared/DiagnosticId.csIntroduces DiagnosticId.UnsafeModifierOnMethod = 5005 (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeModifierOnMethodAnalyzer.csNew analyzer that reports IL5005 on members using the unsafe modifier (when enabled).
src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.csAdds MSBuild property name constant to enable the new analyzer (DEBUG-only).
src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.propsMakes the new MSBuild property compiler-visible.
src/tools/illink/src/ILLink.CodeFix/UnsafeModifierOnMethodCodeFixProvider.csNew code fix to move unsafe into the body and insert audit comments.
src/tools/illink/src/ILLink.CodeFix/Resources.resxAdds the code fix title resource.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.csApplies fixer output: wraps stackalloc usage in unsafe {} and adds SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.StringSegment.csApplies fixer output to multiple writer helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.csApplies fixer output to string escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.csApplies fixer output for numeric formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.csApplies fixer output around transcoding + pooled buffer logic.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.csApplies fixer output for float formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.csApplies fixer output for double formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.csApplies fixer output for decimal formatting paths.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.csApplies fixer output in property-name numeric writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.csApplies fixer output in literal property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.csApplies fixer output in Guid property-name writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.csApplies fixer output in formatted-number property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.csApplies fixer output in float property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.csApplies fixer output in double property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.csApplies fixer output in decimal property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.csApplies fixer output in DateTimeOffset property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.csApplies fixer output in DateTime property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.csApplies fixer output in base64 property writers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.csApplies fixer output in writer start-property escaping helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Date.csApplies fixer output in Date/DateTimeOffset trim formatting helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.csApplies fixer output in string quoting/escaping helper.
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.csApplies fixer output in stackalloc-based Truncate helper.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.csApplies fixer output in UTF-16->UTF-8 transcoding read helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.csApplies fixer output in TimeSpan converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.csApplies fixer output in TimeOnly converter stackalloc paths.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.csApplies fixer output in converter read/write helpers using stackalloc / pools.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.csApplies fixer output in converter read/write helpers and constant parsing.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.csApplies fixer output around ValueStringBuilder(stackalloc) usage.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.csApplies fixer output in DateOnly converter stackalloc formatting.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.csApplies fixer output around stackalloc + CopyString.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.csApplies fixer output around escape handling / parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.csApplies fixer output in multi-segment literal validation helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csApplies fixer output in ValueTextEquals transcoding helper.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.csApplies fixer output across unescaping and base64 helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.netstandard.csApplies fixer output in netstandard span scanning helper (vectorized path).
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.csApplies fixer output in escaped DateTime/Guid parsing helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.csApplies fixer output to GetPath, introducing unsafe {} + SAFETY-TODO.
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.csApplies fixer output in escaping helpers using stackalloc/ArrayPool.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csApplies fixer output in TranscodeAndEncode stackalloc/ArrayPool path.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.csApplies fixer output in property lookup helpers.
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.csApplies fixer output in TextEquals transcoding helper.
src/libraries/System.Text.Json/src/System.Text.Json.csprojEnables the new analyzer via project property.
src/libraries/System.Private.CoreLib/src/System/Text/Rune.csApplies fixer output to remove unsafe modifier and wrap bodies in unsafe {}.
src/libraries/Common/src/System/Text/AsciiPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/StringBuilderPolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/SinglePolyfills.csApplies fixer output to move unsafe from signature into body.
src/libraries/Common/src/Polyfills/EncodingPolyfills.csApplies fixer output to move unsafe from signatures into bodies and wrap pointer helpers.
eng/liveILLink.targetsTreats the new MSBuild property as requiring live ILLink wiring.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 21:48

CopilotAI 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 55 out of 55 changed files in this pull request and generated 5 comments.

Comment threadsrc/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs Outdated
CopilotAI review requested due to automatic review settings May 17, 2026 22:31

CopilotAI 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 59 out of 59 changed files in this pull request and generated 4 comments.

Comment threadsrc/libraries/System.Text.Json/src/System.Text.Json.csproj
Comment threadsrc/tools/illink/src/ILLink.Shared/SharedStrings.resx Outdated

CopilotAI 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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment threadsrc/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeV2MigrationAnalyzer.cs Outdated
@EgorBo

EgorBo commented May 19, 2026

Copy link
Copy Markdown
MemberAuthor

PTAL @agocke@jkotas@jjonescz@tannergooding@333fred
I think this blocks us from adopting the new rules: we don't want to enable them when suddenly thousands of functions where unsafe was put on the modifier level for "enable global unsafe context" become caller-unsafe.

I tested it on STJ and it worked as I expected, it gave up on a few cases (see desc.) but I was able to easily fix those by hand.
If someone wants to make it "smallest possible scope" - feel free to take over, in my opinion it's very hard and adds a lot of mess:

  • we need wrap every pointer dereference and there was an interesting case in @richlander's example on what is the minimal scope for ptr[2] = 0, by definination it should be tmp = ptr + 2; unsafe { *tmp = 0; }
  • In many cases it requires splitting many expressions into separate variable declarations
  • One way or another, it will be audited by a human (or AI?) to fix the // SAFETY comment and adjust the scope

We might need unsafe-as-expression for it then.

CopilotAI review requested due to automatic review settings June 4, 2026 23:31
@EgorBoEgorBo changed the title CodeFixer: move unsafe from method modifier to method bodyILLink: codefix for C# unsafe evolutionJun 4, 2026
CopilotAI review requested due to automatic review settings June 5, 2026 15:05

CopilotAI 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 12 out of 12 changed files in this pull request and generated 3 comments.

EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jun 5, 2026
…ests, polish
Comments addressed:
* IntroduceUnsafeBlockCodeFixProvider now handles expression-bodied members. Diagnostics in 'int M() => UnsafeCall();', 'int P => UnsafeCall();', etc. previously offered no fix because FindContainingStatement returned null. The fixer now also walks for an enclosing ArrowExpressionClauseSyntax and, when found, rewrites the member to a block body with 'unsafe { /* SAFETY-TODO */ return expr; }' (or 'expr;' for void/Task/set/init/add/remove/ctor/dtor). Properties/indexers with arrow bodies are converted to explicit 'get { ... }' accessors.
* Added EventFieldDeclaration / EventDeclaration tests for IL5006 -- the analyzer has registered for those syntax kinds but no test covered them.
* Removed misleading 'fall back to wrap-as-is' comment on the ForwardDeclare defensive path -- the implementation actually bails out unchanged (wrap-as-is would not be safe because the local escapes past the wrap point, which is why we picked ForwardDeclare). Comment updated to match behavior.
* Removed unused 'using System;' from RemoveUnsafeModifierCodeFixTests.cs.
All 51 UnsafeEvolution tests pass (was 46; +2 event tests, +3 arrow-body tests).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (modifiers.Any(SyntaxKind.ExternKeyword))
return false;

// Partial members require both halves to agree on 'unsafe'; we can't fix one safely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we still produce the diagnostic at least on those parts that have a body? Compiler diagnostic will then ensure the parts match.


// ---- Wrapping an expression-bodied member ----

private static async Task<Document> WrapArrowBodyAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unsafe expressions (dotnet/roslyn#84012) should be merged soon, so maybe we don't need this churn?

return false;

// Be conservative for members nested inside a type that also carries 'unsafe' - the
// type-level IL5005 will fire on the containing type, which is the better fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And then we re-run the fix for the unsafe on the member again? Why not just run both fixes together?


<!-- Keep this in sync with the '#if DEBUG' gate on UnsafeEvolutionAnalyzer and its
descriptors: AnalyzerReleases.Unshipped.md declares IL5005/IL5006, which are
only supported when the analyzer is compiled in Debug.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to eventually move this analyzer into NetAnalyzers so others could also use it?

Otherwise it feels like this PR doesn't even have to be merged, you can just use it to migrate (and modify as you discover cases it doesn't handle for example) and the migration is what should be reviewed.

if (n is StatementSyntax statement)
return statement;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

if (n is ArrowExpressionClauseSyntax arrow)
return arrow;
if (n is AnonymousFunctionExpressionSyntax)
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we handle local functions similarly?

// Skip statements whose tokens enclose a preprocessor directive (e.g. an argument
// list with #if/#else/#endif between commas). Wrapping such a statement would
// corrupt the directive region.
if (UnsafeBlockHelpers.ContainsInternalDirectiveTrivia(containingStatement))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we check this for expression-bodied members too?

public void M2()
{
using var stream = M1();
stream.Flush();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a case where the variable isn't referenced, like:

usingvarscope=M1();DoWork();

Are we fine with shortening the lifetime to the following?

unsafe
{usingvarscope=M1();}DoWork();

CopilotAI review requested due to automatic review settings June 22, 2026 20:00

CopilotAI 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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +403 to +408
// Build 'return <expr>;' or '<expr>;' depending on the member's effective return type.
// Preserve the original expression's trivia inside the new statement so any inline
// comments authored on the arrow expression survive.
StatementSyntax inner = requiresReturn
? SyntaxFactory.ReturnStatement(arrow.Expression.WithoutTrivia())
: SyntaxFactory.ExpressionStatement(arrow.Expression.WithoutTrivia());
Comment on lines +91 to +103
// Expression-bodied members (e.g. 'int M() => Helper();') have no enclosing
// statement. If we can rewrite the arrow body into a block body, offer that fix.
var arrow = FindContainingArrowBody(node);
if (arrow is null)
return;

var semanticModelForArrow = await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
context.RegisterCodeFix(
CodeAction.Create(
title: WrapStatementTitle,
createChangedDocument: ct => WrapArrowBodyAsync(document, arrow, semanticModelForArrow, ct),
equivalenceKey: WrapStatementTitle),
diagnostic);
DEBUG-only ILLink analyzers + code fixers, driven by 'dotnet format analyzers --diagnostics IL5005 IL5006', that migrate source to the updated memory-safety rules: IL5005 removes no-longer-needed 'unsafe' modifiers; IL5006 introduces minimal 'unsafe { }' blocks or 'unsafe(...)' expressions. Gated behind EnableUnsafeAnalyzer + the updated-memory-safety-rules feature, so inert in normal builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
egorboand others added 2 commits July 9, 2026 02:43
…table locals
The IL5006 code fixer now rewrites a local whose initializer needs an unsafe
context (e.g. 'Span<byte> x = stackalloc byte[n];') into a bare 'scoped'
declaration plus an 'unsafe { }' block that performs the assignment, giving the
SAFETY comment its own clean line. Ref-struct locals (identified by a stackalloc
initializer) get 'scoped'.
It falls back to the 'unsafe(...)' expression form when splitting would narrow
the escape scope - i.e. when a value derived from the local is returned, assigned
to a wider-scoped target, or passed by ref/out. The escape check is purely
syntactic because the migration's reference-swapped compilation can't be trusted
for semantic type resolution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:12

CopilotAI 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 15 out of 15 changed files in this pull request and generated 2 comments.

Comment threadsrc/tools/illink/src/ILLink.CodeFix/UnsafeContextStrategy.cs Outdated
Comment on lines +58 to +62
<!-- Unsafe-evolution migration only (opt-in via the UnsafeMigration property, typically an env var).
'dotnet format' loads the runtime's inbox libraries (and CoreLib) as live workspace ProjectReferences,
which produce no usable metadata references and leave the compilation without core types, so the
IL5005/IL5006 analyzers cannot run. For the migration we compile against the prebuilt reference pack and
CoreLib assembly instead. This is inert in normal builds. -->
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 9, 2026 07:19

CopilotAI 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 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +63 to +71
<ItemGroup Condition="'$(UnsafeMigrationRefSwap)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '@(ProjectReference)' != ''">
<ProjectReference Remove="$(LibrariesProjectRoot)*\src\*.csproj" />
<ProjectReference Remove="$(LibrariesProjectRoot)*\ref\*.csproj" />
<ProjectReference Remove="$(CoreLibProject)" />
<Reference Include="$(MicrosoftNetCoreAppRefPackRefDir)*.dll"
Exclude="$(MicrosoftNetCoreAppRefPackRefDir)$(MSBuildProjectName).dll"
Private="false" />
<Reference Include="$(ArtifactsBinDir)System.Private.CoreLib\ref\$(Configuration)\$(NetCoreAppCurrent)\System.Private.CoreLib.dll" Private="false" />
</ItemGroup>
Comment on lines +49 to +50
internal static bool IsSpanType(ITypeSymbol? type) =>
type is INamedTypeSymbol { IsGenericType: true, Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" };
Comment on lines +61 to +64
return compilation.SourceModule.GetAttributes().Any(IsSkipLocalsInit);

static bool IsSkipLocalsInit(AttributeData attribute) => attribute.AttributeClass?.Name == "SkipLocalsInitAttribute";
}
@EgorBo

Copy link
Copy Markdown
MemberAuthor

A fresh start: #130611

@EgorBoEgorBo closed this Jul 13, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Text.Jsonlinkable-frameworkIssues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@jjonescz