From fbc253400d66a386e185b68d22ea2c8fc8b3b979 Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Sat, 29 Aug 2026 23:26:07 -0700 Subject: [PATCH 1/2] Allow constrained. call to static virtual interface methods --- .../ILTests/PrefixTests.il | 65 ++++++++++++++++++- .../tools/ILVerification/ILImporter.Verify.cs | 29 ++++++--- src/coreclr/tools/ILVerification/Strings.resx | 3 + .../tools/ILVerification/VerifierError.cs | 1 + 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il index 46e4738dd39efb..aa831e597e92a7 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il @@ -9,11 +9,74 @@ { } +.class interface public auto ansi abstract IStaticInterface +{ + .method public hidebysig newslot abstract virtual static void StaticAbstractMethod() cil managed + { + } +} + +.class public sequential ansi sealed beforefieldinit ImplStruct + extends [System.Runtime]System.ValueType + implements IStaticInterface +{ + .pack 0 + .size 1 + + .method public hidebysig static void StaticAbstractMethod() cil managed + { + .override method void IStaticInterface::StaticAbstractMethod() + ret + } +} + +.class public sequential ansi sealed beforefieldinit NonImplStruct + extends [System.Runtime]System.ValueType +{ + .pack 0 + .size 1 +} + .class public auto ansi beforefieldinit PrefixTestsType extends [System.Runtime]System.Object { - .method static public hidebysig void StaticMethod() cil managed + .method static public hidebysig void StaticMethod() cil managed + { + ret + } + + .method static public hidebysig void ConstrainedCall.StaticAbstractOnConstrainedTypeParam_Valid<(IStaticInterface) T>() cil managed + { + constrained. !!T + call void IStaticInterface::StaticAbstractMethod() + ret + } + + .method static public hidebysig void ConstrainedCall.StaticAbstractOnImplStruct_Valid() cil managed + { + constrained. ImplStruct + call void IStaticInterface::StaticAbstractMethod() + ret + } + + .method static public hidebysig void ConstrainedCall.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + { + constrained. !!T + call void IStaticInterface::StaticAbstractMethod() + ret + } + + .method static public hidebysig void ConstrainedCall.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + { + constrained. NonImplStruct + call void IStaticInterface::StaticAbstractMethod() + ret + } + + .method static public hidebysig void ConstrainedCall.NonVirtualStaticMethod_Invalid_Constrained<(IStaticInterface) T>() cil managed { + constrained. !!T + call void PrefixTestsType::StaticMethod() ret } diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index 963f470e8ea7bf..d619598dfc8723 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -1553,12 +1553,27 @@ void ImportCall(ILOpcode opcode, int token) TypeDesc constrained = null; bool tailCall = false; + MethodDesc method = ResolveMethodToken(token); + MethodSignature sig = method.Signature; + if (opcode != ILOpcode.newobj) { - if (HasPendingPrefix(Prefix.Constrained) && opcode == ILOpcode.callvirt) + if (HasPendingPrefix(Prefix.Constrained)) { - ClearPendingPrefix(Prefix.Constrained); - constrained = _constrained; + if (opcode == ILOpcode.callvirt) + { + ClearPendingPrefix(Prefix.Constrained); + constrained = _constrained; + } + else if (opcode == ILOpcode.call && method.IsVirtual && sig.IsStatic && method.OwningType.IsInterface) + { + ClearPendingPrefix(Prefix.Constrained); + constrained = _constrained; + + // The constrained type must implement the interface declaring the static virtual method + if (!constrained.CanCastTo(method.OwningType)) + VerificationError(VerifierError.ConstrainedTypeNoInterfaceImpl, constrained, method.OwningType); + } } if (HasPendingPrefix(Prefix.Tail)) @@ -1572,10 +1587,6 @@ void ImportCall(ILOpcode opcode, int token) // if (sig.isVarArg()) // eeGetCallSiteSig(memberRef, getCurrentModuleHandle(), getCurrentContext(), &sig, false); - MethodDesc method = ResolveMethodToken(token); - - MethodSignature sig = method.Signature; - TypeDesc methodType = sig.IsStatic ? null : method.OwningType; if (opcode == ILOpcode.callvirt) @@ -1587,7 +1598,9 @@ void ImportCall(ILOpcode opcode, int token) { EcmaMethod ecmaMethod = method.GetTypicalMethodDefinition() as EcmaMethod; if (ecmaMethod != null) - Check(!ecmaMethod.IsAbstract, VerifierError.CallAbstract); + { + Check(!ecmaMethod.IsAbstract || (method.OwningType.IsInterface && constrained != null), VerifierError.CallAbstract); + } } if (opcode == ILOpcode.newobj && methodType.IsDelegate) diff --git a/src/coreclr/tools/ILVerification/Strings.resx b/src/coreclr/tools/ILVerification/Strings.resx index f89e854992af17..f8da49bb2b5c5c 100644 --- a/src/coreclr/tools/ILVerification/Strings.resx +++ b/src/coreclr/tools/ILVerification/Strings.resx @@ -183,6 +183,9 @@ The 'this' argument to a constrained call must have ByRef type. + + The type operand of the constrained prefix must implement the interface declaring the static virtual method. + .ctor expected. diff --git a/src/coreclr/tools/ILVerification/VerifierError.cs b/src/coreclr/tools/ILVerification/VerifierError.cs index 4a4103febacb97..6bff88641aae2a 100644 --- a/src/coreclr/tools/ILVerification/VerifierError.cs +++ b/src/coreclr/tools/ILVerification/VerifierError.cs @@ -192,5 +192,6 @@ public enum VerifierError LocallocStackNotEmpty, // localloc requires that stack must be empty, except for 'size' argument InvalidBaseType, // Type has an invalid base type. BadTypeSpec, // Invalid TypeSpec metadata. + ConstrainedTypeNoInterfaceImpl, // The type operand of the constrained prefix must implement the interface declaring the static virtual method. } } From 2c8bf39dfd243bb2456b4ddaf1c9d8cf264ad01f Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Sun, 30 Aug 2026 19:55:31 -0700 Subject: [PATCH 2/2] Add constrained ldftn support for static virtual interface methods --- .../ILTests/PrefixTests.il | 40 +++++++++++++++++++ .../tools/ILVerification/ILImporter.Verify.cs | 8 ++++ 2 files changed, 48 insertions(+) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il index aa831e597e92a7..ad4dc32c49e399 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/PrefixTests.il @@ -80,6 +80,46 @@ ret } + .method static public hidebysig void ConstrainedLdftn.StaticAbstractOnConstrainedTypeParam_Valid<(IStaticInterface) T>() cil managed + { + constrained. !!T + ldftn void IStaticInterface::StaticAbstractMethod() + pop + ret + } + + .method static public hidebysig void ConstrainedLdftn.StaticAbstractOnImplStruct_Valid() cil managed + { + constrained. ImplStruct + ldftn void IStaticInterface::StaticAbstractMethod() + pop + ret + } + + .method static public hidebysig void ConstrainedLdftn.UnconstrainedTypeParam_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + { + constrained. !!T + ldftn void IStaticInterface::StaticAbstractMethod() + pop + ret + } + + .method static public hidebysig void ConstrainedLdftn.NonImplStruct_Invalid_ConstrainedTypeNoInterfaceImpl() cil managed + { + constrained. NonImplStruct + ldftn void IStaticInterface::StaticAbstractMethod() + pop + ret + } + + .method static public hidebysig void ConstrainedLdftn.NonVirtualStaticMethod_Invalid_Constrained<(IStaticInterface) T>() cil managed + { + constrained. !!T + ldftn void PrefixTestsType::StaticMethod() + pop + ret + } + .method static public hidebysig void Readonly.Ldelema_Valid() cil managed { .locals init (int32[] V_0) diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index d619598dfc8723..e87be8d0be02e0 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -1833,6 +1833,14 @@ void ImportLdFtn(int token, ILOpcode opCode) _delegateCreateStart = _currentInstructionOffset; instance = null; + + if (HasPendingPrefix(Prefix.Constrained) && method.IsVirtual && + method.Signature.IsStatic && method.OwningType.IsInterface) + { + ClearPendingPrefix(Prefix.Constrained); + if (!_constrained.CanCastTo(method.OwningType)) + VerificationError(VerifierError.ConstrainedTypeNoInterfaceImpl, _constrained, method.OwningType); + } } else if (opCode == ILOpcode.ldvirtftn) {