Skip to content

Fix System.Text.Json source-generated unsafe accessors - #133599

Merged
eiriktsarpalis merged 5 commits into
dotnet:mainfrom
eiriktsarpalis:stj/fix-ctor-generic-accessors
Sep 14, 2026
Merged

eiriktsarpalis merged 5 commits into
dotnet:mainfrom
eiriktsarpalis:stj/fix-ctor-generic-accessors

Conversation

@eiriktsarpalis

@eiriktsarpalis eiriktsarpalis commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

Fixes a number of codegen issues related to the source generator's handling of newly introduced unsafe accessors. To be backported to .NET 11.

Fixes #133369. Fixes #133423. Fixes #133592.

Preserve open generic constructor signatures and share indexed generic
helpers with member accessors. Use reflection for generic layouts that
the existing helper scheme cannot support, preserving user exceptions
and updates to boxed struct fields.

Emit feature-gated safety annotations and enable the updated memory
safety rules across System.Text.Json test projects. Add public-API
coverage for constructors, inherited members, and struct accessors.

Fix dotnet#133369
Fix dotnet#133423
Fix dotnet#133592

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2af8f83f-b01f-4ae3-b171-750c75bfde9e
Copilot AI lite review requested due to automatic review settings September 10, 2026 14:22
@eiriktsarpalis eiriktsarpalis added area-System.Text.Json needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet labels Sep 10, 2026
@eiriktsarpalis
eiriktsarpalis deployed to copilot-pat-pool September 10, 2026 14:22 — with GitHub Actions Active
@azure-pipelines

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

@eiriktsarpalis
eiriktsarpalis deployed to copilot-pat-pool September 10, 2026 14:23 — with GitHub Actions Active
@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.

1 similar comment
@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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

It appears to introduce/expand public API surface in the source-generator model types (including new required members) without any linked api-approved issue, which is a merge-blocking compatibility/process concern.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates the System.Text.Json source generator and its tests to improve handling of inaccessible members/constructors on generic types, reduce generated helper collisions, and make generated/accessor code compatible with the updated-memory-safety-rules compiler feature.

Changes:

  • Extend generator model/spec metadata to track open generic signatures, declaring-type hierarchy index, target framework, and memory-safety feature usage.
  • Update generator emission for generic unsafe accessor wrappers (including constructors) and adjust reflection fallback invocation to preserve original exceptions.
  • Enable updated-memory-safety-rules across System.Text.Json test projects and update/expand tests + baselines accordingly.
File summaries
File Description
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.Values.StringSegment.cs Wraps reinterpret-cast spans in unsafe(...) for updated memory-safety rules.
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs Adjusts unsafe pointer dereference usage to comply with updated memory-safety rules.
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj Enables updated-memory-safety-rules feature for the main STJ test project.
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonEncodedTextTests.cs Updates overridden unsafe methods to use unsafe(...) expressions under new rules.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.targets Enables updated-memory-safety-rules for source-gen unit test compilation.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/Baselines/UnsafeAccessors_InaccessibleConstructor/net462/MyContext.InaccessibleCtor.g.cs.txt Updates baseline to use unwrapped reflection invocation for ctor exceptions.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/Baselines/UnsafeAccessors_InaccessibleConstructor/net462/MyContext.g.cs.txt Updates baseline to include new InvokeUnwrapped helper.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/Baselines/UnsafeAccessors_GenericTypeInitOnlyProperties/netcoreapp/MyContext.GenericPropsInt32.g.cs.txt Updates baseline for renamed/disambiguated generic accessor helper naming.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets Enables updated-memory-safety-rules and unsafe blocks for source-gen functional tests.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs Adds coverage for generic contexts/nesting and additional inaccessible-member scenarios.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs Adds coverage for generic types with inaccessible [JsonConstructor] constructors.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/NETStandardContextTests.cs Adds NETStandard coverage for exception preservation with reflection accessors.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs Narrows unsafe usage around Unsafe.Unbox to satisfy updated rules.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/TestClasses.cs Adds new test types and includes them in the NETStandard serializer context.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/System.Text.Json.TestLibrary.targets Enables updated-memory-safety-rules for the netstandard test library build.
src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs Adds new generic/non-public constructor test types and imports needed attributes.
src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs Expands common non-public accessor test matrix (generic structs, fields, inheritance).
src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs Adds init-only roundtrip coverage for generic color type (NET-only).
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs Adds regression coverage for preserving original exceptions from non-public ctors.
src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs Adds generic non-public-ctor roundtrip coverage and required/init-only scenarios.
src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets Includes the new generator model file in compilation.
src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs Adds open-generic/type-parameter metadata for constructor accessor emission on generic types.
src/libraries/System.Text.Json/gen/Model/TargetFramework.cs Introduces a target-framework discriminator used by emission logic.
src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs Adds declaring-type hierarchy index and refines open-type metadata for generic member accessors.
src/libraries/System.Text.Json/gen/Model/ParameterGenerationSpec.cs Adds open-parameter-type metadata for generic constructor accessor signatures.
src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs Adds target framework and updated memory-safety feature flags to the context spec.
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs Computes open-generic metadata, declaring-type indices, and feature flags during parsing.
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Updates emission for safe extern accessors, generic ctor wrappers, and exception-unwrapping reflection invokes.
src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs Detects consumer target framework via core library identity to guide emission.
Review details
  • Files reviewed: 29/29 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Outdated
Comment thread src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs
Comment thread src/libraries/System.Text.Json/gen/Model/TargetFramework.cs Outdated
@eiriktsarpalis eiriktsarpalis removed the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Sep 10, 2026
@EgorBo

EgorBo commented Sep 10, 2026

Copy link
Copy Markdown
Member

@eiriktsarpalis with the new rules, the fact that Unsafe.Unbox is used in the generated code now forces users to enable <AllowUnsafeBlocks>true. Is it possible to remove it?

Comment thread src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs Outdated
@eiriktsarpalis

eiriktsarpalis commented Sep 10, 2026 •

Copy link
Copy Markdown
Member Author

@eiriktsarpalis with the new rules, the fact that Unsafe.Unbox is used in the generated code now forces users to enable <AllowUnsafeBlocks>true. Is it possible to remove it?

@EgorBo I don't think that it is. The problem fundamentally is the shape of STJ's setter delegate which uses object for the declaring type. To my knowledge, this offers no better alternative for supporting structs :/

@agocke

agocke commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

@eiriktsarpalis Separately, does the generator have a way to disable unsafe code emit? Unsafe code without AllowUnsafeBlocks is not allowed in the v2 safety rules and there will certainly be some users who are not allowed to use unsafe code via policy restriction. Do we have a way to make sure that those people can still use the source generator in supported configurations that don't require unsafe?

Copilot AI review requested due to automatic review settings September 10, 2026 21:09
@eiriktsarpalis

Copy link
Copy Markdown
Member Author

Separately, does the generator have a way to disable unsafe code emit?

Not really, but doing so would necessitate having a fallback available. Unsafe accessors (only used in scenaria where a customer has explicitly opted into an inaccessible field via the JsonInclude attribute) could be replaced with reflection I suppose (this fallback already exists for netfx targets) but I know of no safe workaround for mutating boxed structs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The generated generic unsafe-constructor wrappers must preserve ref-kind modifiers before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 27/27 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Outdated
@agocke

agocke commented Sep 10, 2026

Copy link
Copy Markdown
Member

. Unsafe accessors (only used in scenaria where a customer has explicitly opted into an inaccessible field via the JsonInclude attribute)

I think this is basically the answer -- you have to opt-in to a specific feature to get unsafe code emitted. I think that's OK for now.

@EgorBo

EgorBo commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

. Unsafe accessors (only used in scenaria where a customer has explicitly opted into an inaccessible field via the JsonInclude attribute)

I think this is basically the answer -- you have to opt-in to a specific feature to get unsafe code emitted. I think that's OK for now.

@eiriktsarpalis @agocke just to make sure I understand. This minimal totally safely looking code:

using System.Text.Json.Serialization;

public struct Point
{
    public int X { get; set; }
}

[JsonSerializable(typeof(Point))]
internal partial class MyContext : JsonSerializerContext { }

currently complains with:

error CS9362: 'Unsafe.Unbox<Point>(object)' must be used in an unsafe context

While SG should wrap it with the unsafe {} what this PR does, it also means the user has to enable AllowUnsafeBlocks on their side.

Copilot AI review requested due to automatic review settings September 14, 2026 12:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

RoslynExtensions binds an enum-returning getter to Func<IModuleSymbol, int>, which can fail generator initialization.

Review details

Suppressed comments (1)

src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs:43

  • IModuleSymbol.MemorySafetyRulesVersion returns the MemorySafetyRulesVersion enum, not int. MethodInfo.CreateDelegate requires a compatible return type and does not reinterpret an enum as its underlying integer, so this cast throws during RoslynExtensions type initialization on compilers that expose the new API; the generator then fails before emitting code. Adapt the reflected result (or use a compatible adapter) instead of binding the getter directly to Func<IModuleSymbol, int>.
                : (Func<IModuleSymbol, int>)getter.CreateDelegate(typeof(Func<IModuleSymbol, int>));
  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@eiriktsarpalis

Copy link
Copy Markdown
Member Author

/ba-g test failure is unrelated to STJ.

@eiriktsarpalis
eiriktsarpalis enabled auto-merge (squash) September 14, 2026 15:11
@eiriktsarpalis
eiriktsarpalis merged commit a3bff54 into dotnet:main Sep 14, 2026
80 of 82 checks passed
@eiriktsarpalis
eiriktsarpalis deleted the stj/fix-ctor-generic-accessors branch September 14, 2026 15:13
@eiriktsarpalis

Copy link
Copy Markdown
Member Author

/backport to release/11.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0 (link to workflow run)

@agocke

agocke commented Sep 14, 2026

Copy link
Copy Markdown
Member

@eiriktsarpalis @agocke just to make sure I understand. This minimal totally safely looking code

Yep, that one's the real problem.

Yeah this is something we'll need to fix for 12.

@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 15, 2026
artl93 pushed a commit that referenced this pull request Sep 15, 2026
…#133886)

Backport of #133599 to release/11.0

/cc @eiriktsarpalis

## Customer Impact

- [x] Customer reported
- [ ] Found internally

Address two closely related customer reported issues:

1. Corner cases not accounted for in the newly introduced unsafe
accessor support in the STJ source generator.
2. STJ source generator codegen is incompatible with unsafe v2
semantics. Enabling unsafe v2 in projects using the source generator
will frequently result in uncompilable code without possibility for
workarounds.

## Regression

- [x] Yes
- [ ] No

#124650 introduced unsafe accessor
suppor in .NET 11.

## Testing

Added comprehensive testing covering the impacted corner cases. Enabled
unsafe v2 in the STJ test projects to ensure it is being appropriately
dogfed.

## Risk

Medium. The fix involves nontrivial changes to the source generator
emitter.

Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2af8f83f-b01f-4ae3-b171-750c75bfde9e
svick added a commit that referenced this pull request Sep 16, 2026
Reconciles the shared UnsafeAccessorEmitter extraction with #133599 (fix STJ
source-generated unsafe accessors). The only conflict was
JsonSourceGenerator.Emitter.cs; it was resolved by taking main's post-#133599
accessor code and re-applying the extraction on top.

The #133599 improvements are folded into the shared helper so STJ output stays
byte-identical:
- safe/unsafe extern modifier, threaded as a useUpdatedMemorySafetyRules bool
  parameter on EmitMemberAccessors/EmitConstructorAccessor (STJ computes the
  flag at parse time and stores it on ContextGenerationSpec);
- DeclaringTypeIndex disambiguator in the generic wrapper class name;
- generic constructor accessor emitted inside a partial
  __GenericAccessors_<name>_0 wrapper shared with the member accessors;
- ref/out/in constructor parameters (including the reflection fallback that
  writes back ref/out args);
- field reflection-fallback and open-type fallbacks.

STJ's GenerateMemberAccessors/GenerateConstructorAccessor remain thin adapters
that build the neutral specs and delegate to the helper; the moved name/dup
helpers and the GenericAccessorEntry alias are removed. Both Roslyn versions
build clean and the SourceGeneratedOutputTests baselines are byte-identical.
svick added a commit to svick/runtime that referenced this pull request Sep 17, 2026
svick added a commit to svick/runtime that referenced this pull request Sep 17, 2026
Now that the shared UnsafeAccessorEmitter emits the constructor extern inside a
generic wrapper class (reconciled with dotnet#133599), the config binder can construct
generic required-member types through an [UnsafeAccessor(Constructor)] accessor
on .NET 9+ instead of falling back to reflection.

- Drop the !IsGenericType gate on ConstructorCanUseUnsafeAccessor; a generic type
  now qualifies when generic UnsafeAccessor is supported and it is not nested in a
  generic type, matching the STJ parser logic.
- Thread the type's open-generic definition (type-parameter names, open FQN,
  constraint clauses) and each constructor parameter's open type FQN to the helper
  so the wrapper and its extern are emitted with open types.
- Thread DeclaringTypeIndex through init-only setter accessors so the generic
  wrapper class name matches the shared helper's __GenericAccessors_<name>_<index>
  scheme, and adopt the safe/unsafe extern modifier by computing the compilation's
  updated-memory-safety-rules flag at parse time (stored on SourceGenerationSpec).

Adds a functional test binding a generic type with a required init-only member,
exercising the generic constructor + setter accessor wrappers; it passes under
both the source-generated and reflection binders. Only the netcoreapp
UnsupportedTypes baseline changes, adopting the _<index> wrapper name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1a0d7ac2-f2a3-4526-93ab-36bf1a23933f
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Sep 18, 2026
Fixes a number of codegen issues related to the source generator's
handling of newly introduced unsafe accessors. To be backported to .NET
11.

Fixes dotnet#133369. Fixes dotnet#133423. Fixes dotnet#133592.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2af8f83f-b01f-4ae3-b171-750c75bfde9e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

8 participants