diff --git a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs index 430c7e083b8ece..434d2ab7687b38 100644 --- a/src/coreclr/tools/ILVerification/ILImporter.Verify.cs +++ b/src/coreclr/tools/ILVerification/ILImporter.Verify.cs @@ -1875,7 +1875,22 @@ void ImportReturn() var declaredReturnType = _method.Signature.ReturnType; - if (declaredReturnType.IsVoid) + // For async methods, unwrap Task/ValueTask return types + TypeDesc expectedReturnType = declaredReturnType; + if (_method.IsAsync) + { + if (IsTaskOrValueTaskType(declaredReturnType, out TypeDesc unwrappedType)) + { + expectedReturnType = unwrappedType; + } + else + { + // Async methods must return Task or ValueTask + VerificationError(VerifierError.StackUnexpected); + } + } + + if (expectedReturnType.IsVoid) { Debug.Assert(_stackTop >= 0); @@ -1891,11 +1906,49 @@ void ImportReturn() Check(_stackTop == 1, VerifierError.ReturnEmpty); var actualReturnType = Pop(); - CheckIsAssignable(actualReturnType, StackValue.CreateFromType(declaredReturnType)); + CheckIsAssignable(actualReturnType, StackValue.CreateFromType(expectedReturnType)); + + Check((!expectedReturnType.IsByRef && !expectedReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack); + } + } + } + + bool IsTaskOrValueTaskType(TypeDesc type, out TypeDesc unwrappedType) + { + unwrappedType = null; + + if (type is not MetadataType metadataType) + return false; + + if (!metadataType.Namespace.SequenceEqual("System.Threading.Tasks"u8)) + return false; + + // Check for Task (non-generic) + if (metadataType.Name.SequenceEqual("Task"u8) && !metadataType.HasInstantiation) + { + unwrappedType = _typeSystemContext.GetWellKnownType(WellKnownType.Void); + return true; + } - Check((!declaredReturnType.IsByRef && !declaredReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack); + // Check for ValueTask (non-generic) + if (metadataType.Name.SequenceEqual("ValueTask"u8) && !metadataType.HasInstantiation) + { + unwrappedType = _typeSystemContext.GetWellKnownType(WellKnownType.Void); + return true; + } + + // Check for Task and ValueTask + if (metadataType.HasInstantiation && metadataType.Instantiation.Length == 1) + { + if (metadataType.Name.SequenceEqual("Task`1"u8) || + metadataType.Name.SequenceEqual("ValueTask`1"u8)) + { + unwrappedType = metadataType.Instantiation[0]; + return true; } } + + return false; } void ImportFallthrough(BasicBlock next) diff --git a/src/tests/ilverify/ILTests/RuntimeAsyncTests.il b/src/tests/ilverify/ILTests/RuntimeAsyncTests.il new file mode 100644 index 00000000000000..2a4c4334ac2df7 --- /dev/null +++ b/src/tests/ilverify/ILTests/RuntimeAsyncTests.il @@ -0,0 +1,159 @@ +// 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 RuntimeAsyncTests +{ +} + +.class public auto ansi beforefieldinit RuntimeAsyncTestsType + extends [System.Runtime]System.Object +{ + // Valid async method returning Task with int on stack + .method public hidebysig instance class [System.Runtime]System.Threading.Tasks.Task`1 AsyncTaskInt_Valid() cil managed async + { + ldc.i4.0 + ret + } + + // Valid async method returning ValueTask with int on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncValueTaskInt_Valid() cil managed async + { + ldc.i4.0 + ret + } + + // Valid async method returning ValueTask with bool (i4) on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncValueTaskBool_Valid() cil managed async + { + ldc.i4.1 + ret + } + + // Valid async method returning ValueTask with object on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncValueTaskObject_Valid() cil managed async + { + ldnull + ret + } + + // Invalid: async method with wrong stack state - empty stack for ValueTask + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncEmptyStack_Invalid_ReturnMissing() cil managed async + { + ret + } + + // Invalid: async method with wrong type on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncWrongType_Invalid_StackUnexpected() cil managed async + { + ldnull + ret + } + + // Invalid: async method with extra items on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncExtraStack_Invalid_ReturnEmpty() cil managed async + { + ldc.i4.0 + ldc.i4.1 + ret + } + + // Invalid: async method with ValueTask return should have empty stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask AsyncReturnsInt_Invalid_ReturnVoid() cil managed async + { + ldc.i4.0 + ret + } + + // Invalid: method with async flag but returning integer type + .method public hidebysig instance int32 NonAsyncReturnType_Invalid_StackUnexpected() cil managed async + { + ldc.i4.0 + ret + } + + // Valid: async method returning Task with nothing on stack + .method public hidebysig instance class [System.Runtime]System.Threading.Tasks.Task AsyncTask_Valid() cil managed async + { + ret + } + + // Valid: async method returning ValueTask with nothing on stack + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask AsyncValueTask_Valid() cil managed async + { + ret + } + + // Invalid: async method returning from try block (not allowed) + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncReturnFromTry_Invalid_ReturnFromTry() cil managed async + { + .try + { + ldc.i4.0 + ret + } + catch [System.Runtime]System.Object + { + pop + leave.s lbl_ret + } + + lbl_ret: + ldc.i4.1 + ret + } + + // Invalid: async method returning from catch block (not allowed) + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncReturnFromCatch_Invalid_ReturnFromHandler() cil managed async + { + .try + { + leave.s lbl_ret + } + catch [System.Runtime]System.Object + { + pop + ldc.i4.0 + ret + } + + lbl_ret: + ldc.i4.1 + ret + } + + // Invalid: async method returning from filter (still not allowed) + .method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 AsyncReturnFromFilter_Invalid_ReturnFromFilter() cil managed async + { + .try + { + leave.s lbl_ret + } + filter + { + pop + ldc.i4.0 + ret + + endfilter + } + { + pop + leave.s lbl_ret + } + + lbl_ret: + ldc.i4.1 + 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/tests/ilverify/ILTests/RuntimeAsyncTests.ilproj b/src/tests/ilverify/ILTests/RuntimeAsyncTests.ilproj new file mode 100644 index 00000000000000..8e8765d14a524d --- /dev/null +++ b/src/tests/ilverify/ILTests/RuntimeAsyncTests.ilproj @@ -0,0 +1,3 @@ + + +