From c2898a3d582364130f1805bf7a181f612f2eb043 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:51:05 +0000 Subject: [PATCH 1/4] Initial plan From 2fec7f8992f5ed26814b4a830fca83354f67f9e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:27:58 +0000 Subject: [PATCH 2/4] Fix DestroyStructure_Blittable_Success: skip NativeMemory.Clear for blittable 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 (initialized via Marshal.HasLayout) 2. Adding IsBlittable property to LayoutClassMarshaler.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> --- .../src/System/StubHelpers.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index bdd9d25dc3ee0c..d7a1c86e1d7dd7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1402,6 +1402,17 @@ internal static class MarshalOperation internal static unsafe class StructureMarshaler where T : notnull { + // Blittable types have a no-op FreeCore (the [Intrinsic] C# body is used) and need no NativeMemory.Clear. + // Non-blittable types have a JIT-generated FreeCore stub and require NativeMemory.Clear after cleanup. + private static readonly bool s_isBlittable = ComputeIsBlittable(); + + private static bool ComputeIsBlittable() + { + RuntimeType type = (RuntimeType)typeof(T); + Marshal.HasLayout(new QCallTypeHandle(ref type), out bool isBlittable, out _); + return isBlittable; + } + [Conditional("DEBUG")] private static void Validate() { @@ -1457,7 +1468,10 @@ private static void FreeCore(ref T managed, byte* unmanaged, ref CleanupWorkList public static void Free(ref T managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) { - if (unmanaged != null) + // 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 && !s_isBlittable) { FreeCore(ref managed, unmanaged, ref cleanupWorkList); NativeMemory.Clear(unmanaged, (nuint)nativeSize); @@ -1519,6 +1533,8 @@ private static void BlittableFree(ref byte managed, byte* unmanaged, ref Cleanup internal static delegate* ConvertToManaged => _convertToManaged; internal static delegate* Free => _free; + + internal static bool IsBlittable => s_nativeSizeForBlittableTypes != 0; } private static void ConvertToUnmanagedCore(T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) @@ -1600,7 +1616,10 @@ static void CallFree(T? managed, byte* unmanaged, ref CleanupWorkListElement? cl public static void Free(T? managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) { - if (unmanaged != null) + // 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) { FreeCore(managed, unmanaged, ref cleanupWorkList); NativeMemory.Clear(unmanaged, (nuint)nativeSize); From a830c1b7dc293db1a30b890a7db93befe794a6b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:29:46 +0000 Subject: [PATCH 3/4] Rename ComputeIsBlittable to InitIsBlittable and add comment to Methods.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> --- src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index d7a1c86e1d7dd7..c08eec09af240c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1404,9 +1404,9 @@ internal static unsafe class StructureMarshaler where T : notnull { // Blittable types have a no-op FreeCore (the [Intrinsic] C# body is used) and need no NativeMemory.Clear. // Non-blittable types have a JIT-generated FreeCore stub and require NativeMemory.Clear after cleanup. - private static readonly bool s_isBlittable = ComputeIsBlittable(); + private static readonly bool s_isBlittable = InitIsBlittable(); - private static bool ComputeIsBlittable() + private static bool InitIsBlittable() { RuntimeType type = (RuntimeType)typeof(T); Marshal.HasLayout(new QCallTypeHandle(ref type), out bool isBlittable, out _); @@ -1534,6 +1534,7 @@ private static void BlittableFree(ref byte managed, byte* unmanaged, ref Cleanup internal static delegate* Free => _free; + // s_nativeSizeForBlittableTypes is non-zero for blittable types and zero for non-blittable types. internal static bool IsBlittable => s_nativeSizeForBlittableTypes != 0; } From 844dcd899ef2e9bb3d25b34931034d50d93b6f25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 02:43:45 +0000 Subject: [PATCH 4/4] Wrap IsBlittable access in no-inline method with TypeInitializationException 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> --- .../src/System/StubHelpers.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index c08eec09af240c..890bff5ec3c7ec 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1615,12 +1615,28 @@ static void CallFree(T? managed, byte* unmanaged, ref CleanupWorkListElement? cl } } + private static bool GetIsBlittable() + { + try + { + return CallIsBlittable(); + } + catch (TypeInitializationException ex) + { + ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw(); + return false; // unreachable + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CallIsBlittable() => Methods.IsBlittable; + } + public static void Free(T? managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) { // 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) + if (unmanaged != null && !GetIsBlittable()) { FreeCore(managed, unmanaged, ref cleanupWorkList); NativeMemory.Clear(unmanaged, (nuint)nativeSize);