Skip to content

[NativeAOT] Missing stack trace records for method with a generic template without scanner #121093

@SingleAccretion

Description

@SingleAccretion

This was hit downstream in some XUnit async code in libraries tests. The small reproduction is as follows:

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

internal class Program
{
    public static void Main(string[] args)
    {
        // Expose 'ReflectedMethod'
        typeof(Program).GetMethod("ReflectedMethod", BindingFlags.Static | BindingFlags.NonPublic);
        ReflectedMethod<string>();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void ReflectedMethod<T>()
    {
        NonReflectedGenericMethod<T>();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void NonReflectedGenericMethod<T>()
    {
        Console.WriteLine(typeof(T));
        Console.WriteLine(Environment.StackTrace);
    }
}
> dotnet publish -r win-x64 /p:PublishAot=true -c debug
> .\bin\debug\net10.0\win-x64\publish\TestConsole.exe
System.String
   at System.Environment.get_StackTrace() + 0x33
   at TestConsole!<BaseAddress>+0x1d917f
   at Program.ReflectedMethod[T]() + 0x25
   at Program.Main(String[] args) + 0x5e

Notice at TestConsole!<BaseAddress>+0x1d917f that should be at Program.NonReflectedGenericMethod[T]() + 0x1e:

> dotnet publish -r win-x64 /p:PublishAot=true -c release
System.String
   at System.Environment.get_StackTrace() + 0x21
   at Program.NonReflectedGenericMethod[T]() + 0x1e
   at Program.ReflectedMethod[T]() + 0x11
   at Program.Main(String[] args) + 0x29

The underlying ILC logic goes something like this:

  1. ReflectedMethod gets reflection-exposed.
  2. This causes us to generate generic templates for NonReflectedGenericMethod (its callee).
  3. This adds a 'limited' MethodMetadataNode node to the graph (GetNativeLayoutMetadataDependencies).
  4. This causes the "metadata transform" to consider the method included in the metadata produced.
  5. This causes it to be excluded it from the normal ST mappings (
    // Methods that will end up in the reflection invoke table should not have an entry in stack trace table
    // We'll try looking them up in reflection data at runtime.
    if (transformed.GetTransformedMethodDefinition(typicalMethod) != null &&
    ShouldMethodBeInInvokeMap(method) &&
    (GetMetadataCategory(method) & MetadataCategory.RuntimeMapping) != 0)
    continue;
    ).
  6. But we don't actually generate any invoke map entries for it, since that's not needed.

It seems it would be nice to avoid this kind of discrepancy by construction by more directly connecting the code which determines which methods go into the invoke map with the stack trace mappings code.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions