Skip to content

.NET 8 ReadyToRun regression bug: TypeLoadException  #96225

@bitbonk

Description

@bitbonk

Description

When publishing an application with ReadyToRun in .NET 8, that includes the below C# type hierarchy, the following exception is thrown during the start of the published application:

System.TypeLoadException: Return type in method 'ReadyToRunRepro.DataElementBase`1[TIdentifier].get_Id()' on type 'ReadyToRunRepro.DataElementBase`1[TIdentifier]' from assembly 'ReadyToRunRepro, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with base type method 'ReadyToRunRepro.DataElementBase.get_Id()

The application with the same type hierarchy works without problems in .NET 7.

The type hierarchy:

public abstract class DataElementBase
{
    private protected DataElementBase(IdentifierBase id)
    {
        this.Id = id;
    }

    public virtual IdentifierBase Id { get; }
}

public abstract class DataElementBase<TIdentifier> : DataElementBase
    where TIdentifier : DataElementIdentifierBase
{
    private protected DataElementBase(
        TIdentifier id)
        : base(id)
    {
    }
    
    public override TIdentifier Id => (TIdentifier) base.Id;
}

public sealed class EventDefinition : DataElementBase<EventDefinitionIdentifier>
{
    internal EventDefinition(EventDefinitionIdentifier id)
        : base(id)
    {
    }
}

public abstract class EventDefinitionBase : DataElementBase<EventDefinitionIdentifier>
{
    private protected EventDefinitionBase(
        NodeIdentifier nodeId,
        EventSettingsBase settings,
        Type? argumentType = null)
        : base(
            new EventDefinitionIdentifier(nodeId))
    {
    }

    protected internal abstract EventSettingsBase Settings { get; }
}

public abstract class EventDefinitionBase<TSettings> : EventDefinitionBase
    where TSettings : EventSettingsBase
{
    private protected EventDefinitionBase(
        NodeIdentifier nodeId,
        TSettings settings,
        Type? argumentType = null)
        : base(nodeId, settings, argumentType)
    {
        this.Settings = settings;
    }

    protected internal override TSettings Settings { get; }
}

public abstract class IdentifierBase
{
}

public sealed class NodeIdentifier : IdentifierBase, IEquatable<NodeIdentifier>
{
    public bool Equals(NodeIdentifier? other)
    {
        return true;
    }
}

public abstract class OwnedIdentifierBase<TOwner> : IdentifierBase, IEquatable<OwnedIdentifierBase<TOwner>>
    where TOwner : IdentifierBase
{
    public OwnedIdentifierBase(TOwner owner)
    {
        this.Owner = owner;
    }

    public TOwner Owner { get; }

    public bool Equals(OwnedIdentifierBase<TOwner>? other)
    {
        return true;
    }
}

public abstract class DataElementIdentifierBase : OwnedIdentifierBase<NodeIdentifier>
{
    protected DataElementIdentifierBase(NodeIdentifier owner)
        : base(owner)
    {
    }
}

public abstract record DataElementSettingsBase
{
    private protected DataElementSettingsBase()
    {
    }
}

public sealed class EventDefinitionIdentifier : DataElementIdentifierBase
{
    public EventDefinitionIdentifier(NodeIdentifier owner)
        : base(owner)
    {
    }
}

public abstract record EventSettingsBase : DataElementSettingsBase;

public record EventSettings : EventSettingsBase;

Reproduction Steps

  1. Clone the repo https://github.com/bitbonk/ReadyToRunRepro and run

    dotnet publish .\ReadyToRunRepro\ReadyToRunRepro.csproj --configuration=Release -p:PublishReadyToRun=true --runtime win-x64 --self-contained

    followed by

    .\ReadyToRunRepro\bin\Release\net7.0\win-x64\publish\ReadyToRunRepro.exe

  2. See that the worker service starts correctly and runs without errors

  3. Change the target framework in ReadyToRunRepro.csproj to .NET 8: <TargetFramework>net8.0</TargetFramework>

  4. Publish again with the same command as in 1.:

    dotnet publish .\ReadyToRunRepro\ReadyToRunRepro.csproj --configuration=Release -p:PublishReadyToRun=true --runtime win-x64 --self-contained

    and then start the published .NET 8 exe:

    .\ReadyToRunRepro\bin\Release\net8.0\win-x64\publish\ReadyToRunRepro.exe

Expected behavior

The worker service application published with a .NET 8 TFM starts and runs without problems.

Actual behavior

the following exception occurs:

Unhandled exception. System.TypeLoadException: Return type in method 'ReadyToRunRepro.DataElementBase`1[TIdentifier].get_Id()' on type 'ReadyToRunRepro.DataElementBase`1[TIdentifier]' from assembly 'ReadyToRunRepro, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with base type method 'ReadyToRunRepro.DataElementBase.get_Id()'.
   at ReadyToRunRepro.Service..ctor()
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at Program.<Main>$(String[] args) in C:\source\ReadyToRunRepro\ReadyToRunRepro\Program.cs:line 8

Regression?

Yes this is a regression, since it works with a .NET 7 TFM.

Known Workarounds

none is known

Configuration

No response

Other information

Deleting either one of these three lines (commented with in code deleting this line will make the error go away) will make the problem go away:

  • in Service.cs:

    private readonly NestedDictionary<NodeIdentifier, EventDefinitionIdentifier, EventDefinitionBase> dict = new();
  • in Worker.cs

    this.logger.LogInformation("Service {service} acquired", this.service);
  • in Types.cs:

    public record EventSettings : EventSettingsBase;
    

See also https://github.com/bitbonk/ReadyToRunRepro/pull/2/files which contains step 3 and 4 of the reproduction steps.
You can just switch to branch net8 and run buildandrun.ps1 to see the error.

Metadata

Metadata

Assignees

Labels

area-ReadyToRun-coreclrin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions