Skip to content

Commit b82dbda

Browse files
author
Julien Couvreur
committed
Update ILVerify to honor the "async" flag
1 parent 4459d20 commit b82dbda

File tree

3 files changed

+277
-5
lines changed

3 files changed

+277
-5
lines changed

src/coreclr/tools/ILVerification/ILImporter.Verify.cs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,13 +1869,32 @@ void ImportReturn()
18691869
Check(_isThisInitialized || _thisType.IsObject, VerifierError.ThisUninitReturn);
18701870

18711871
// Check current region type
1872+
// Async methods can return from try and catch blocks (but not filter blocks)
18721873
Check(_currentBasicBlock.FilterIndex == null, VerifierError.ReturnFromFilter);
1873-
Check(_currentBasicBlock.TryIndex == null, VerifierError.ReturnFromTry);
1874-
Check(_currentBasicBlock.HandlerIndex == null, VerifierError.ReturnFromHandler);
1874+
if (!_method.IsAsync)
1875+
{
1876+
Check(_currentBasicBlock.TryIndex == null, VerifierError.ReturnFromTry);
1877+
Check(_currentBasicBlock.HandlerIndex == null, VerifierError.ReturnFromHandler);
1878+
}
18751879

18761880
var declaredReturnType = _method.Signature.ReturnType;
18771881

1878-
if (declaredReturnType.IsVoid)
1882+
// For async methods, unwrap Task/ValueTask return types
1883+
TypeDesc expectedReturnType = declaredReturnType;
1884+
if (_method.IsAsync)
1885+
{
1886+
if (IsTaskOrValueTaskType(declaredReturnType, out TypeDesc unwrappedType))
1887+
{
1888+
expectedReturnType = unwrappedType;
1889+
}
1890+
else
1891+
{
1892+
// Async methods must return Task or ValueTask
1893+
VerificationError(VerifierError.StackUnexpected);
1894+
}
1895+
}
1896+
1897+
if (expectedReturnType.IsVoid)
18791898
{
18801899
Debug.Assert(_stackTop >= 0);
18811900

@@ -1891,11 +1910,49 @@ void ImportReturn()
18911910
Check(_stackTop == 1, VerifierError.ReturnEmpty);
18921911

18931912
var actualReturnType = Pop();
1894-
CheckIsAssignable(actualReturnType, StackValue.CreateFromType(declaredReturnType));
1913+
CheckIsAssignable(actualReturnType, StackValue.CreateFromType(expectedReturnType));
1914+
1915+
Check((!expectedReturnType.IsByRef && !expectedReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack);
1916+
}
1917+
}
1918+
}
1919+
1920+
bool IsTaskOrValueTaskType(TypeDesc type, out TypeDesc unwrappedType)
1921+
{
1922+
unwrappedType = null;
18951923

1896-
Check((!declaredReturnType.IsByRef && !declaredReturnType.IsByRefLike) || actualReturnType.IsPermanentHome, VerifierError.ReturnPtrToStack);
1924+
if (type is not MetadataType metadataType)
1925+
return false;
1926+
1927+
if (!metadataType.Namespace.SequenceEqual("System.Threading.Tasks"u8))
1928+
return false;
1929+
1930+
// Check for Task (non-generic)
1931+
if (metadataType.Name.SequenceEqual("Task"u8) && !metadataType.HasInstantiation)
1932+
{
1933+
unwrappedType = _typeSystemContext.GetWellKnownType(WellKnownType.Void);
1934+
return true;
1935+
}
1936+
1937+
// Check for ValueTask (non-generic)
1938+
if (metadataType.Name.SequenceEqual("ValueTask"u8) && !metadataType.HasInstantiation)
1939+
{
1940+
unwrappedType = _typeSystemContext.GetWellKnownType(WellKnownType.Void);
1941+
return true;
1942+
}
1943+
1944+
// Check for Task<T> and ValueTask<T>
1945+
if (metadataType.HasInstantiation && metadataType.Instantiation.Length == 1)
1946+
{
1947+
if (metadataType.Name.SequenceEqual("Task`1"u8) ||
1948+
metadataType.Name.SequenceEqual("ValueTask`1"u8))
1949+
{
1950+
unwrappedType = metadataType.Instantiation[0];
1951+
return true;
18971952
}
18981953
}
1954+
1955+
return false;
18991956
}
19001957

19011958
void ImportFallthrough(BasicBlock next)
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
.assembly extern System.Runtime
5+
{
6+
}
7+
8+
.assembly RuntimeAsyncTests
9+
{
10+
}
11+
12+
.class public auto ansi beforefieldinit RuntimeAsyncTestsType
13+
extends [System.Runtime]System.Object
14+
{
15+
// Valid async method returning ValueTask<int> with int on stack
16+
// [ExpectLocalsInit] - removed, async methods should work with or without .locals init
17+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncValueTaskInt_Valid() cil managed async
18+
{
19+
ldc.i4.0
20+
ret
21+
}
22+
23+
// Valid async method returning ValueTask<bool> with bool (i4) on stack
24+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<bool> AsyncValueTaskBool_Valid() cil managed async
25+
{
26+
ldc.i4.1
27+
ret
28+
}
29+
30+
// Valid async method returning ValueTask with nothing on stack
31+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask AsyncValueTaskVoid_Valid() cil managed async
32+
{
33+
ret
34+
}
35+
36+
// Valid async method with complex control flow
37+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncWithBranching_Valid() cil managed async
38+
{
39+
.maxstack 2
40+
.locals init ([0] int32)
41+
42+
ldarg.0
43+
brfalse.s branch1
44+
45+
ldc.i4.1
46+
stloc.0
47+
br.s end
48+
49+
branch1:
50+
ldc.i4.0
51+
stloc.0
52+
53+
end:
54+
ldloc.0
55+
ret
56+
}
57+
58+
// Valid async method returning ValueTask<object> with object on stack
59+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<object> AsyncValueTaskObject_Valid() cil managed async
60+
{
61+
ldnull
62+
ret
63+
}
64+
65+
// Invalid: async method with wrong stack state - empty stack for ValueTask<int>
66+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncEmptyStack_Invalid_ReturnMissing() cil managed async
67+
{
68+
ret
69+
}
70+
71+
// Invalid: async method with wrong type on stack
72+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncWrongType_Invalid_StackUnexpected() cil managed async
73+
{
74+
ldnull
75+
ret
76+
}
77+
78+
// Invalid: async method with extra items on stack
79+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncExtraStack_Invalid_ReturnEmpty() cil managed async
80+
{
81+
ldc.i4.0
82+
ldc.i4.1
83+
ret
84+
}
85+
86+
// Invalid: async method returning full ValueTask<int> instead of unwrapped int
87+
// With the async flag, the method should return just the int32, not the wrapped ValueTask<int>
88+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncReturnsWrappedValueTask_Invalid_StackUnexpected() cil managed async
89+
{
90+
.locals init ([0] valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32>)
91+
92+
ldc.i4.5
93+
call valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!0> valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32>::.ctor(!0)
94+
stloc.0
95+
ldloc.0
96+
ret
97+
}
98+
99+
// Invalid: async method returning full ValueTask instead of void/empty stack
100+
// With the async flag, the method should return nothing (empty stack), not the wrapped ValueTask
101+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask AsyncReturnsWrappedValueTaskVoid_Invalid_StackUnexpected() cil managed async
102+
{
103+
.locals init ([0] valuetype [System.Runtime]System.Threading.Tasks.ValueTask)
104+
105+
call valuetype [System.Runtime]System.Threading.Tasks.ValueTask [System.Runtime]System.Threading.Tasks.ValueTask::get_CompletedTask()
106+
stloc.0
107+
ldloc.0
108+
ret
109+
}
110+
111+
// Invalid: non-async method with async flag but returning wrong type
112+
.method public hidebysig instance int32 NonAsyncReturnType_Invalid_StackUnexpected() cil managed async
113+
{
114+
ldc.i4.0
115+
ret
116+
}
117+
118+
// Valid: async method returning Task<int> with int on stack
119+
// The async flag works with both Task and ValueTask
120+
.method public hidebysig instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> AsyncTaskInt_Valid() cil managed async
121+
{
122+
ldc.i4.0
123+
ret
124+
}
125+
126+
// Valid: async method returning Task with nothing on stack
127+
// The async flag works with both Task and ValueTask
128+
.method public hidebysig instance class [System.Runtime]System.Threading.Tasks.Task AsyncTask_Valid() cil managed async
129+
{
130+
ret
131+
}
132+
133+
// Valid: async method can return from try block (unlike non-async)
134+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncReturnFromTry_Valid() cil managed async
135+
{
136+
.try
137+
{
138+
ldc.i4.0
139+
ret
140+
}
141+
catch [System.Runtime]System.Object
142+
{
143+
pop
144+
ldc.i4.1
145+
ret
146+
}
147+
}
148+
149+
// Valid: async method returning from catch block
150+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncReturnFromCatch_Valid() cil managed async
151+
{
152+
.try
153+
{
154+
leave.s lbl_ret
155+
}
156+
catch [System.Runtime]System.Object
157+
{
158+
pop
159+
ldc.i4.0
160+
ret
161+
}
162+
163+
lbl_ret:
164+
ldc.i4.1
165+
ret
166+
}
167+
168+
// Invalid: async method returning from filter (still not allowed)
169+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncReturnFromFilter_Invalid_ReturnFromFilter() cil managed async
170+
{
171+
.try
172+
{
173+
leave.s lbl_ret
174+
}
175+
filter
176+
{
177+
pop
178+
ldc.i4.0
179+
ret
180+
181+
endfilter
182+
}
183+
{
184+
pop
185+
leave.s lbl_ret
186+
}
187+
188+
lbl_ret:
189+
ldc.i4.1
190+
ret
191+
}
192+
193+
// Valid: async method with locals
194+
.method public hidebysig instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<int32> AsyncWithLocals_Valid() cil managed async
195+
{
196+
.locals init ([0] int32, [1] bool)
197+
198+
ldc.i4.5
199+
stloc.0
200+
ldc.i4.1
201+
stloc.1
202+
ldloc.0
203+
ret
204+
}
205+
206+
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
207+
{
208+
ldarg.0
209+
call instance void [System.Runtime]System.Object::.ctor()
210+
ret
211+
}
212+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+
<Import Project="ILTests.targets" />
3+
</Project>

0 commit comments

Comments
 (0)