Skip to content

Devirtualization in CrossGen2 for internal virtual overrides mismatches runtime behaviour  #60969

Description

@AnakinRaW

Description

While playing around with the virtual method resolution algorithm in the crossgen project I noticed that the resolution algorithm does not handle a rare (synthetical) scenario where overriding methods are internal and made available for certain subclasses via the attribute InternalsVisibleTo.

While the runtime handles this accordingly with a MethodAccessException the method resolution for crossgen apparently does consider accessibility (at least it seems like it).

Reproduction Steps

I created the following assemblies:

.assembly A
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.InternalsVisibleToAttribute::.ctor(string) = ( 01 00 01 42 00 00 )                               // ...B..
}
.mscorlib
.class public auto ansi beforefieldinit A.ClassA
{
  .method assembly hidebysig newslot strict virtual 
          instance void  M() cil managed { }
}
.assembly extern A {}
.assembly B {}
.mscorlib
.class public auto ansi beforefieldinit B.ClassB
       extends [A]A.ClassA { }
.assembly extern A { }
.assembly C { }
.mscorlib
.class public auto ansi beforefieldinit C.ClassC
       extends [A]A.ClassA { }

I conducted the following test case:

var aAssm = _context.GetModuleForSimpleName("A");
var bAssm = _context.GetModuleForSimpleName("B");
var cAssm = _context.GetModuleForSimpleName("C");
MetadataType a = aAssm.GetType("A", "ClassA");
MetadataType b = bAssm.GetType("B", "ClassB");
MetadataType c = cAssm.GetType("C", "ClassC");
MethodDesc aMethod = a.GetMethods().Where(m => m.Name == "M").Single();

{
    var result = new DevirtualizationManager().ResolveVirtualMethod(aMethod, a, out _); // Returns [A]AClass::M
    result = new DevirtualizationManager().ResolveVirtualMethod(aMethod, b, out _); // Returns [A]AClass::M
    result = new DevirtualizationManager().ResolveVirtualMethod(aMethod, c, out _); // Returns [A]AClass::M
}

Expected behavior

Running the same assemblies with with a given Main method in ClassC

newobj     instance void C.ClassC::.ctor()
callvirt   instance void [A]A.ClassA::M()

yields a System.MethodAccessException: Attempt by method 'C.ClassC.Main(System.String[])' to access method 'A.ClassA.M()' failed., as expected.

However when the provided test case above returns [A]A.ClassA::M(). The out parameter of the devirtualization has default value CORINFO_DEVIRTUALIZATION_UNKNOWN.

The Roslyn Compiler guys already covered this issue to explicitly deny compilation in such a scenario. so maybe this helps in case my own words are too confusing ;)
https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs#L2044

Actual behavior

The result value on the 3rd case is expected to be null because at runtime such an operation would fail in a MethodAccessException.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions