Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0be8947

Browse files
Fix reflection to work with default interface methods (#16034)
Fixes #15645. Fixes #15644.
1 parent 616fdb2 commit 0be8947

File tree

13 files changed

+477
-25
lines changed

13 files changed

+477
-25
lines changed

src/mscorlib/src/System/RtType.cs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -580,26 +580,25 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
580580

581581
#region Loop through all methods on the interface
582582
Debug.Assert(!methodHandle.IsNullHandle());
583-
// Except for .ctor, .cctor, IL_STUB*, and static methods, all interface methods should be abstract, virtual, and non-RTSpecialName.
584-
// Note that this assumption will become invalid when we add support for non-abstract or static methods on interfaces.
583+
584+
MethodAttributes methodAttributes = RuntimeMethodHandle.GetAttributes(methodHandle);
585+
586+
#region Continue if this is a constructor
585587
Debug.Assert(
586-
(RuntimeMethodHandle.GetAttributes(methodHandle) & (MethodAttributes.RTSpecialName | MethodAttributes.Abstract | MethodAttributes.Virtual)) == (MethodAttributes.Abstract | MethodAttributes.Virtual) ||
587-
(RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.Static) == MethodAttributes.Static ||
588-
RuntimeMethodHandle.GetName(methodHandle).Equals(".ctor") ||
589-
RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor") ||
590-
RuntimeMethodHandle.GetName(methodHandle).StartsWith("IL_STUB", StringComparison.Ordinal));
588+
(RuntimeMethodHandle.GetAttributes(methodHandle) & MethodAttributes.RTSpecialName) == 0 ||
589+
RuntimeMethodHandle.GetName(methodHandle).Equals(".cctor"));
590+
591+
if ((methodAttributes & MethodAttributes.RTSpecialName) != 0)
592+
continue;
593+
#endregion
591594

592595
#region Calculate Binding Flags
593-
MethodAttributes methodAttributes = RuntimeMethodHandle.GetAttributes(methodHandle);
594596
bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
595597
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
596598
bool isInherited = false;
597599
BindingFlags bindingFlags = RuntimeType.FilterPreCalculate(isPublic, isInherited, isStatic);
598600
#endregion
599601

600-
if ((methodAttributes & MethodAttributes.RTSpecialName) != 0)
601-
continue;
602-
603602
// get the unboxing stub or instantiating stub if needed
604603
RuntimeMethodHandleInternal instantiatedHandle = RuntimeMethodHandle.GetStubIfNeeded(methodHandle, declaringType, null);
605604

@@ -2747,15 +2746,19 @@ public override InterfaceMapping GetInterfaceMap(Type ifaceType)
27472746
Debug.Assert(ifaceMethodBase is RuntimeMethodInfo);
27482747
im.InterfaceMethods[i] = (MethodInfo)ifaceMethodBase;
27492748

2750-
// If the slot is -1, then virtual stub dispatch is active.
2751-
int slot = GetTypeHandleInternal().GetInterfaceMethodImplementationSlot(ifaceRtTypeHandle, ifaceRtMethodHandle);
2749+
// If the impl is null, then virtual stub dispatch is active.
2750+
RuntimeMethodHandleInternal classRtMethodHandle = GetTypeHandleInternal().GetInterfaceMethodImplementation(ifaceRtTypeHandle, ifaceRtMethodHandle);
27522751

2753-
if (slot == -1) continue;
2752+
if (classRtMethodHandle.IsNullHandle())
2753+
continue;
27542754

2755-
RuntimeMethodHandleInternal classRtMethodHandle = RuntimeTypeHandle.GetMethodAt(this, slot);
2755+
// If we resolved to an interface method, use the interface type as reflected type. Otherwise use `this`.
2756+
RuntimeType reflectedType = RuntimeMethodHandle.GetDeclaringType(classRtMethodHandle);
2757+
if (!reflectedType.IsInterface)
2758+
reflectedType = this;
27562759

27572760
// GetMethodBase will convert this to the instantiating/unboxing stub if necessary
2758-
MethodBase rtTypeMethodBase = RuntimeType.GetMethodBase(this, classRtMethodHandle);
2761+
MethodBase rtTypeMethodBase = RuntimeType.GetMethodBase(reflectedType, classRtMethodHandle);
27592762
// a class may not implement all the methods of an interface (abstract class) so null is a valid value
27602763
Debug.Assert(rtTypeMethodBase == null || rtTypeMethodBase is RuntimeMethodInfo);
27612764
im.TargetMethods[i] = (MethodInfo)rtTypeMethodBase;

src/mscorlib/src/System/RuntimeHandles.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,11 @@ internal void VerifyInterfaceIsImplemented(RuntimeTypeHandle interfaceHandle)
358358
}
359359

360360
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
361-
private extern static int GetInterfaceMethodImplementationSlot(RuntimeTypeHandle handle, RuntimeTypeHandle interfaceHandle, RuntimeMethodHandleInternal interfaceMethodHandle);
361+
private extern static RuntimeMethodHandleInternal GetInterfaceMethodImplementation(RuntimeTypeHandle handle, RuntimeTypeHandle interfaceHandle, RuntimeMethodHandleInternal interfaceMethodHandle);
362362

363-
internal int GetInterfaceMethodImplementationSlot(RuntimeTypeHandle interfaceHandle, RuntimeMethodHandleInternal interfaceMethodHandle)
363+
internal RuntimeMethodHandleInternal GetInterfaceMethodImplementation(RuntimeTypeHandle interfaceHandle, RuntimeMethodHandleInternal interfaceMethodHandle)
364364
{
365-
return GetInterfaceMethodImplementationSlot(GetNativeHandle(), interfaceHandle.GetNativeHandle(), interfaceMethodHandle);
365+
return GetInterfaceMethodImplementation(GetNativeHandle(), interfaceHandle.GetNativeHandle(), interfaceMethodHandle);
366366
}
367367

368368
[MethodImplAttribute(MethodImplOptions.InternalCall)]

src/vm/ecalllist.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ FCFuncStart(gCOMTypeHandleFuncs)
267267
FCFuncElement("_GetMetadataImport", RuntimeTypeHandle::GetMetadataImport)
268268
FCFuncElement("GetNumVirtuals", RuntimeTypeHandle::GetNumVirtuals)
269269
QCFuncElement("VerifyInterfaceIsImplemented", RuntimeTypeHandle::VerifyInterfaceIsImplemented)
270-
QCFuncElement("GetInterfaceMethodImplementationSlot", RuntimeTypeHandle::GetInterfaceMethodImplementationSlot)
270+
QCFuncElement("GetInterfaceMethodImplementation", RuntimeTypeHandle::GetInterfaceMethodImplementation)
271271
FCFuncElement("IsComObject", RuntimeTypeHandle::IsComObject)
272272
FCFuncElement("IsValueType", RuntimeTypeHandle::IsValueType)
273273
FCFuncElement("IsInterface", RuntimeTypeHandle::IsInterface)

src/vm/runtimehandles.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,11 +1181,11 @@ void QCALLTYPE RuntimeTypeHandle::VerifyInterfaceIsImplemented(EnregisteredTypeH
11811181
END_QCALL;
11821182
}
11831183

