Skip to content

Fix NullReferenceException in DestroyStructure for blittable types#126795

Open
Copilot wants to merge 4 commits intomainfrom
copilot/fix-destroystructure-blittable-test
Open

Fix NullReferenceException in DestroyStructure for blittable types#126795
Copilot wants to merge 4 commits intomainfrom
copilot/fix-destroystructure-blittable-test

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

Description

BoxedLayoutTypeMarshaler<T> (introduced in #126621) routes DestroyStructure for blittable value types through StructureMarshaler<T>.Free, which unconditionally called NativeMemory.Clear(ptr, size) even when FreeCore was a no-op. Passing an invalid pointer like (IntPtr)1 — the longstanding test pattern for "blittable types should be a no-op" — faults at address 1, manifesting as a NullReferenceException on Android.

Changes

  • StructureMarshaler<T>: Added a s_isBlittable static field (initialized once via Marshal.HasLayout). Free() now skips both FreeCore and NativeMemory.Clear for blittable types. For non-blittable types (array marshaling path via NonBlittableStructureArrayFree), the JIT generates a FreeCore IL stub that frees native sub-structures; NativeMemory.Clear still follows to prevent double-free.

  • LayoutClassMarshaler<T>.Methods: Added IsBlittable property using the existing s_nativeSizeForBlittableTypes field (non-zero ⟺ blittable). Access to IsBlittable is wrapped in a GetIsBlittable() method that follows the same [MethodImpl(MethodImplOptions.NoInlining)] + try/catch (TypeInitializationException) pattern used by all other Methods accessors, ensuring TypeLoadException is surfaced instead of TypeInitializationException on recursive native layout failures. Free() is now a no-op for blittable reference types — same fix, same reasoning.

Testing

Existing DestroyStructure_Blittable_Success test covers the regression. Full System.Runtime.InteropServices.Tests suite: 2549 passed, 0 failed.

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

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

…littable types in StructureMarshaler and LayoutClassMarshaler

For blittable types, FreeCore is a no-op (uses the C# body since TryGenerateStructMarshallingMethod
returns false for blittable types). Calling NativeMemory.Clear on a potentially invalid pointer
(like (IntPtr)1 used in tests) causes a NullReferenceException/fault.

Fix by:
1. Adding s_isBlittable static field to StructureMarshaler<T> (initialized via Marshal.HasLayout)
2. Adding IsBlittable property to LayoutClassMarshaler<T>.Methods (using existing s_nativeSizeForBlittableTypes)
3. Skipping FreeCore and NativeMemory.Clear in both Free() methods for blittable types

This restores the original behavior where DestroyStructure is a no-op for blittable types.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f610b358-8378-4bba-b4dd-98615601bdeb

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 11, 2026 20:28
…ds.IsBlittable

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f610b358-8378-4bba-b4dd-98615601bdeb

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 11, 2026 20:29
Copilot AI changed the title [WIP] Fix null reference exception in DestroyStructure_Blittable_Success test Fix NullReferenceException in DestroyStructure for blittable types Apr 11, 2026
Copilot AI requested a review from jkoritzinsky April 11, 2026 20:34
@am11
Copy link
Copy Markdown
Member

am11 commented Apr 11, 2026

Failure seems related https://helix.dot.net/api/2019-06-17/jobs/ac25ae7c-952e-4650-900f-5f3fcb70ec27/workitems/Interop/consol

Test FAIL: Xunit.Sdk.ThrowsException: Assert.Throws() Failure: Exception type was not an exact match
Expected: typeof(System.TypeLoadException)
Actual:   typeof(System.TypeInitializationException)
   at Xunit.Assert.ThrowsImpl(Type exceptionType, Exception exception, Func`2 inspector) in /_/src/arcade/src/Microsoft.DotNet.XUnitAssert/src/ExceptionAsserts.cs:line 509
   at MarshalArrayAsField.LPArray.Test.RunTest2(String report)
   at MarshalArrayAsField.LPArray.Test.TestEntryPoint()
Xunit.Sdk.EqualException: Assert.Equal() Failure: Values differ
Expected: 100
Actual:   101
   at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in /_/src/arcade/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs:line 102
   at Program.<<Main>$>g__TestExecutor41|0_42(StreamWriter tempLogSw, StreamWriter statsCsvSw, <>c__DisplayClass0_0&)
21:55:07.441 Failed test: global::MarshalArrayAsField.LPArray.Test.TestEntryPoint()
21:55:07.446 Running test: global::MarshalArrayAsParam.Default.ArrayMarshal.TestMarshalByVal_NoAttributes()
ByVal marshaling CLR array as c-style-array no attributes

// For blittable types, FreeCore is a no-op and there are no native sub-structures to free.
// Calling NativeMemory.Clear on a potentially invalid pointer (e.g., in DestroyStructure tests)
// would cause a fault, so we skip cleanup entirely for blittable types.
if (unmanaged != null && !Methods.IsBlittable)
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.

@copilot Please wrap the access to IsBlittable in a no-inline property that does the same try-catch-rethrow pattern as all other accessors to the Methods subclass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 844dcd8. Added a GetIsBlittable() method with the same try/catch (TypeInitializationException)/ExceptionDispatchInfo.Capture(...).Throw() pattern and a [MethodImpl(MethodImplOptions.NoInlining)] local CallIsBlittable() function accessing Methods.IsBlittable, matching all other accessors in the class.

…ception try-catch pattern

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f3d7123a-231f-4ec5-9460-294f83aeb412

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 12, 2026 02:43
Copilot AI requested a review from jkoritzinsky April 12, 2026 02:44
@jkoritzinsky jkoritzinsky marked this pull request as ready for review April 12, 2026 03:14
Copilot AI review requested due to automatic review settings April 12, 2026 03:14
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

Fixes a crash in Marshal.DestroyStructure for blittable layout types by avoiding NativeMemory.Clear on potentially invalid pointers (e.g., (IntPtr)1), and preserves the existing “unwrap TypeInitializationException” behavior for blittable detection in the layout-class marshaler.

Changes:

  • Skip FreeCore/NativeMemory.Clear in StructureMarshaler<T>.Free when T is blittable (computed once via Marshal.HasLayout).
  • Add Methods.IsBlittable + GetIsBlittable() wrapper in LayoutClassMarshaler<T> and make Free a no-op for blittable layout classes.
  • Keep NativeMemory.Clear for non-blittable types to prevent double-free after nested cleanup.

@jkoritzinsky jkoritzinsky enabled auto-merge (squash) April 12, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

System.Runtime.InteropServices.Tests.DestroyStructureTests.DestroyStructure_Blittable_Success failing in CI

5 participants