dotnet / dotnet/runtime

NativeAOT: `IDynamicInterfaceCastable` ignored — `is` returns false and the cast crashes

Open
#133,525 1 comment 0 reactions 0 assignees View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

## Repro

```csharp
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// IMarker's only implementor is sealed.
interface IMarker { }
sealed class Ordinary : IMarker { }

// IMarker2's implementor has a subclass (control case).
interface IMarker2 { }
class OpenBase : IMarker2 { }
sealed class OpenDerived : OpenBase { }

sealed class Dynamic : IDynamicInterfaceCastable
{
public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
=> interfaceType.Equals(typeof(IMarker).TypeHandle)
|| interfaceType.Equals(typeof(IMarker2).TypeHandle);

public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
=> throw new NotSupportedException();
}

static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
static bool Is(object value) => value is IMarker;

[MethodImpl(MethodImplOptions.NoInlining)]
static bool IsControl(object value) => value is IMarker2;

[MethodImpl(MethodImplOptions.NoInlining)]
static string Cast(object value)
{
try { return ((IMarker)value) is null ? "null" : "ok"; }
catch (InvalidCastException) { return "InvalidCastException"; }
}

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
static bool Baseline(object value) => value is IMarker;

static int Main()
{
object value = new Dynamic();

Console.WriteLine($"sanity (Ordinary is IMarker): {Is(new Ordinary())}");
Console.WriteLine($"baseline (no optimization) : {Baseline(value)}");
Console.WriteLine($"control (non-exact impl) : {IsControl(value)}");
Console.WriteLine($"optimized (is IMarker) : {Is(value)}");
Console.WriteLine($"optimized (cast to IMarker) : {Cast(value)}");
return 0;
}
}
```

Add to the project file:

```xml
true
```

## Expected

```
sanity (Ordinary is IMarker): True
baseline (no optimization) : True
control (non-exact impl) : True
optimized (is IMarker) : True
optimized (cast to IMarker) : ok
```

This is what CoreCLR prints for the same IL.

## Actual

```
sanity (Ordinary is IMarker): True
baseline (no optimization) : True
control (non-exact impl) : True
optimized (is IMarker) : False
```

The last line never prints; the process exits with `-2147483645` (`0x80000003`) inside `Cast`.

## Platform

Windows x64, NativeAOT with the stock .NET 11.0.100-rc.1.26425.128 SDK. CoreCLR is unaffected.

Contributor guide

Open the contributing guide

Research direction

Start by running the supplied C# repro with DynamicInterfaceCastableSupport enabled and compare the NativeAOT output with CoreCLR. Check the optimized `is` and cast paths represented by `Is` and `Cast`; done means NativeAOT reports `True` and `ok` without terminating inside the cast.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.