Skip to content

Unsafe evolution: unsafe fields#83694

Open
jjonescz wants to merge 3 commits into
dotnet:mainfrom
jjonescz:Unsafe-35-Fields
Open

Unsafe evolution: unsafe fields#83694
jjonescz wants to merge 3 commits into
dotnet:mainfrom
jjonescz:Unsafe-35-Fields

Conversation

@jjonescz
Copy link
Copy Markdown
Member

@jjonescz jjonescz commented May 14, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 extends the C# compiler’s “unsafe evolution” implementation so that fields can be explicitly marked unsafe under the updated memory safety rules, making field access require an unsafe context via RequiresUnsafeAttribute/CallerUnsafeMode.

Changes:

  • Add field support to RequiresUnsafeAttribute and update compiler tests to validate unsafe field behavior and metadata.
  • Implement CallerUnsafeMode for fields across source, metadata, and wrapper/synthesized field symbols (including synthesis and decoding of RequiresUnsafeAttribute).
  • Adjust binder behavior so unsafe on declarations does not introduce an unsafe region under updated rules, and emit diagnostics (not just DEBUG asserts) when inline-array element fields are caller-unsafe.

Reviewed changes

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

Show a summary per file
File Description
src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs Updates the test definition of RequiresUnsafeAttribute to allow Field targets.
src/Compilers/CSharp/Test/CSharp15/UnsafeEvolutionTests.cs Adds coverage for unsafe fields, including metadata validation for PE fields.
src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs Propagates CallerUnsafeMode from the underlying field for tuple element fields.
src/Compilers/CSharp/Portable/Symbols/Tuples/TupleErrorFieldSymbol.cs Explicitly sets CallerUnsafeMode to None for error tuple fields.
src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLambdaCacheFieldSymbol.cs Sets synthesized lambda cache fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedFieldSymbol.cs Sets synthesized fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEnumValueFieldSymbol.cs Sets enum backing fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs Sets auto-property backing fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPrimaryConstructorParameterBackingFieldSymbol.cs Sets primary-ctor backing fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/SubstitutedFieldSymbol.cs Delegates CallerUnsafeMode to the underlying field for substituted fields.
src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs Implements explicit caller-unsafe fields under updated rules and synthesizes RequiresUnsafeAttribute.
src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs Ensures enum constants remain CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Symbols/Source/FieldSymbolWithAttributesAndModifiers.cs Reports ERR_RequiresUnsafeAttributeInSource when RequiresUnsafeAttribute is applied to fields in source.
src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingFieldSymbol.cs Delegates CallerUnsafeMode to the underlying field for retargeting wrappers.
src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs Decodes/filter-caches RequiresUnsafeAttribute from metadata and exposes it via CallerUnsafeMode.
src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs Removes the previous “always None” default to allow field-specific CallerUnsafeMode.
src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.FieldSymbol.cs Sets anonymous type backing fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineFieldSymbol.cs Sets state machine fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Lowering/ClosureConversion/LambdaCapturedVariable.cs Sets captured-variable fields to CallerUnsafeMode.None.
src/Compilers/CSharp/Portable/Binder/Binder_Flags.cs Updates unsafe-region introduction rules under updated memory safety rules.
src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs Emits diagnostics for unsafe inline-array element field access.
src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs Emits diagnostics for unsafe inline-array conversions via caller-unsafe element fields.

Comment thread src/Compilers/CSharp/Portable/Binder/Binder_Flags.cs
Comment thread src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Comment thread src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
@jjonescz jjonescz changed the title Unsafe evolution: allow unsafe fields Unsafe evolution: unsafe fields May 14, 2026
@jjonescz jjonescz marked this pull request as ready for review May 15, 2026 06:51
@jjonescz jjonescz requested a review from a team as a code owner May 15, 2026 06:51
@jjonescz jjonescz requested review from 333fred, AlekseyTs and Copilot May 15, 2026 06:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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

Comment thread src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs
@jjonescz
Copy link
Copy Markdown
Member Author

@333fred @AlekseyTs for reviews, thanks

@phil-allen-msft
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s), but failed to run 1 pipeline(s).

@jjonescz
Copy link
Copy Markdown
Member Author

@333fred @AlekseyTs for reviews, thanks

{
get
{
if (ContainingModule.UseUpdatedMemorySafetyRules &&
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.

If the containing module uses updated rules, shouldn't we fall into this branch always, then differentiate based on the declaration modifiers? As currently written, int* field; as a field in source will always be either CallerUnsafeMode.Explicit or CallerUnsafeMode.Implicit, whereas I'd expect it to be CallerUnsafeMode.None in such a case. SourceMethodSymbol does not do this: void M(int* i) would be CallerUnsafeMode.None when the new rules are turned on.

{
MarshalAsAttributeDecoder<FieldWellKnownAttributeData, AttributeSyntax, CSharpAttributeData, AttributeLocation>.Decode(ref arguments, AttributeTargets.Field, MessageProvider.Instance);
}
else if (attribute.IsTargetAttribute(AttributeDescription.RequiresUnsafeAttribute))
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.

We should probably just add RequiresUnsafeAttribute to ReservedAttributes below.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a slightly better diagnostics experience though since it tells you what to use instead (the unsafe keyword) which the generic diagnostic cannot. We have specialized diagnostics like this for other features too.

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.

I see FixedSizeBuffer_SafeContext; is there a test with an unsafe fixed-size buffer?


public override ImmutableArray<CustomModifier> RefCustomModifiers => ImmutableArray<CustomModifier>.Empty;

internal override CallerUnsafeMode CallerUnsafeMode => CallerUnsafeMode.None;
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.

Suggested change
internal override CallerUnsafeMode CallerUnsafeMode => CallerUnsafeMode.None;
internal sealed override CallerUnsafeMode CallerUnsafeMode => CallerUnsafeMode.None;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The containing class is already sealed.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants