-
Notifications
You must be signed in to change notification settings - Fork 151
Async postconditions wraps original exception into additional AE #155
Description
Current implementation of the asynchronous postconditions is wrong. Consider following code:
static async Task<int> WillThrow()
{
Contract.Ensures(Contract.Result<int>() == 42);
await Task.Delay(42);
throw new InvalidOperationException();
}
static async Task Run()
{
try
{
await WillThrow();
}
catch (InvalidOperationException)
{
Console.WriteLine("Expected!");
}
catch (AggregateException ae)
{
Console.WriteLine("NOT EXPECTED!");
}
}In this case Expected! should be printed with or without any postconditions in the WillThrow method. Unfortunately, right now NOT EXPECTED! would be printed.
Here is a reason for this. ccrewrite uses following approach: AsyncClosure class is generated with int CheckPost(Task<int> task) method:
public int CheckPost(Task<int> task)
{
int result = task.Result;
if (__ContractsRuntime.insideContractEvaluation <= 4)
{
try
{
__ContractsRuntime.insideContractEvaluation++;
__ContractsRuntime.Ensures(task.Result == 42, null, "Contract.Result<int>() == 42");
}
finally
{
__ContractsRuntime.insideContractEvaluation--;
}
}
return result;
}And following transformation are made for original async method:
public Task<int> WillThrow(string str)
{
ThrowsWithAsyncPostconditionMixed.<WillThrow>AsyncContractClosure_0 @object = new ThrowsWithAsyncPostconditionMixed.<WillThrow>AsyncContractClosure_0();
__ContractsRuntime.Requires(str != null, null, "str != null");
ThrowsWithAsyncPostconditionMixed.<WillThrow>d__0 <WillThrow>d__;
<WillThrow>d__.<>4__this = this;
<WillThrow>d__.str = str;
<WillThrow>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
<WillThrow>d__.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = <WillThrow>d__.<>t__builder;
<>t__builder.Start<ThrowsWithAsyncPostconditionMixed.<WillThrow>d__0>(ref <WillThrow>d__);
Task<int> task = <WillThrow>d__.<>t__builder.Task;
Func<Task<int>, int> continuationFunction = new Func<Task<int>, int>(@object.CheckPost);
return task.ContinueWith<int>(continuationFunction);
}The problem here, that async method was changed to return new task that is result of the continuation task. But if original task fails, continuation function will try to get result via Result property. This will lead to AggregateException.
Basically, when the task faults original error would be wrapped into another AggregateException object that will break any clients that will try to handle them.