From 952430bfb8496ac3a9d33af2edf0028168323a33 Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Tue, 20 Oct 2026 12:08:30 -0700 Subject: [PATCH] ILVerify: allow family access through implemented interfaces The ECMA-335 augments for default interface methods let a type that implements an interface access the interface's protected members. The runtime enforces this in ClassLoader::CanAccessFamily: when the target is an interface, access is granted if the caller, or a type that encloses it, has an interface with the same type definition in its interface map. Any instantiation of a generic interface counts. ILVerify's CanAccessFamily only walked base classes, so it reported MethodAccess for these calls. One example is Int32.CreateChecked, which calls the protected static abstract method INumberBase.TryConvertToChecked. When the target is an interface, look for its type definition among the runtime interfaces of the current type. The rule that a protected instance member is accessed through an instance of the current type is unchanged. --- .../ILTests/AccessTests.il | 133 ++++++++++++++++++ .../AccessVerificationHelpers.cs | 12 ++ 2 files changed, 145 insertions(+) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTests.il index 4132dd2c34a9d7..bc4ecaae839876 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTests.il @@ -480,3 +480,136 @@ IL_0007: ret } } + +// Family access to the members of an interface from the types that implement it (the ECMA-335 augments for +// default interface methods). Like the runtime, any instantiation of a generic interface counts. + +.class interface public auto ansi abstract IFamilyMembers`1 +{ + .method family hidebysig newslot abstract virtual static void StaticMethod() cil managed + { + } + + .method family hidebysig newslot virtual instance void InstanceMethod() cil managed + { + ret + } +} + +.class interface public auto ansi abstract IFamilyMembersDerived`1 + implements class IFamilyMembers`1 +{ + .method public hidebysig newslot virtual instance void Call.FamilyInstanceOfBaseInterface_Valid() cil managed + { + ldarg.0 + callvirt instance void class IFamilyMembers`1::InstanceMethod() + ret + } +} + +.class interface public auto ansi abstract IOtherInterface +{ +} + +// The shape of Int32.CreateChecked calling INumberBase.TryConvertToChecked +.class public sequential ansi sealed beforefieldinit FamilyMembersStruct + extends [System.Runtime]System.ValueType + implements class IFamilyMembers`1 +{ + .pack 0 + .size 1 + + .method private hidebysig static void StaticMethod() cil managed + { + .override method void class IFamilyMembers`1::StaticMethod() + ret + } + + .method public hidebysig static void Call.FamilyStaticOfOtherInstantiation_Valid<(class IFamilyMembers`1) T>() cil managed + { + constrained. !!T + call void class IFamilyMembers`1::StaticMethod() + ret + } +} + +.class public auto ansi beforefieldinit FamilyMembersBase + extends [System.Runtime]System.Object + implements class IFamilyMembers`1 +{ + .method private hidebysig static void StaticMethod() cil managed + { + .override method void class IFamilyMembers`1::StaticMethod() + ret + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } + + .method public hidebysig instance void Call.FamilyInstanceThroughOtherImplementer_Invalid_MethodAccess(class FamilyMembersSibling other) cil managed + { + ldarg.1 + callvirt instance void class IFamilyMembers`1::InstanceMethod() + ret + } + + .class nested public auto ansi beforefieldinit Nested + extends [System.Runtime]System.Object + { + .method public hidebysig static void Call.FamilyStaticFromNestedType_Valid<(class IFamilyMembers`1) T>() cil managed + { + constrained. !!T + call void class IFamilyMembers`1::StaticMethod() + ret + } + } +} + +.class public auto ansi beforefieldinit FamilyMembersDerived + extends FamilyMembersBase +{ + .method public hidebysig static void Call.FamilyStaticOfInterfaceOfBaseClass_Valid<(class IFamilyMembers`1) T>() cil managed + { + constrained. !!T + call void class IFamilyMembers`1::StaticMethod() + ret + } +} + +.class public auto ansi beforefieldinit FamilyMembersSibling + extends [System.Runtime]System.Object + implements class IFamilyMembers`1 +{ + .method private hidebysig static void StaticMethod() cil managed + { + .override method void class IFamilyMembers`1::StaticMethod() + ret + } +} + +.class public auto ansi beforefieldinit FamilyMembersOtherInterface + extends [System.Runtime]System.Object + implements IOtherInterface +{ + .method public hidebysig static void Call.FamilyStaticFromOtherInterface_Invalid_MethodAccess<(class IFamilyMembers`1) T>() cil managed + { + constrained. !!T + call void class IFamilyMembers`1::StaticMethod() + ret + } +} + +.class public auto ansi beforefieldinit FamilyMembersUnrelated + extends [System.Runtime]System.Object +{ + .method public hidebysig static void Call.FamilyStaticFromUnrelatedType_Invalid_MethodAccess<(class IFamilyMembers`1) T>() cil managed + { + constrained. !!T + call void class IFamilyMembers`1::StaticMethod() + ret + } +} diff --git a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs index 2ed98c0bb636d9..69829147861b96 100644 --- a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs +++ b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs @@ -215,6 +215,18 @@ private static bool CanAccessFamily(TypeDesc currentType, TypeDesc targetTypeDef if (currentTypeDef == curInstTypeDef.GetTypeDefinition()) { // At this point we know that the instance type is able to access the same family fields as current type + if (targetTypeDef.IsInterface) + { + // Like ClassLoader::CanAccessFamily in the runtime, implementing any instantiation of the interface is enough + foreach (DefType implementedInterface in currentTypeDef.RuntimeInterfaces) + { + if (implementedInterface.GetTypeDefinition() == targetTypeDef) + return true; + } + + return false; + } + // Now iterate through all super types of current type to see if current type can access family target type while (currentTypeDef != null) {