1184-
INT32 QCALLTYPE RuntimeTypeHandle::GetInterfaceMethodImplementationSlot(EnregisteredTypeHandle pTypeHandle, EnregisteredTypeHandle pOwner, MethodDesc * pMD)
1184+
MethodDesc* QCALLTYPE RuntimeTypeHandle::GetInterfaceMethodImplementation(EnregisteredTypeHandle pTypeHandle, EnregisteredTypeHandle pOwner, MethodDesc * pMD)
11851185
{
11861186
QCALL_CONTRACT;
11871187

1188-
INT32 slotNumber = -1;
1188+
MethodDesc* pResult = nullptr;
11891189

11901190
BEGIN_QCALL;
11911191

@@ -1199,11 +1199,11 @@ INT32 QCALLTYPE RuntimeTypeHandle::GetInterfaceMethodImplementationSlot(Enregist
11991199
//@TODO: be done faster - just need to make a function FindDispatchDecl.
12001200
DispatchSlot slot(typeHandle.GetMethodTable()->FindDispatchSlotForInterfaceMD(thOwnerOfMD, pMD));
12011201
if (!slot.IsNull())
1202-
slotNumber = slot.GetMethodDesc()->GetSlot();
1202+
pResult = slot.GetMethodDesc();
12031203

12041204
END_QCALL;
12051205

1206-
return slotNumber;
1206+
return pResult;
12071207
}
12081208

12091209
void QCALLTYPE RuntimeTypeHandle::GetDefaultConstructor(EnregisteredTypeHandle pTypeHandle, QCall::ObjectHandleOnStack retMethod)

src/vm/runtimehandles.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class RuntimeTypeHandle {
248248
void QCALLTYPE VerifyInterfaceIsImplemented(EnregisteredTypeHandle pTypeHandle, EnregisteredTypeHandle pIFaceHandle);
249249

250250
static
251-
INT32 QCALLTYPE GetInterfaceMethodImplementationSlot(EnregisteredTypeHandle pTypeHandle, EnregisteredTypeHandle pOwner, MethodDesc * pMD);
251+
MethodDesc* QCALLTYPE GetInterfaceMethodImplementation(EnregisteredTypeHandle pTypeHandle, EnregisteredTypeHandle pOwner, MethodDesc * pMD);
252252

253253
static FCDECL3(FC_BOOL_RET, GetFields, ReflectClassBaseObject *pType, INT32 **result, INT32 *pCount);
254254

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Reflection;
7+
8+
class Program
9+
{
10+
static int Main()
11+
{
12+
bool failed = false;
13+
14+
{
15+
var map = typeof(Fooer).GetInterfaceMap(typeof(IFoo<Fooer>));
16+
17+
int foundMatchMask = 0;
18+
19+
MethodInfo ifooDefaultMethod = typeof(IFoo<Fooer>).GetMethod("DefaultMethod");
20+
MethodInfo ifooOtherMethod = typeof(IFoo<Fooer>).GetMethod("OtherMethod");
21+
MethodInfo ibarOtherMethod = typeof(IBar<Fooer>).GetMethod("OtherMethod");
22+
23+
for (int i = 0; i < map.InterfaceMethods.Length; i++)
24+
{
25+
MethodInfo declMethod = map.InterfaceMethods[i];
26+
MethodInfo implMethod = map.TargetMethods[i];
27+
28+
Console.Write("{0} ({1}) - {2} ({3}) - ", declMethod, declMethod.DeclaringType, implMethod, implMethod.DeclaringType);
29+
30+
if (declMethod.Equals(ifooDefaultMethod))
31+
{
32+
foundMatchMask |= 1;
33+
CheckEqual(ref failed, implMethod, ifooDefaultMethod);
34+
}
35+
else if (declMethod.Equals(ifooOtherMethod))
36+
{
37+
foundMatchMask |= 2;
38+
CheckEqual(ref failed, implMethod, ibarOtherMethod);
39+
}
40+
else
41+
{
42+
Console.WriteLine("UNEXPECTED");
43+
failed = true;
44+
}
45+
}
46+
47+
if (foundMatchMask != 3)
48+
return 10;
49+
}
50+
51+
{
52+
var map = typeof(Fooer).GetInterfaceMap(typeof(IFoo));
53+
54+
int foundMatchMask = 0;
55+
56+
MethodInfo ifooDefaultMethod = typeof(IFoo).GetMethod("DefaultMethod");
57+
MethodInfo ifooOtherMethod = typeof(IFoo).GetMethod("OtherMethod");
58+
MethodInfo ibarOtherMethod = typeof(IBar).GetMethod("OtherMethod");
59+
60+
for (int i = 0; i < map.InterfaceMethods.Length; i++)
61+
{
62+
MethodInfo declMethod = map.InterfaceMethods[i];
63+
MethodInfo implMethod = map.TargetMethods[i];
64+
65+
Console.Write("{0} ({1}) - {2} ({3}) - ", declMethod, declMethod.DeclaringType, implMethod, implMethod.DeclaringType);
66+
67+
if (declMethod.Equals(ifooDefaultMethod))
68+
{
69+
foundMatchMask |= 1;
70+
CheckEqual(ref failed, implMethod, ifooDefaultMethod);
71+
}
72+
else if (declMethod.Equals(ifooOtherMethod))
73+
{
74+
foundMatchMask |= 2;
75+
CheckEqual(ref failed, implMethod, ibarOtherMethod);
76+
}
77+
else
78+
{
79+
Console.WriteLine("UNEXPECTED");
80+
failed = true;
81+
}
82+
}
83+
84+
if (foundMatchMask != 3)
85+
return 10;
86+
}
87+
88+
return failed ? -1 : 100;
89+
}
90+
91+
static void CheckEqual(ref bool failed, MethodInfo method1, MethodInfo method2)
92+
{
93+
if (method1.Equals(method2))
94+
Console.WriteLine("OK");
95+
else
96+
{
97+
Console.WriteLine("FAIL");
98+
failed = true;
99+
}
100+
}
101+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
9+
<OutputType>Exe</OutputType>
10+
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
12+
<CLRTestKind>BuildAndRun</CLRTestKind>
13+
<CLRTestPriority>0</CLRTestPriority>
14+
</PropertyGroup>
15+
<!-- Default configurations to help VS understand the configurations -->
16+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
18+
<ItemGroup>
19+
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
20+
<Visible>False</Visible>
21+
</CodeAnalysisDependentAssemblyPaths>
22+
</ItemGroup>
23+
<ItemGroup>
24+
<!-- Add Compile Object Here -->
25+
<Compile Include="GetInterfaceMapConsumer.cs" />
26+
</ItemGroup>
27+
<ItemGroup>
28+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
29+
</ItemGroup>
30+
<ItemGroup>
31+
<ProjectReference Include="GetInterfaceMapProvider.ilproj" />
32+
</ItemGroup>
33+
<ItemGroup>
34+
<NoWarn Include="42016,42020,42025,42024" />
35+
</ItemGroup>
36+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
37+
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
38+
</Project>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
.assembly extern mscorlib { }
6+
7+
.assembly GetInterfaceMapProvider { }
8+
9+
.class interface public abstract auto ansi IFoo
10+
{
11+
.method public hidebysig newslot virtual instance int32 DefaultMethod(int32) cil managed
12+
{
13+
ldarg.1
14+
ldc.i4 50
15+
add
16+
ret
17+
}
18+
19+
.method public hidebysig newslot virtual abstract instance int32 OtherMethod(int32) cil managed
20+
{
21+
}
22+
23+
.method public hidebysig instance int32 InstanceMethod(int32) cil managed
24+
{
25+
ldnull
26+
throw
27+
}
28+
29+
.method public hidebysig static int32 StaticMethod(int32) cil managed
30+
{
31+
ldnull
32+
throw
33+
}
34+
}
35+
36+
.class interface public abstract auto ansi IBar implements IFoo
37+
{
38+
.method public hidebysig newslot virtual final instance int32 OtherMethod(int32) cil managed
39+
{
40+
.override IFoo::OtherMethod
41+
ldarg.1
42+
ret
43+
}
44+
}
45+
46+
.class interface public abstract auto ansi IFoo`1<T>
47+
{
48+
.method public hidebysig newslot virtual instance valuetype [mscorlib]System.RuntimeTypeHandle DefaultMethod() cil managed
49+
{
50+
ldtoken !T
51+
ret
52+
}
53+
54+
.method public hidebysig newslot virtual abstract instance int32 OtherMethod(int32) cil managed
55+
{
56+
}
57+
}
58+
59+
.class interface public abstract auto ansi IBar`1<T> implements class IFoo`1<!T>
60+
{
61+
.method public hidebysig newslot virtual final instance int32 OtherMethod(int32) cil managed
62+
{
63+
.override class IFoo`1<!T>::OtherMethod
64+
ldarg.1
65+
ret
66+
}
67+
68+
}
69+
70+
.class public auto ansi beforefieldinit Fooer
71+
extends [mscorlib]System.Object
72+
implements IBar, class IBar`1<class Fooer>
73+
{
74+
.method public hidebysig specialname rtspecialname
75+
instance void .ctor() cil managed
76+
{
77+
ldarg.0
78+
call instance void [mscorlib]System.Object::.ctor()
79+
ret
80+
}
81+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
8+
<SchemaVersion>2.0</SchemaVersion>
9+
<ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
10+
<OutputType>Library</OutputType>
11+
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
12+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
13+
</PropertyGroup>
14+
<!-- Default configurations to help VS understand the configurations -->
15+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
16+
</PropertyGroup>
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
18+
</PropertyGroup>
19+
<ItemGroup>
20+
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
21+
<Visible>False</Visible>
22+
</CodeAnalysisDependentAssemblyPaths>
23+
</ItemGroup>
24+
<PropertyGroup>
25+
26+
</PropertyGroup>
27+
<ItemGroup>
28+
<Compile Include="GetInterfaceMapProvider.il" />
29+
</ItemGroup>
30+
<ItemGroup>
31+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
32+
</ItemGroup>
33+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
34+
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
35+
</PropertyGroup>
36+
</Project>

0 commit comments

Comments
 (0)