Skip to content

VerificationException from StructureMarshaler<T> on any trimmed app marshalling a non-blittable struct array (regression in P5) #128027

Description

@simonrozsival

Trimmed apps using the .NET 11 P5 runtime throw System.Security.VerificationException from System.StubHelpers.StubHelpers whenever they marshal an array of a non-blittable struct via [MarshalAs(UnmanagedType.LPArray)] / [MarshalAs(UnmanagedType.ByValArray)] / Marshal.StructureToPtr etc.

This is the same family of bug as #127952 (which is being addressed by #128020 for the DateMarshaler case). The fix in #128020 does not cover StructureMarshaler<T>, which is the marshaller used for any user-defined non-blittable struct array — the much more common scenario.

Impact

Every net11.0-android Release build crashes at startup, because Java.Interop's JNIEnvInit.Initialize registers JNI methods via Java.Interop.JniEnvironment.Types._RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], int) — an LPArray of the non-blittable struct JniNativeMethodRegistration — before any user code runs.

Reproduced with:

  • A bare dotnet new android project (no dependencies)
  • A real shipping app (Microsoft Seeing AI for Android)

The Android failure stack:

System.TypeInitializationException: TypeInitialization_Type, Java.Interop.ManagedPeer
 ---> System.Security.VerificationException: Method System.StubHelpers.StubHelpers.ConvertArraySpaceToNative:
      type argument 'System.StubHelpers.StructureMarshaler`1[Java.Interop.JniNativeMethodRegistration]'
      violates the constraint of type parameter 'TMarshaler'.
   at Java.Interop.JniEnvironment.Types._RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], Int32)
   at Java.Interop.JniEnvironment.Types.RegisterNatives(...)
   at Java.Interop.JniType.RegisterNativeMethods(...)
   at Java.Interop.ManagedPeer..cctor()
   at Android.Runtime.JNIEnvInit.Initialize(JnienvInitializeArgs*)

This will break every MAUI and dotnet-android customer that picks up a P5 nightly. The same regression also affects iOS/tvOS Release builds (which is how #127952 was first noticed).

Minimal repro

Program.cs:

using System;
using System.Runtime.InteropServices;

var structure = new StructWithNonBlittableArray
{
    array = new NonBlittableStruct[]
    {
        new() { Name = "name", Signature = "()V", Marshaler = (Action)(() => { }) },
    },
};

int size = Marshal.SizeOf(structure);
IntPtr memory = Marshal.AllocHGlobal(size);
try
{
    Marshal.StructureToPtr(structure, memory, false);
    Marshal.StructureToPtr(structure, memory, true);
}
finally
{
    Marshal.DestroyStructure(memory, structure.GetType());
    Marshal.FreeHGlobal(memory);
}

return 100;

public struct NonBlittableStruct
{
    public string Name;
    public string Signature;
    public Delegate Marshaler;
}

public struct StructWithNonBlittableArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public NonBlittableStruct[] array;
}

repro.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
    <SelfContained>true</SelfContained>
  </PropertyGroup>
</Project>
$ dotnet publish -c Release
$ ./bin/Release/net11.0/osx-arm64/publish/repro
Unhandled exception. System.Security.VerificationException: Method System.StubHelpers.StubHelpers.FreeArrayContents:
    type argument 'System.StubHelpers.StructureMarshaler`1[NonBlittableStruct]'
    violates the constraint of type parameter 'TMarshaler'.
   at System.StubHelpers.StructureMarshaler`1.FreeCore(...)
   at System.StubHelpers.StructureMarshaler`1.Free(...)
   at System.StubHelpers.StructureMarshaler`1.ConvertToUnmanaged(...)
   at System.StubHelpers.BoxedLayoutTypeMarshaler`1.ConvertToUnmanaged(...)
   at System.Runtime.InteropServices.Marshal.LayoutTypeMarshalerMethods.ConvertToUnmanaged(...)
   at System.Runtime.InteropServices.Marshal.StructureToPtr(Object, IntPtr, Boolean)

A trimming test mirroring this repro is attached as a draft PR (companion to #128020's MarshalStructureToPtrByValDateArray.cs).

Bisect

SDK Build date Relative to #126911 Result
11.0.100-preview.3.26207.106 2026-04-14 before ✅ exit 100
11.0.100-preview.5.26258.110 2026-05-08 after ❌ VerificationException

Regression introduced by #126911 ("Move built-in array marshalling to managed and make olevariant.cpp Windows-only"). Decompiling the trimmed System.Private.CoreLib.dll shows the smoking gun:

Untrimmed (pack ships this):

.class private auto ansi sealed beforefieldinit System.StubHelpers.StructureMarshaler`1<T>
    extends System.Object
    implements class System.StubHelpers.IArrayElementMarshaler`2<!T, class System.StubHelpers.StructureMarshaler`1<!T>>,
               class System.StubHelpers.IArrayMarshaler`2<!T, class System.StubHelpers.StructureMarshaler`1<!T>>

After ILLink (in app's linked/System.Private.CoreLib.dll):

.class private auto ansi sealed beforefieldinit System.StubHelpers.StructureMarshaler`1<T>
    extends System.Object
    // interface implementations are gone

The runtime's IL-stub generator references StructureMarshaler<T> by name from native code (CLASS__STRUCTURE_MARSHALER in src/coreclr/vm/corelib.h) and emits a call to ConvertArraySpaceToNative<T, StructureMarshaler<T>> / FreeArrayContents<T, StructureMarshaler<T>> whose generic constraint is where TMarshaler : IArrayMarshaler<T, TMarshaler>. Once the linker strips the interface implementations from StructureMarshaler<T>, the constraint check fails at runtime.

Proposed fix

Extend src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml (the same file PR #128020 modifies) to also preserve StructureMarshaler<T> and BlittableArrayMarshaler<T>:

<type fullname="System.StubHelpers.IArrayMarshaler`2" preserve="all" />
<type fullname="System.StubHelpers.IArrayElementMarshaler`2" preserve="all" />
<type fullname="System.StubHelpers.DateMarshaler" preserve="all" />
<type fullname="System.StubHelpers.StructureMarshaler`1" preserve="all" />     <!-- NEW -->
<type fullname="System.StubHelpers.BlittableArrayMarshaler`1" preserve="all" /> <!-- NEW -->

Verified locally — adding these entries makes the minimal repro return 100 and unblocks the Android dotnet new android Release build.

More generally, any other StubHelpers type whose name is hard-coded in src/coreclr/vm/corelib.h and used as a TMarshaler constraint argument is at risk. Worth a one-pass audit of all classes added in #126911 to make sure they're all in the shared descriptor.

Environment

  • Microsoft.NETCore.App.Runtime 11.0.0-preview.5.26258.110
  • macOS 26.4 / arm64; Samsung A16 / android-arm64 / API 36
  • Reproduces with both Release default (CoreCLR + JIT, no AOT, no R2R) and any trim configuration (TrimMode=full, TrimMode=copyused, etc.)
  • Does not require AOT, R2R, or NativeAOT
  • Does not reproduce with PublishTrimmed=false

cc @jkoritzinsky @AaronRobinsonMSFT @dotnet/interop-contrib

Activity

  1. dotnet-policy-service commented on May 11, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'linkable-framework': @eerhardt, @vitek-karas, @LakshanF, @sbomer, @joperezr
    See info in area-owners.md if you want to be subscribed.

  2. dotnet-policy-service commented on May 11, 2026

    @dotnet-policy-service
    Contributor

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

  3. added a commit that references this issue on May 13, 2026
  4. added a commit that references this issue on Jun 23, 2026
  5. locked and limited conversation to collaborators on Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions