From 257aefb23b5721f0c2de328cc4f891d77c4d14d9 Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Mon, 19 Oct 2026 23:29:48 -0700 Subject: [PATCH 1/3] ILVerify: verify calli and typed references ILVerify stopped verifying a method at calli, mkrefany, refanyval or refanytype, so errors after the instruction were not reported. calli is now always reported as Unverifiable, and its call site is still checked: the signature token, the method pointer, the arguments, tail. and the return value. When the pointer comes straight from ldftn or ldvirtftn, the call-site signature is also compared with that method's signature. The tail. return checks move from ImportCall into CheckTailCallReturn, which call and calli both use. mkrefany, refanyval and refanytype follow ECMA-335 III.4.19, III.4.23 and III.4.22. mkrefany also rejects readonly pointers. It accepts a byref-like class, because the ECMA-335 augments allow a managed pointer to a byref-like type. arglist is unchanged and is still not supported. New VerifierError codes: ExpectedSignatureToken, CalliSignatureMismatch and ReadOnlyInMkrefany. Contributes to #136111 --- .../ILTests/CalliTests.il | 363 ++++++++++++++++++ .../ILTests/CalliTests.ilproj | 9 + .../ILTests/TypedReferenceTests.il | 196 ++++++++++ .../ILTests/TypedReferenceTests.ilproj | 9 + .../tools/ILVerification/ILImporter.Verify.cs | 183 +++++++-- src/coreclr/tools/ILVerification/Strings.resx | 9 + .../tools/ILVerification/VerifierError.cs | 3 + 7 files changed, 742 insertions(+), 30 deletions(-) create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.ilproj create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.ilproj diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il new file mode 100644 index 00000000000000..b7ab6793a30f5d --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il @@ -0,0 +1,363 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime +{ +} + +.assembly extern System.Runtime.InteropServices +{ +} + +.assembly CalliTests +{ +} + +// ILVerify reports every calli as Unverifiable, because the runtime does not track the lifetime of function +// pointers, and then checks the call site. +.class public auto ansi beforefieldinit CalliTests + extends [System.Runtime]System.Object +{ + .method private hidebysig static void StaticTarget(int32 x) cil managed + { + ret + } + + .method private hidebysig static void ManagedTarget() cil managed + { + ret + } + + .method private hidebysig static int32 ReturnsInt32() cil managed + { + ldc.i4.0 + ret + } + + .method private hidebysig static string ReturnsString() cil managed + { + ldnull + ret + } + + .method private hidebysig static object ReturnsObject() cil managed + { + ldnull + ret + } + + .method private hidebysig static void TakesString(string s) cil managed + { + ret + } + + .method private hidebysig static void TakesObject(object o) cil managed + { + ret + } + + .method private hidebysig instance void InstanceTarget(int32 x) cil managed + { + ret + } + + .method private hidebysig static void UnmanagedTarget() cil managed + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute::.ctor() = ( 01 00 00 00 ) + ret + } + + .method public hidebysig static void Calli.StaticFromLdftn_Invalid_Unverifiable() cil managed + { + ldc.i4.1 + ldftn void CalliTests::StaticTarget(int32) + calli void(int32) + ret + } + + .method public hidebysig static int32 Calli.ReturnValue_Invalid_Unverifiable() cil managed + { + ldftn int32 CalliTests::ReturnsInt32() + calli int32() + ret + } + + .method public hidebysig static void Calli.ErrorAfterCalli_Invalid_Unverifiable.StackUnderflow() cil managed + { + ldc.i4.1 + ldftn void CalliTests::StaticTarget(int32) + calli void(int32) + pop + ret + } + + .method public hidebysig static void Calli.FromArgument_Invalid_Unverifiable(method void *(int32) f) cil managed + { + ldc.i4.1 + ldarg.0 + calli void(int32) + ret + } + + .method public hidebysig static void Calli.ArgumentMismatch_Invalid_Unverifiable.StackUnexpected(method void *(int32) f) cil managed + { + ldnull + ldarg.0 + calli void(int32) + ret + } + + .method public hidebysig static void Calli.MethodPointerNotNativeInt_Invalid_Unverifiable.StackMethod() cil managed + { + ldc.i4.1 + ldc.i4.0 + calli void(int32) + ret + } + + .method public hidebysig instance void Calli.ImplicitThis_Invalid_Unverifiable(method instance void *(int32) f) cil managed + { + ldarg.0 + ldc.i4.1 + ldarg.1 + calli instance void(int32) + ret + } + + .method public hidebysig instance void Calli.ImplicitThisFromLdftn_Invalid_Unverifiable() cil managed + { + ldarg.0 + ldc.i4.1 + ldftn instance void CalliTests::InstanceTarget(int32) + calli instance void(int32) + ret + } + + .method public hidebysig static void Calli.ImplicitThisMismatch_Invalid_Unverifiable.StackUnexpected() cil managed + { + ldstr "this" + ldc.i4.1 + ldftn instance void CalliTests::InstanceTarget(int32) + calli instance void(int32) + ret + } + + // The 'this' of a value type's instance method is a managed pointer. + .method public hidebysig static void Calli.ValueTypeThisFromLdftn_Invalid_Unverifiable() cil managed + { + .locals init (valuetype CalliStruct V_0) + ldloca.s V_0 + ldftn instance void CalliStruct::InstanceTarget() + calli instance void() + ret + } + + .method public hidebysig instance void Calli.ExplicitThis_Invalid_Unverifiable() cil managed + { + ldarg.0 + ldc.i4.1 + ldftn instance void CalliTests::InstanceTarget(int32) + calli instance explicit void(class CalliTests, int32) + ret + } + + .method public hidebysig instance void Calli.StaticSiteForInstanceTarget_Invalid_Unverifiable() cil managed + { + ldarg.0 + ldc.i4.1 + ldftn instance void CalliTests::InstanceTarget(int32) + calli void(class CalliTests, int32) + ret + } + + .method public hidebysig static void Calli.TargetParameterMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldc.i4.1 + ldftn void CalliTests::TakesString(string) + calli void(int32) + ret + } + + .method public hidebysig static void Calli.TargetParameterContravariant_Invalid_Unverifiable() cil managed + { + ldstr "argument" + ldftn void CalliTests::TakesObject(object) + calli void(string) + ret + } + + .method public hidebysig static void Calli.TargetParameterCovariant_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldnull + ldftn void CalliTests::TakesString(string) + calli void(object) + ret + } + + .method public hidebysig static void Calli.TargetReturnMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn int32 CalliTests::ReturnsInt32() + calli void() + ret + } + + .method public hidebysig static void Calli.TargetReturnCovariant_Invalid_Unverifiable() cil managed + { + ldftn string CalliTests::ReturnsString() + calli object() + pop + ret + } + + .method public hidebysig static void Calli.TargetReturnContravariant_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn object CalliTests::ReturnsObject() + calli string() + pop + ret + } + + .method public hidebysig static void Calli.TargetArgumentCountMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn void CalliTests::StaticTarget(int32) + calli void() + ret + } + + .method public hidebysig static void Calli.TargetCallingConventionMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn void CalliTests::ManagedTarget() + calli unmanaged cdecl void() + ret + } + + .method public hidebysig static void Calli.UnmanagedCallersOnly_Invalid_Unverifiable() cil managed + { + ldftn void CalliTests::UnmanagedTarget() + calli unmanaged cdecl void() + ret + } + + .method public hidebysig static void Calli.UnmanagedCallersOnlyManagedSite_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn void CalliTests::UnmanagedTarget() + calli void() + ret + } + + // An UnmanagedCallersOnly method cannot be called through a managed vararg call site either. + .method public hidebysig static void Calli.UnmanagedCallersOnlyVarargSite_Invalid_Unverifiable.CalliSignatureMismatch() cil managed + { + ldftn void CalliTests::UnmanagedTarget() + calli vararg void() + ret + } + + // A vararg call site with no extra arguments has no sentinel. + .method public hidebysig static void Calli.Vararg_Invalid_Unverifiable(method vararg void *(int32) f) cil managed + { + ldc.i4.1 + ldarg.0 + calli vararg void(int32) + ret + } + + .method public hidebysig static void Calli.TailCall_Invalid_Unverifiable() cil managed + { + ldftn void CalliTests::ManagedTarget() + tail. calli void() + ret + } + + .method public hidebysig static void Calli.TailCallByRefArgument_Invalid_Unverifiable.TailByRef(method void *(int32&) f) cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + ldarg.0 + tail. calli void(int32&) + ret + } + + .method public hidebysig static void Calli.TailCallByRefThis_Invalid_Unverifiable.TailByRef(method instance void *() f) cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + ldarg.0 + tail. calli instance void() + ret + } + + .method public hidebysig static void Calli.TailCallNotFollowedByRet_Invalid_Unverifiable.TailRet() cil managed + { + ldftn void CalliTests::ManagedTarget() + tail. calli void() + nop + ret + } + + // The tail call's return checks are shared with call. A void method cannot tail-call a method returning a value; + // the value then left on the stack also makes the ret report ReturnVoid. + .method public hidebysig static void Calli.TailCallReturnsValue_Invalid_Unverifiable.TailRetVoid.ReturnVoid() cil managed + { + ldftn int32 CalliTests::ReturnsInt32() + tail. calli int32() + ret + } + + .method public hidebysig static int32& Calli.ByRefReturn_Invalid_Unverifiable(method int32& *() f) cil managed + { + ldarg.0 + calli int32&() + ret + } + + // The calli operand is the TypeDef token of CalliTests (0x02000002), not a StandAloneSig token. + .method public hidebysig static void Calli.NotSignatureToken_Invalid_Unverifiable.ExpectedSignatureToken() cil managed + { + ldftn void CalliTests::ManagedTarget() + .emitbyte 0x29 + .emitbyte 0x02 + .emitbyte 0x00 + .emitbyte 0x00 + .emitbyte 0x02 + ret + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } +} + +.class public sequential ansi sealed beforefieldinit CalliStruct + extends [System.Runtime]System.ValueType +{ + .field public int32 Value + + .method public hidebysig instance void InstanceTarget() cil managed + { + ret + } +} + +.class public auto ansi beforefieldinit CalliConstructorTests + extends [System.Runtime]System.Object +{ + // Passing the uninitialized 'this' to a method pointer is not allowed (ECMA-335 III.1.8.1.4). + .method public hidebysig specialname rtspecialname instance void .ctor(method void *(object) f) cil managed + { + ldarg.0 + ldarg.1 + calli void(object) + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } + + .method public hidebysig instance void 'special.CalliUninitThis..ctor_Invalid_Unverifiable.UninitStack'(method void *(object) f) cil managed + { + ret + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il new file mode 100644 index 00000000000000..6a510a76a09d38 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.il @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime +{ +} + +.assembly TypedReferenceTests +{ +} + +.class public sequential ansi sealed beforefieldinit RefStruct + extends [System.Runtime]System.ValueType +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = ( 01 00 00 00 ) + .field public int32 Value +} + +.class public auto ansi beforefieldinit TypedReferenceOther + extends [System.Runtime]System.Object +{ + .class nested private sequential ansi sealed beforefieldinit Hidden + extends [System.Runtime]System.ValueType + { + .field public int32 Value + } +} + +.class public auto ansi beforefieldinit TypedReferenceTests + extends [System.Runtime]System.Object +{ + .method private hidebysig static void TakesTypedReference(typedref tr) cil managed + { + ret + } + + // ---- mkrefany (ECMA-335 III.4.19) ---- + + .method public hidebysig static void Mkrefany.Local_Valid() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + mkrefany [System.Runtime]System.Int32 + call void TypedReferenceTests::TakesTypedReference(typedref) + ret + } + + .method public hidebysig static void Mkrefany.ErrorAfterMkrefany_Invalid_StackUnderflow() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + mkrefany [System.Runtime]System.Int32 + pop + pop + ret + } + + // Correct CIL, but verification requires a managed pointer. + .method public hidebysig static void Mkrefany.NativeIntPointer_Invalid_StackByRef() cil managed + { + ldc.i4.0 + conv.i + mkrefany [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Mkrefany.TypeMismatch_Invalid_StackUnexpected() cil managed + { + .locals init (int32 V_0) + ldloca.s V_0 + mkrefany [System.Runtime]System.Int64 + pop + ret + } + + // The pointer must point to exactly the type of the class token, not to a derived type. + .method public hidebysig static void Mkrefany.ReferenceTypeMismatch_Invalid_StackUnexpected() cil managed + { + .locals init (string V_0) + ldloca.s V_0 + mkrefany [System.Runtime]System.Object + pop + ret + } + + .method public hidebysig static void Mkrefany.ReadOnlyPointer_Invalid_ReadOnlyInMkrefany() cil managed + { + ldc.i4.1 + newarr [System.Runtime]System.Int32 + ldc.i4.0 + readonly. ldelema [System.Runtime]System.Int32 + mkrefany [System.Runtime]System.Int32 + pop + ret + } + + // The ECMA-335 augments allow a managed pointer to a byref-like type, so mkrefany accepts a byref-like class. + .method public hidebysig static void Mkrefany.TypedReference_Valid() cil managed + { + .locals init (typedref V_0) + ldloca.s V_0 + mkrefany [System.Runtime]System.TypedReference + pop + ret + } + + .method public hidebysig static void Mkrefany.ByRefLikeClass_Valid() cil managed + { + .locals init (valuetype RefStruct V_0) + ldloca.s V_0 + mkrefany RefStruct + pop + ret + } + + .method public hidebysig static void Mkrefany.InaccessibleType_Invalid_TypeAccess() cil managed + { + .locals init (valuetype TypedReferenceOther/Hidden V_0) + ldloca.s V_0 + mkrefany TypedReferenceOther/Hidden + pop + ret + } + + // ---- refanyval (ECMA-335 III.4.23) ---- + + .method public hidebysig static void Refanyval.ReadThroughAddress_Valid(typedref tr) cil managed + { + ldarg.0 + refanyval [System.Runtime]System.Int32 + ldind.i4 + pop + ret + } + + .method public hidebysig static void Refanyval.NotTypedReference_Invalid_StackUnexpected() cil managed + { + ldc.i4.0 + refanyval [System.Runtime]System.Int32 + pop + ret + } + + .method public hidebysig static void Refanyval.ErrorAfterRefanyval_Invalid_StackUnderflow(typedref tr) cil managed + { + ldarg.0 + refanyval [System.Runtime]System.Int32 + pop + pop + ret + } + + // The address in a typed reference can point to a local of a caller. + .method public hidebysig static int32& Refanyval.ReturnAddress_Invalid_ReturnPtrToStack(typedref tr) cil managed + { + ldarg.0 + refanyval [System.Runtime]System.Int32 + ret + } + + // ---- refanytype (ECMA-335 III.4.22) ---- + + .method public hidebysig static void Refanytype.TypeFromHandle_Valid(typedref tr) cil managed + { + ldarg.0 + refanytype + call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle) + pop + ret + } + + .method public hidebysig static void Refanytype.NotTypedReference_Invalid_StackUnexpected() cil managed + { + ldc.i4.0 + refanytype + pop + ret + } + + .method public hidebysig static void Refanytype.ErrorAfterRefanytype_Invalid_StackUnderflow(typedref tr) cil managed + { + ldarg.0 + refanytype + pop + pop + ret + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/TypedReferenceTests.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index 4cac413209b443..b2de1a298acb18 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -1773,31 +1773,7 @@ void ImportCall(ILOpcode opcode, int token) // special checks for tailcalls if (tailCall) - { - TypeDesc callerReturnType = _methodSignature.ReturnType; - - if (returnType.IsVoid || callerReturnType.IsVoid) - { - Check(returnType.IsVoid && callerReturnType.IsVoid, VerifierError.TailRetVoid); - } - // else - // if (returnType.IsValueType || callerReturnType.IsValueType) - // { - // TODO: Check exact match - // } - else - { - var retStackType = StackValue.CreateFromType(returnType); - var callerRetStackType = StackValue.CreateFromType(callerReturnType); - Check(IsAssignable(retStackType, callerRetStackType), VerifierError.TailRetType, retStackType, callerRetStackType); - } - - // for tailcall, stack must be empty - Check(_stackTop == 0, VerifierError.TailStackEmpty); - - // The instruction following a tail.call shall be a ret - Check(_currentOffset < _ilBytes.Length && (ILOpcode)_ilBytes[_currentOffset] == ILOpcode.ret, VerifierError.TailRet); - } + CheckTailCallReturn(returnType); // now push on the result if (opcode == ILOpcode.newobj) @@ -1829,9 +1805,130 @@ void ImportCall(ILOpcode opcode, int token) } } + void CheckTailCallReturn(TypeDesc returnType) + { + TypeDesc callerReturnType = _methodSignature.ReturnType; + + if (returnType.IsVoid || callerReturnType.IsVoid) + { + Check(returnType.IsVoid && callerReturnType.IsVoid, VerifierError.TailRetVoid); + } + // else + // if (returnType.IsValueType || callerReturnType.IsValueType) + // { + // TODO: Check exact match + // } + else + { + var retStackType = StackValue.CreateFromType(returnType); + var callerRetStackType = StackValue.CreateFromType(callerReturnType); + Check(IsAssignable(retStackType, callerRetStackType), VerifierError.TailRetType, retStackType, callerRetStackType); + } + + // for tailcall, stack must be empty + Check(_stackTop == 0, VerifierError.TailStackEmpty); + + // The instruction following a tail.call shall be a ret + Check(_currentOffset < _ilBytes.Length && (ILOpcode)_ilBytes[_currentOffset] == ILOpcode.ret, VerifierError.TailRet); + } + void ImportCalli(int token) { - throw new NotImplementedException($"{nameof(ImportCalli)} not implemented"); + // calli is always reported as unverifiable. The runtime does not track the lifetime of function + // pointers, so the target may be code that has been unloaded. The call site is still checked, and + // verification goes on. + Unverifiable(); + + bool tailCall = HasPendingPrefix(Prefix.Tail); + ClearPendingPrefix(Prefix.Tail); + + object tokenObj = ResolveToken(token); + FatalCheck(tokenObj is MethodSignature, VerifierError.ExpectedSignatureToken); + var sig = (MethodSignature)tokenObj; + + var ftn = Pop(); + Check(ftn.Kind == StackValueKind.NativeInt, VerifierError.StackMethod, ftn); + + // An instance call site that does not list the type of 'this' takes one argument more than it declares. + bool implicitThis = !sig.IsStatic && !sig.IsExplicitThis; + var args = new StackValue[sig.Length + (implicitThis ? 1 : 0)]; + for (int i = args.Length - 1; i >= 0; i--) + args[i] = Pop(); + + int firstParameter = args.Length - sig.Length; + for (int i = 0; i < sig.Length; i++) + { + var declared = StackValue.CreateFromType(sig[i]); + CheckIsAssignable(args[firstParameter + i], declared); + + if (tailCall) + Check(!IsByRefLike(declared), VerifierError.TailByRef, declared); + } + + if (implicitThis && tailCall) + Check(!IsByRefLike(args[0]), VerifierError.TailByRef, args[0]); + + // A method pointer pushed by ldftn or ldvirtftn carries its method, so the call site can be checked against it. + if (ftn.IsMethod) + CheckCalliTarget(ftn.Method, sig, args); + + if (tailCall) + CheckTailCallReturn(sig.ReturnType); + + if (!sig.ReturnType.IsVoid) + { + var returnValue = StackValue.CreateFromType(sig.ReturnType); + + if (returnValue.Kind == StackValueKind.ByRef) + returnValue.SetIsPermanentHome(); + + Push(returnValue); + } + } + + /// + /// Checks the call site of a calli against the method that the method pointer was loaded from. + /// ECMA-335 III.3.20 (Correctness) requires the method's signature to be method-signature compatible-with + /// the call site's, ignoring HASTHIS and EXPLICITTHIS, so 'this' is compared as a first parameter + /// (I.8.7.1). Parameter and return types are compared in the same direction as in IsDelegateAssignable. + /// + void CheckCalliTarget(MethodDesc target, MethodSignature site, StackValue[] args) + { + MethodSignature targetSig = target.Signature; + int firstTargetParameter = (targetSig.IsStatic || targetSig.IsExplicitThis) ? 0 : 1; + int firstSiteParameter = args.Length - site.Length; + + // Metadata gives an UnmanagedCallersOnly method a managed signature, but it is called through an unmanaged + // call site. Only managed vs unmanaged is compared; the attribute's CallConvs are not. + var callingConvention = site.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask; + bool callingConventionMatches = target.IsUnmanagedCallersOnly ? + callingConvention is not (MethodSignatureFlags.None or MethodSignatureFlags.CallingConventionVarargs) : + callingConvention == (targetSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask); + + if (!callingConventionMatches || + targetSig.Length + firstTargetParameter != args.Length || + !IsAssignable(targetSig.ReturnType, site.ReturnType)) + { + VerificationError(VerifierError.CalliSignatureMismatch); + return; + } + + for (int i = 0; i < args.Length; i++) + { + TypeDesc targetType = i < firstTargetParameter ? + (target.OwningType.IsValueType ? target.OwningType.MakeByRefType() : target.OwningType) : + targetSig[i - firstTargetParameter]; + + if (i < firstSiteParameter) + { + // The call site does not give the type of 'this', so check the argument itself. + CheckIsAssignable(args[i], StackValue.CreateFromType(targetType)); + } + else if (!IsAssignable(site[i - firstSiteParameter], targetType)) + { + VerificationError(VerifierError.CalliSignatureMismatch); + } + } } void ImportLdFtn(int token, ILOpcode opCode) @@ -2918,7 +3015,7 @@ void ReportInvalidExceptionRegion() } // - // Deprecated + // Variable argument lists and typed references // void ImportArgList() @@ -2928,17 +3025,43 @@ void ImportArgList() void ImportRefAnyType() { - throw new PlatformNotSupportedException("TypedReference not supported in .NET Core"); + var typedReference = Pop(); + CheckIsAssignable(typedReference, StackValue.CreateValueType(GetWellKnownType(WellKnownType.TypedReference))); + + Push(StackValue.CreateValueType(GetWellKnownType(WellKnownType.RuntimeTypeHandle))); } void ImportMkRefAny(int token) { - throw new PlatformNotSupportedException("TypedReference not supported in .NET Core"); + var type = ResolveTypeToken(token); + + var ptr = Pop(); + + // ECMA-335 III.4.19: verification requires a managed pointer to an instance of the class. + if (Check(ptr.Kind == StackValueKind.ByRef, VerifierError.StackByRef, ptr)) + { + // III.1.8.1.2.2: a controlled-mutability managed pointer cannot be used with mkrefany. + Check(!ptr.IsReadOnly, VerifierError.ReadOnlyInMkrefany); + + if (ptr.Type != type) + VerificationError(VerifierError.StackUnexpected, ptr, StackValue.CreateByRef(type)); + } + + // A byref-like class is allowed: the ECMA-335 augments allow a managed pointer to a byref-like type. + Check(_method.OwningType.CanAccess(type), VerifierError.TypeAccess); + + Push(StackValue.CreateValueType(GetWellKnownType(WellKnownType.TypedReference))); } void ImportRefAnyVal(int token) { - throw new PlatformNotSupportedException("TypedReference not supported in .NET Core"); + var type = ResolveTypeToken(token); + + var typedReference = Pop(); + CheckIsAssignable(typedReference, StackValue.CreateValueType(GetWellKnownType(WellKnownType.TypedReference))); + + // The address can point to a local of a caller, so it does not have a permanent home. + Push(StackValue.CreateByRef(type)); } } } diff --git a/src/coreclr/tools/ILVerification/Strings.resx b/src/coreclr/tools/ILVerification/Strings.resx index 66b0abac4723ba..7f5d494879e1ec 100644 --- a/src/coreclr/tools/ILVerification/Strings.resx +++ b/src/coreclr/tools/ILVerification/Strings.resx @@ -168,6 +168,9 @@ Callvirt on a value type method. + + Method pointer signature does not match the calli call-site signature. + ByRef not allowed as catch type. @@ -231,6 +234,9 @@ Expected numeric type on the stack. + + Expected signature token. + Expected static field. @@ -336,6 +342,9 @@ Illegal write to readonly ByRef. + + A readonly ByRef cannot be used with mkrefany. + The readonly prefix may only be applied to calls to array methods returning ByRefs. diff --git a/src/coreclr/tools/ILVerification/VerifierError.cs b/src/coreclr/tools/ILVerification/VerifierError.cs index 87491f2741b387..7790327e4c0145 100644 --- a/src/coreclr/tools/ILVerification/VerifierError.cs +++ b/src/coreclr/tools/ILVerification/VerifierError.cs @@ -142,5 +142,8 @@ public enum VerifierError EHClauseOrder, // An exception handling clause nested in a try must precede that try's clause. EHClauseFilterEnd, // A filter must have exactly one endfilter as its last instruction. UnalignedAlignment, // Alignment specified for 'unaligned.' must be 1, 2, or 4. + ExpectedSignatureToken, // Expected signature token. + CalliSignatureMismatch, // Method pointer signature does not match the calli call-site signature. + ReadOnlyInMkrefany, // A readonly ByRef cannot be used with mkrefany. } } From 3287d0338a35f50053b66001771cf772bd9ab87a Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Tue, 20 Oct 2026 08:53:38 -0700 Subject: [PATCH 2/3] ILVerify: move the mkrefany byref-like comment to the rule it qualifies The comment saying that mkrefany accepts a byref-like class sat above the type access check, which it does not describe. It now follows the ECMA-335 III.4.19 comment on the managed-pointer check, which is the rule it qualifies. There is no change in behaviour. --- src/coreclr/tools/ILVerification/ILImporter.Verify.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index b2de1a298acb18..5580f2ac93a4c1 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -3038,6 +3038,7 @@ void ImportMkRefAny(int token) var ptr = Pop(); // ECMA-335 III.4.19: verification requires a managed pointer to an instance of the class. + // The class may be byref-like: the ECMA-335 augments allow a managed pointer to a byref-like type. if (Check(ptr.Kind == StackValueKind.ByRef, VerifierError.StackByRef, ptr)) { // III.1.8.1.2.2: a controlled-mutability managed pointer cannot be used with mkrefany. @@ -3047,7 +3048,6 @@ void ImportMkRefAny(int token) VerificationError(VerifierError.StackUnexpected, ptr, StackValue.CreateByRef(type)); } - // A byref-like class is allowed: the ECMA-335 augments allow a managed pointer to a byref-like type. Check(_method.OwningType.CanAccess(type), VerifierError.TypeAccess); Push(StackValue.CreateValueType(GetWellKnownType(WellKnownType.TypedReference))); From 84638369b16d7537530440f112b0efdba000375d Mon Sep 17 00:00:00 2001 From: Peter Shrosbree Date: Tue, 20 Oct 2026 11:22:55 -0700 Subject: [PATCH 3/3] ILVerify: remove the ldftn check at calli sites When the function pointer came directly from ldftn or ldvirtftn, the calli call-site signature was also compared with the target method's signature. Following review feedback, that check is removed, because real code does not pass the pointer to calli this way. CalliSignatureMismatch and the tests for the check go with it; the other calli call-site checks are unchanged. --- .../ILTests/CalliTests.il | 163 ------------------ .../tools/ILVerification/ILImporter.Verify.cs | 49 ------ src/coreclr/tools/ILVerification/Strings.resx | 3 - .../tools/ILVerification/VerifierError.cs | 1 - 4 files changed, 216 deletions(-) diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il index b7ab6793a30f5d..cd0f85d74234da 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il @@ -5,10 +5,6 @@ { } -.assembly extern System.Runtime.InteropServices -{ -} - .assembly CalliTests { } @@ -34,39 +30,11 @@ ret } - .method private hidebysig static string ReturnsString() cil managed - { - ldnull - ret - } - - .method private hidebysig static object ReturnsObject() cil managed - { - ldnull - ret - } - - .method private hidebysig static void TakesString(string s) cil managed - { - ret - } - - .method private hidebysig static void TakesObject(object o) cil managed - { - ret - } - .method private hidebysig instance void InstanceTarget(int32 x) cil managed { ret } - .method private hidebysig static void UnmanagedTarget() cil managed - { - .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute::.ctor() = ( 01 00 00 00 ) - ret - } - .method public hidebysig static void Calli.StaticFromLdftn_Invalid_Unverifiable() cil managed { ldc.i4.1 @@ -124,34 +92,6 @@ ret } - .method public hidebysig instance void Calli.ImplicitThisFromLdftn_Invalid_Unverifiable() cil managed - { - ldarg.0 - ldc.i4.1 - ldftn instance void CalliTests::InstanceTarget(int32) - calli instance void(int32) - ret - } - - .method public hidebysig static void Calli.ImplicitThisMismatch_Invalid_Unverifiable.StackUnexpected() cil managed - { - ldstr "this" - ldc.i4.1 - ldftn instance void CalliTests::InstanceTarget(int32) - calli instance void(int32) - ret - } - - // The 'this' of a value type's instance method is a managed pointer. - .method public hidebysig static void Calli.ValueTypeThisFromLdftn_Invalid_Unverifiable() cil managed - { - .locals init (valuetype CalliStruct V_0) - ldloca.s V_0 - ldftn instance void CalliStruct::InstanceTarget() - calli instance void() - ret - } - .method public hidebysig instance void Calli.ExplicitThis_Invalid_Unverifiable() cil managed { ldarg.0 @@ -161,98 +101,6 @@ ret } - .method public hidebysig instance void Calli.StaticSiteForInstanceTarget_Invalid_Unverifiable() cil managed - { - ldarg.0 - ldc.i4.1 - ldftn instance void CalliTests::InstanceTarget(int32) - calli void(class CalliTests, int32) - ret - } - - .method public hidebysig static void Calli.TargetParameterMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldc.i4.1 - ldftn void CalliTests::TakesString(string) - calli void(int32) - ret - } - - .method public hidebysig static void Calli.TargetParameterContravariant_Invalid_Unverifiable() cil managed - { - ldstr "argument" - ldftn void CalliTests::TakesObject(object) - calli void(string) - ret - } - - .method public hidebysig static void Calli.TargetParameterCovariant_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldnull - ldftn void CalliTests::TakesString(string) - calli void(object) - ret - } - - .method public hidebysig static void Calli.TargetReturnMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn int32 CalliTests::ReturnsInt32() - calli void() - ret - } - - .method public hidebysig static void Calli.TargetReturnCovariant_Invalid_Unverifiable() cil managed - { - ldftn string CalliTests::ReturnsString() - calli object() - pop - ret - } - - .method public hidebysig static void Calli.TargetReturnContravariant_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn object CalliTests::ReturnsObject() - calli string() - pop - ret - } - - .method public hidebysig static void Calli.TargetArgumentCountMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn void CalliTests::StaticTarget(int32) - calli void() - ret - } - - .method public hidebysig static void Calli.TargetCallingConventionMismatch_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn void CalliTests::ManagedTarget() - calli unmanaged cdecl void() - ret - } - - .method public hidebysig static void Calli.UnmanagedCallersOnly_Invalid_Unverifiable() cil managed - { - ldftn void CalliTests::UnmanagedTarget() - calli unmanaged cdecl void() - ret - } - - .method public hidebysig static void Calli.UnmanagedCallersOnlyManagedSite_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn void CalliTests::UnmanagedTarget() - calli void() - ret - } - - // An UnmanagedCallersOnly method cannot be called through a managed vararg call site either. - .method public hidebysig static void Calli.UnmanagedCallersOnlyVarargSite_Invalid_Unverifiable.CalliSignatureMismatch() cil managed - { - ldftn void CalliTests::UnmanagedTarget() - calli vararg void() - ret - } - // A vararg call site with no extra arguments has no sentinel. .method public hidebysig static void Calli.Vararg_Invalid_Unverifiable(method vararg void *(int32) f) cil managed { @@ -331,17 +179,6 @@ } } -.class public sequential ansi sealed beforefieldinit CalliStruct - extends [System.Runtime]System.ValueType -{ - .field public int32 Value - - .method public hidebysig instance void InstanceTarget() cil managed - { - ret - } -} - .class public auto ansi beforefieldinit CalliConstructorTests extends [System.Runtime]System.Object { diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index 5580f2ac93a4c1..7770c8ffba7b29 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -1868,10 +1868,6 @@ void ImportCalli(int token) if (implicitThis && tailCall) Check(!IsByRefLike(args[0]), VerifierError.TailByRef, args[0]); - // A method pointer pushed by ldftn or ldvirtftn carries its method, so the call site can be checked against it. - if (ftn.IsMethod) - CheckCalliTarget(ftn.Method, sig, args); - if (tailCall) CheckTailCallReturn(sig.ReturnType); @@ -1886,51 +1882,6 @@ void ImportCalli(int token) } } - /// - /// Checks the call site of a calli against the method that the method pointer was loaded from. - /// ECMA-335 III.3.20 (Correctness) requires the method's signature to be method-signature compatible-with - /// the call site's, ignoring HASTHIS and EXPLICITTHIS, so 'this' is compared as a first parameter - /// (I.8.7.1). Parameter and return types are compared in the same direction as in IsDelegateAssignable. - /// - void CheckCalliTarget(MethodDesc target, MethodSignature site, StackValue[] args) - { - MethodSignature targetSig = target.Signature; - int firstTargetParameter = (targetSig.IsStatic || targetSig.IsExplicitThis) ? 0 : 1; - int firstSiteParameter = args.Length - site.Length; - - // Metadata gives an UnmanagedCallersOnly method a managed signature, but it is called through an unmanaged - // call site. Only managed vs unmanaged is compared; the attribute's CallConvs are not. - var callingConvention = site.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask; - bool callingConventionMatches = target.IsUnmanagedCallersOnly ? - callingConvention is not (MethodSignatureFlags.None or MethodSignatureFlags.CallingConventionVarargs) : - callingConvention == (targetSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask); - - if (!callingConventionMatches || - targetSig.Length + firstTargetParameter != args.Length || - !IsAssignable(targetSig.ReturnType, site.ReturnType)) - { - VerificationError(VerifierError.CalliSignatureMismatch); - return; - } - - for (int i = 0; i < args.Length; i++) - { - TypeDesc targetType = i < firstTargetParameter ? - (target.OwningType.IsValueType ? target.OwningType.MakeByRefType() : target.OwningType) : - targetSig[i - firstTargetParameter]; - - if (i < firstSiteParameter) - { - // The call site does not give the type of 'this', so check the argument itself. - CheckIsAssignable(args[i], StackValue.CreateFromType(targetType)); - } - else if (!IsAssignable(site[i - firstSiteParameter], targetType)) - { - VerificationError(VerifierError.CalliSignatureMismatch); - } - } - } - void ImportLdFtn(int token, ILOpcode opCode) { MethodDesc method = ResolveMethodToken(token); diff --git a/src/coreclr/tools/ILVerification/Strings.resx b/src/coreclr/tools/ILVerification/Strings.resx index 7f5d494879e1ec..8e000e803b0002 100644 --- a/src/coreclr/tools/ILVerification/Strings.resx +++ b/src/coreclr/tools/ILVerification/Strings.resx @@ -168,9 +168,6 @@ Callvirt on a value type method. - - Method pointer signature does not match the calli call-site signature. - ByRef not allowed as catch type. diff --git a/src/coreclr/tools/ILVerification/VerifierError.cs b/src/coreclr/tools/ILVerification/VerifierError.cs index 7790327e4c0145..40c33f2d6b5bd5 100644 --- a/src/coreclr/tools/ILVerification/VerifierError.cs +++ b/src/coreclr/tools/ILVerification/VerifierError.cs @@ -143,7 +143,6 @@ public enum VerifierError EHClauseFilterEnd, // A filter must have exactly one endfilter as its last instruction. UnalignedAlignment, // Alignment specified for 'unaligned.' must be 1, 2, or 4. ExpectedSignatureToken, // Expected signature token. - CalliSignatureMismatch, // Method pointer signature does not match the calli call-site signature. ReadOnlyInMkrefany, // A readonly ByRef cannot be used with mkrefany. } }