There already are some open issues that deal with the rewriter missing Asserts/Assumes in async methods/iterators (notably #41), however, these are concerned with rather complex conditions in these Asserts.
This issue is about Contract.Requires with simple conditions (such as param != null) in async methods compiled by Roslyn (VS 2015).
Simple working example (class library project):
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
namespace RewriterWithRoslynRepro
{
public class Class
{
public async Task<int> AsyncMethod(string str)
{
Contract.Requires(str != null);
await Task.Delay(100);
return 0;
}
}
}
After some investigation (I am not familiar with these sources), the rewriter seems to do the following steps upon encountering an async method (i.e. a method that sets up a state machine and so on)
- it looks up the
MoveNext method of the state machine that is generated by the compiler
- it simulates what
MoveNext would do when it's called for the first time
- it finds an IL block "B" that possibly contains a
Contract.Requires statement
- it looks up the last
Contract.Requires statement that occurs after or in "B". By last, I mean highest in terms of the IL offset of the statement.
The lookup of such a block "B" succeeds for IL compiled with the native compiler but fails for Roslyn compiled IL. Upon further investigation, i found this assumption in the code.
From my experiments with Roslyn compiled code, this does not hold (any more?). The initial state is still -1. Changing this line to var initialState = isAsync ? -1 : 0; lets the rewriter find the block.
Can someone with insight into Roslyn confirm that the initial state of an async state machine is indeed -1 and not 0 ?
EDIT: According to the Roslyn sources (here and here) the initial state is indeed -1.