Skip to content

[mono] class initializaiton overlap check does not support nested structs with explicit layout #61385

@buybackoff

Description

@buybackoff

Using the new Blazor WASM template for .NET 6, this struct:

[StructLayout(LayoutKind.Explicit, Size = 24)]
public struct ComplexStruct
{
    [FieldOffset(0)]
    public object? Object;

    [FieldOffset(0)]
    public InnerStruct Inner;

    [FieldOffset(8)]
    public double Double;

    [FieldOffset(8)]
    public ulong High;

    [FieldOffset(16)]
    public ulong Low;
}

[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct InnerStruct
{
    [FieldOffset(0)]
    public object? Object;

    [FieldOffset(8)]
    public int High;

    [FieldOffset(12)]
    public int Low;
}

fails with:

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Could not load type 'ComplexStruct' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
System.TypeLoadException: Could not load type 'ComplexStruct' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
   at System.RuntimeTypeHandle.CanCastTo(RuntimeType type, RuntimeType target)
   at System.RuntimeType.IsAssignableFrom(Type c)
   at Microsoft.AspNetCore.Components.RouteTableFactory.<GetRouteableComponents>g__GetRouteableComponents|4_0(List`1 routeableComponents, Assembly assembly)
   at Microsoft.AspNetCore.Components.RouteTableFactory.GetRouteableComponents(RouteKey routeKey)
   at Microsoft.AspNetCore.Components.RouteTableFactory.Create(RouteKey routeKey)
   at Microsoft.AspNetCore.Components.Routing.Router.RefreshRouteTable()
   at Microsoft.AspNetCore.Components.Routing.Router.Refresh(Boolean isNavigationIntercepted)
   at Microsoft.AspNetCore.Components.Routing.Router.RunOnNavigateAsync(String path, Boolean isNavigationIntercepted)
   at Microsoft.AspNetCore.Components.Routing.Router.<>c__DisplayClass62_0.<RunOnNavigateAsync>b__1(RenderTreeBuilder builder)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)

when updating the counter sample like this:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount.Inner.High</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private ComplexStruct currentCount = default;

    private void IncrementCount()
    {
        var x = new ComplexStruct();
        x.Inner.High = currentCount.Inner.High + 1;
        currentCount = x;
    }
}

This is a minimal reproduction. It fails instantly during debug. When I wanted to test a real library with a complex struct like this, and compiled with <RunAOTCompilation>true</RunAOTCompilation>, it compiled and loaded but failed with the same message on click (in that case, the struct was first touched only inside another class instantiated in the counter page).

Such a struct works perfectly fine and fast on full and core .NET frameworks. Without the inner struct, the outer struct with explicit layout and union primitive fields works in WASM.

Is this expected behavior? Will it work some day or it's a WASM/Blazor limitation? The latter would be strange given that the docs claim that even native dependencies could be compiled to WASM, and C union structs are quite common.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions