Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VB.NET: Async method state machine "losing" locals - Release only (Async/Await + Try/Finally + For) #9001

Closed
KirillShlenskiy opened this issue Feb 22, 2016 · 5 comments
Labels
Area-Compilers Bug Resolution-Duplicate The described behavior is tracked in another issue Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece. Verification Not Required

Comments

@KirillShlenskiy
Copy link

I've come across a very strange NullReferenceException issue which only manifests itself when building in Release configuration (with Advanced Compile Options -> Enable optimizations enabled).

This occurs in Async methods (both Task-returning and Async Sub) which have a Try/Finally block where the Try contains a For loop, which in turn contains an Await statement. When a local variable pointing to a reference type instance is declared before such a Try/Finally and accessed inside the Finally, an "impossible" NullReferenceException is thrown.

Here is a complete Console application demonstrating the issue when built with VS2015 (targeting .NET4.5, Release):

Module Module1

  Sub Main()
    TestAsync().Wait()
  End Sub

  Async Function TestAsync() As Task
    Dim obj As New Object

    Try
      For i = 0 To 0
        Await Task.Yield()
      Next
    Finally
      obj.GetType() ' Throws System.NullReferenceException
    End Try
  End Function

End Module

Analysis

Looking at the Release binary with ILDASM, the compiler-generated VB$StateMachine_1_TestAsync is missing the following field definition:

.field assembly object $VB$ResumableLocal_obj$0

Here are the relevant bits of MoveNext IL in Debug (works fine):

ldarg.0
newobj     instance void [mscorlib]System.Object::.ctor()
call       object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
stfld      object ConsoleRepro.Module1/VB$StateMachine_1_TestAsync::$VB$ResumableLocal_obj$0
... Await ...
ldarg.0
ldfld      object ConsoleRepro.Module1/VB$StateMachine_1_TestAsync::$VB$ResumableLocal_obj$0
callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

VS Release (throws):

newobj     instance void [mscorlib]System.Object::.ctor()
call       object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
stloc.1
... Await ...
ldloc.1
callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

The above works fine if the Await completes synchronously, but if not - the local is uninitialized after Await, hence the NullReferenceException.

Environment

The above has been reproduced using:

  • Visual Studio 2015 Professional (14.0.24720.00 Update 1)
  • Visual Studio 2015 Community (14.0.23107.0)

Addendum

If obj is IDisposable, you can replace Try/Finally with a Using statement and then the problem is even worse due to the null check auto-inserted by the compiler before the obj.Dispose call, meaning that NullReferenceException is never thrown - Dispose simply doesn't get called, i.e.

Async Function TestAsync() As Task
  Using obj As New MyDisposable ' IDisposable
    For i = 0 To 0
      Await Task.Yield()
    Next
  End Using ' obj.Dispose *not* called
End Function
@KirillShlenskiy KirillShlenskiy changed the title VB.NET: serious Async state machine bug - Release only (Async/Await + Try/Finally + For + IDisposable initialization) VB.NET: Async method state machine bug - Release only (Async/Await + Try/Finally + For + IDisposable initialization) Feb 22, 2016
@davkean davkean added Bug Area-Compilers Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece. labels Feb 22, 2016
@KirillShlenskiy KirillShlenskiy changed the title VB.NET: Async method state machine bug - Release only (Async/Await + Try/Finally + For + IDisposable initialization) VB.NET: Async method state machine "losing" locals - Release only (Async/Await + Try/Finally + For) Feb 22, 2016
@roken
Copy link

roken commented Feb 23, 2016

This also impacts the state machine generated for iterator methods, and the characteristics you describe are identical: http://stackoverflow.com/questions/35568392/vb-net-iterator-function-loses-local-variables?noredirect=1#comment58844116_35568392

@KirillShlenskiy
Copy link
Author

Thanks @roken

I'm not certain that this is the same issue but I suppose it might be.

I've done a bit of digging and found that the asynchronous variable capture issue is, apparently, already fixed:

#7693

I have confirmed that the code I posted in the opening comment for this issue (9001) compiles and executes as expected using the latest Roslyn source, so I intend to self-close this issue.

As for the iterator problem, I have answered your SO question with an example unit test which passes using the latest version of Roslyn, so I am assuming that it is also fixed.

@KirillShlenskiy
Copy link
Author

Thanks @davkean

I am now closing this issue as I have confirmed that it's been fixed in the latest codebase.

@davkean
Copy link
Member

davkean commented Feb 24, 2016

Thanks for the followup @KirillShlenskiy, appreciate it.

@davkean
Copy link
Member

davkean commented Feb 24, 2016

Dupe of #7693

@davkean davkean added the Resolution-Duplicate The described behavior is tracked in another issue label Feb 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Bug Resolution-Duplicate The described behavior is tracked in another issue Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece. Verification Not Required
Projects
None yet
Development

No branches or pull requests

4 participants