Skip to content
This repository was archived by the owner on Jul 15, 2023. It is now read-only.
This repository was archived by the owner on Jul 15, 2023. It is now read-only.

Async postconditions wraps original exception into additional AE #155

@SergeyTeplyakov

Description

@SergeyTeplyakov

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.

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions