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..cd0f85d74234da --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/CalliTests.il @@ -0,0 +1,200 @@ +// 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 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 instance void InstanceTarget(int32 x) cil managed + { + 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.ExplicitThis_Invalid_Unverifiable() cil managed + { + ldarg.0 + ldc.i4.1 + ldftn instance void CalliTests::InstanceTarget(int32) + calli instance explicit void(class CalliTests, int32) + 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 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..7770c8ffba7b29 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,81 @@ 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]); + + if (tailCall) + CheckTailCallReturn(sig.ReturnType); + + if (!sig.ReturnType.IsVoid) + { + var returnValue = StackValue.CreateFromType(sig.ReturnType); + + if (returnValue.Kind == StackValueKind.ByRef) + returnValue.SetIsPermanentHome(); + + Push(returnValue); + } } void ImportLdFtn(int token, ILOpcode opCode) @@ -2918,7 +2966,7 @@ void ReportInvalidExceptionRegion() } // - // Deprecated + // Variable argument lists and typed references // void ImportArgList() @@ -2928,17 +2976,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. + // 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. + Check(!ptr.IsReadOnly, VerifierError.ReadOnlyInMkrefany); + + if (ptr.Type != type) + VerificationError(VerifierError.StackUnexpected, ptr, StackValue.CreateByRef(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..8e000e803b0002 100644 --- a/src/coreclr/tools/ILVerification/Strings.resx +++ b/src/coreclr/tools/ILVerification/Strings.resx @@ -231,6 +231,9 @@ Expected numeric type on the stack. + + Expected signature token. + Expected static field. @@ -336,6 +339,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..40c33f2d6b5bd5 100644 --- a/src/coreclr/tools/ILVerification/VerifierError.cs +++ b/src/coreclr/tools/ILVerification/VerifierError.cs @@ -142,5 +142,7 @@ 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. + ReadOnlyInMkrefany, // A readonly ByRef cannot be used with mkrefany. } }