-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Definite assignment bug when not awaiting an async closure #43697
Comments
Also repros in VS 16.6-p4 |
What makes you think this is a bug? The local function doesn't contain any |
@svick Right, but the local function throws an exception, and so never assigns to |
The following program prints false, instead of throwing. If SkipLocalsInit was applied, it would be non-deterministic. using System;
using System.Threading.Tasks;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
bool a;
M1();
Console.WriteLine(a);
async Task M1()
{
if ("" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
}
} |
@canton7 @YairHalberstadt Thanks, you're right. The part that I missed (and probably also the mistake in the compiler's analysis) is that an exception thrown from the synchronous part of an |
Assigned to @agocke to advise |
Oops. Yup, that's what I overlooked. I suppose one fix is that calls to async local functions could always be considered reachable at the end. |
Assigned to @RikkiGibson since he's been deep in definite assignment recently and Milestone=Compiler.Next. Feel free to further triage/move if needed. |
I think the solution Andy suggested is good and we should be able to knock it out before too long. |
I did some digging into implementation of flow analysis around local functions. Here is what I found. Finding #1. For the purpose of definite assignment analysis, await expressions and the like are treated in local functions as returns. This is achieved by registering a pending branch while visiting await expression here (https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/FlowAnalysis/AbstractFlowPass.cs,2583) and joining state from all of them in the loop here (https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs,131).
Compiler’s behavior looks reasonable. ‘M1’ is not awaited, therefore, ‘a’ can be accessed before it is assigned in the local function. Finding #2. We do not make any adjustments to the analysis depending on whether an async local function is awaited or not. This can be observed with the following code (https://sharplab.io/#v2:C4LglgNgPgAgTARgLACgYAYAEMEFYDcqqAxAA4BOAhgOYC2lmA7peQHZivWYAmYAzpQBGEAKaYAwgGUALOgTTMAekWYAQiIDGlAK58xwABb9MWiBEzHWAe2DYAnCO4AaTCIAem7cDBXWmKwBmmIZiGtrk5CKstrQihlbcJr7erNoifJiCIgFWkcEGoZRmFhkaVrSkosCOAHQSvnxg3CLkmJSklQCeHFwhmADkMHb9/qQtlMC5wVb5YpF82hC2gbMmRRA1JBQ09Ews7Jw8/EKiEpIIdnYAHEoqACpGGTA3sfGJEJQaANYZg8Oj40m5AylFYiUYkHM5G0fj4nVYGgM5F8Vl0EE6dXEDSaLWwWD6fxGVjGVCB03smGsrAAtMIrN8epgAIIABQAkmszHwXFM/tgAGw1ABKMIAFDUJQBKEaTHgzcQsgCqtNRYKYuS+/j8DEEny+1GRMMShkilG4mxQqBgAGZsHAJKgAN6oTCu7C254CzAAWVFkpdbudKDdIcyVis5kohGDoddQx9CD90djcYuosokuTKYDsc9MH5Cb9OdDQZTobAQVFACIq5gALx1zCSYDkHo1ACiFWAnX9MbLrtL/dzdgFNQAmmARBBuEni2WAL5EPv9hiNltpLOxxfL7fzoA===), which simply adds await at the local function’s call site to the example above:
The error is still reported even though await in ‘M1’ now interrupts execution in ‘M’, and, once execution resumes, ‘a’ is going to be definitely assigned. Back to exceptions In my opinion, for the purpose of definite assignment analysis, explicit throws inside an async local function are somewhat similar to awaits. I.e., if the local function is not awaited, a throw is equivalent to a return from the local function. If the local function is awaited, a throw is equivalent to a throw at the local function’s call site. The analysis will be more accurate if we also detect situations when thrown exceptions are definitely caught inside the local function. For example, catch all or catch the same (or base) exception type. But we will have to track rethrow situations as well. The analysis will be even more accurate if we also analyze finally-es, those might still assign things The analysis will be even more accurate if we start adjusting analysis for definitely awaited call sites. |
We had a short e-email discussion about this issue, here is a brief summary: This may seem draconian in some places – especially when the async local function is immediately being awaited. But if we want to fix this, we should introduce a notion of “immediately awaited calls”: When an async local function call is “immediately awaited” we treat it as if the body was invoked synchronously, any exceptions are propagated, in their absence code executes to the end, etc. (Async iterators would be treated like synchronous iterators, whatever that means). Either way the fix will be a breaking change. If we do the “immediately awaited calls” thing it will be more work, but we suspect the break will also be a lot smaller. |
…ode to execute Fixes dotnet#43697.
Sounds like the right change to me. Similar to the analysis we do for try/catch/finally, where none of the code in the try block is assumed to have been run in the catch/finally, in case a ThreadAbort happened before the first instruction. |
Version Used: master as of 10th march 2020
Steps to Reproduce:
Expected Behavior:
error CS0165: Use of unassigned local variable 'a'
Actual Behavior:
Compiles without error
The text was updated successfully, but these errors were encountered: