-
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
Correctly record the dispose method to be invoked in operations #49481
Correctly record the dispose method to be invoked in operations #49481
Conversation
@dotnet/roslyn-compiler for review Note: this is targeting @333fred's IOperation refactoring branch rather than master to reduce merge issues |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done review pass (commit 2)
@@ -485,6 +485,11 @@ | |||
</summary> | |||
</Comments> | |||
</Property> | |||
<Property Name="DisposeMethod" Type="IMethodSymbol?"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't feel like making this only be populated if it's not IDisposable.Dispose
is particularly useful, it feels more like an implementation detail bleeding through. I think we should just always populate this, and if it's IDiposable
then we populate it with IDisposable.Dispose
. #Closed
@@ -3862,7 +3862,7 @@ void processResource(IOperation resource, ArrayBuilder<(IVariableDeclarationOper | |||
|
|||
LeaveRegion(); | |||
|
|||
AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable, isAsynchronous); | |||
AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable, disposeMethod, isAsynchronous); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's certainly not know to implement IDisposable
if it's a ref struct. Please update the comments above and the parameter name. #Closed
- Always populate the dispose method in binding, even if its for I{Async}Disposable - Remove knownToImplementIDisposable - Just use the precalcualted dispose method in IOperation rather than calculating it - Use the presence of the DisposeMethod to calculate the expected output in CFG - Correctly report invalid operations in CFG - Update tests
@333fred / @AlekseyTs assuming tests are good, this should be good to review now. By always populating the dispose method in binding I was able to remove a bunch of logic in the operation and lowering to just use what we found, instead of calculating it again. |
Note to self: run VB tests locally too. 🤦♂️ |
Note on the determinism test: I haven't merged Aleksey's fix from master into this branch yet, so it's flaky. |
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
AppendNewBlock(endOfFinally); | ||
var isPatternDispose = !disposeMethod.ContainingType.Equals(iDisposable, SymbolEqualityComparer.Default); | ||
var isNull = resource.GetConstantValue() == ConstantValue.Null; | ||
var requiresConversion = !isPatternDispose && !isAsynchronous && !isNull && !_compilation.HasImplicitConversion(resource.Type, iDisposable) && !isNonNullableValueType(resource.Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!_compilation.HasImplicitConversion(resource.Type, iDisposable) [](start = 92, length = 63)
If it has an implicit conversion, we probably still need to represent that implicit conversion, right? Or am I misunderstanding the expansion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it has an implicit conversion we'll represent that later on. This checks if we're doing a runtime conversion check. That only happens in foreach on enumerators. The simplest example is with a dynamic enumerator: we try and convert it to IDisposable first then check it for null. I'll give the var a more descriptive name.
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
Done review pass (commit 9) |
…cfg_pattern_dispose
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
@@ -125,7 +125,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo | |||
if (declarationTypeOpt.IsDynamic()) | |||
{ | |||
iDisposableConversion = Conversion.ImplicitDynamic; | |||
disposeMethodOpt = null; | |||
disposeMethodOpt = originalBinder.Compilation.GetWellKnownDisposeMethod(isAsync: hasAwait); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originalBinder.Compilation.GetWellKnownDisposeMethod(isAsync: hasAwait); [](start = 39, length = 72)
Same comment as for other call-sites #Closed
@@ -185,6 +185,7 @@ bool populateDisposableConversionOrDisposeMethod(bool fromExpression, out Conver | |||
|
|||
if (iDisposableConversion.IsImplicit) | |||
{ | |||
disposeMethodOpt = originalBinder.Compilation.GetWellKnownDisposeMethod(hasAwait); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originalBinder.Compilation.GetWellKnownDisposeMethod(hasAwait); [](start = 39, length = 63)
Same comment #Closed
: compilation.GetSpecialType(SpecialType.System_IDisposable); | ||
|
||
var enumeratorImplementsDisposable = enumeratorInfoOpt.NeedsDisposal | ||
&& compilation.Conversions.ClassifyImplicitConversionFromType(enumeratorInfoOpt.GetEnumeratorMethod.ReturnType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enumeratorInfoOpt.GetEnumeratorMethod [](start = 115, length = 37)
It looks like we dropped a null check for this value. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was never supposed to be able to contain a null value. The enumeratorInfo has nullable enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was never supposed to be able to contain a null value. The enumeratorInfo has nullable enabled.
It looks to me that definition of ForEachEnumeratorInfo is in nullable disabled region.
In reply to: 533648164 [](ancestors = 533648164)
@@ -1008,7 +1008,7 @@ IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (S | |||
|
|||
<CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | |||
<Fact()> | |||
Public Sub UsingFlow_14() | |||
Public Sub UsingFlow_01() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UsingFlow_01 [](start = 19, length = 12)
Why was that renamed? Is it possible that it corresponded to C# test with the same name? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long ago there were UsingFlow_{1-13}
in C# and then {14,15}
in VB, but C# has since added significantly more tests with {14...}
so it makes no sense to have a random set of numbered VB tests. We can move these to the end of the C# tests, but the likelihood is just that they'll clash again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we'd give them descriptive names tbh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move these to the end of the C# tests, but the likelihood is just that they'll clash again.
There is no requirement for test methods to be sorted in any way. The test was not affected by the change as well. I find the rename unnecessary.
In reply to: 533652564 [](ancestors = 533652564)
@@ -1152,7 +1152,7 @@ Block[B8] - Exit | |||
|
|||
<CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | |||
<Fact()> | |||
Public Sub UsingFlow_15() | |||
Public Sub UsingFlow_02() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UsingFlow_02 [](start = 19, length = 12)
Why was this renamed? #Closed
|
||
<CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | ||
<Fact()> | ||
Public Sub UsingFlow_04() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UsingFlow_04 [](start = 19, length = 12)
Is there a similar C# test? Consider using the same name. #Closed
@@ -1236,5 +1236,171 @@ Block[B6] - Exit | |||
VerifyFlowGraphAndDiagnosticsForTest(Of MethodBlockSyntax)(source, expectedFlowGraph, expectedDiagnostics) | |||
End Sub | |||
|
|||
<CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | |||
<Fact()> | |||
Public Sub UsingFlow_03() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UsingFlow_03 [](start = 19, length = 12)
Is there a similar C# test? Consider using the same name. #Closed
Done with review pass (commit 20). Overall, it feels to me that a lot of changes could be avoided. Let's discuss offline in more details. #Closed |
return new AwaitOperation(invocation, semanticModel: null, value.Syntax, _compilation.GetSpecialType(SpecialType.System_Void), isImplicit: true); | ||
} | ||
// resource.Dispose(); | ||
resource = new InvocationOperation(disposeMethod, resource, isVirtual: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new InvocationOperation(disposeMethod, resource, isVirtual: true, [](start = 27, length = 65)
Can pattern-based expose use an extension method? In that scenario, the resource should be the first parameter instead. Do we need to handle omitted optional and param array parameters? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, pattern disposal does not support extensions methods. We should report the params and optional arguments, however
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should report the params and optional arguments, however
See how we deal with arguments for GetEnumerator and MoveNext in VisitForEachLoop
In reply to: 533774968 [](ancestors = 533774968)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlekseyTs I have a fix for this, but its a little involved, as we now need to pass the binder down through the BoundUsingStatement
like we do for Foreach. Given the mild complexity, do you want me to add it to this PR, or wait and open a separate one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the mild complexity, do you want me to add it to this PR, or wait and open a separate one?
I think a separate PR is fine.
In reply to: 534633638 [](ancestors = 534633638)
// { | ||
// ResourceType resource = expr; | ||
// try { statement; } | ||
// finally { if (resource != null) resource.Dispose(); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resource [](start = 53, length = 8)
Is this accurate for a nullable value type? We are not unwrapping the Nullable<T>
? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the comment to show what we do in lowering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the comment to show what we do in lowering.
Why would you do this? Is this comment for lowering? Is this comment for lowering of a specific compiler?
In reply to: 534592569 [](ancestors = 534592569)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood your comment to mean that we should be showing the unwrapping of the nullable in CFG, but clearly I misunderstood you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood your comment to mean that we should be showing the unwrapping of the nullable in CFG, but clearly I misunderstood you.
I asked you about behavior that you are describing in a new comment specifically for a pattern-based case. I couldn't find a test for it, hence the question. Clearly the comment looked strange to me, but you started looking at completely different scenario.
In reply to: 534620372 [](ancestors = 534620372)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, as I said, I misunderstood what you were asking.
@@ -963,7 +964,7 @@ private MethodSymbol GetGetEnumeratorMethod(NamedTypeSymbol collectionType, Diag | |||
return getEnumeratorMethod; | |||
} | |||
|
|||
private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics, TypeSymbol collectionExprType) | |||
private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics, TypeSymbol collectionExprType, bool isAsync) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
, bool isAsync [](start = 166, length = 14)
It looks like this parameter is not used. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
Done with review pass (commit 24) #Closed |
// | ||
// { | ||
// ResourceType resource = expr; | ||
// try { statement; } | ||
// finally { ((IDisposable)resource).Dispose(); } | ||
// } | ||
// | ||
// Otherwise, when ResourceType is a non-nullable value type that implements | ||
// disposal via pattern, the expansion is: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we testing this scenario? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes in UsingDeclaration_Flow_20
#Closed
// | ||
// { | ||
// ResourceType resource = expr; | ||
// try { statement; } | ||
// finally { if (resource != null) ((IDisposable)resource).Dispose(); } | ||
// } | ||
// | ||
// Otherwise, when Resource type is a nullable value type or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when Resource type is a nullable value type [](start = 30, length = 43)
Are we testing this scenario? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in UsingFlow_05
but actually, it doesn't appear to be correctly reporting what we do. It matches the comments in the Builder, but that isnt how we lower the code, as you pointed out we unwrap the nullable, which we're not representing here. https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQBMQGoA+ABATAAgAUBYAKAG8z9r9sAWfAWQApGBPAEQEsoAHAeygRgAGzgB+fFwB2vAK4wk+YP34jlASjIB6AFQAeYDJQA+Xdsqka12gAZ8zAOJwY3PoOFjmG/OMkz5GC0rG2pLUNDgfABefBgEOTgAbioIgF9U6jS9fW0jaVNzMkymTh4BIVEJfGdXco8q7xiTOIALBH4Ad3xpORERFNIM0jJYBIBjGFK3Cs84fBBaAEYAOgBJGYaxMnCabABmWgZNqDgm6JaYdq6evoGyNKA=
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, interesting. Actually we're representing in ControlFlowGraph what the spec says we should be doing:
http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs,277
Except in lowering we purposefully deviate from the spec to improve perf.
@AlekseyTs what do you think? Should the CFG represent the spirit of the code in the spec, or what we actually lower to? I guess I lean towards representing the spec? because like the async stuff we give a general representation of what it's lowered to, not the implementation specific details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks to me that UsingFlow_05 and the sharplab link is about non-pattern disposal and this change is not supposed to make any changes for these scenarios. I am fine with the current behavior of the builder for them. The question I have is about pattern-based disposal for nullable value types. This is what this comment is about. Are we testing the scenario?
In reply to: 534599322 [](ancestors = 534599322)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not currently possible to perform pattern disposal on a nullable value types: see #34701
I will add a test to show the error recovery scenario, and update the comment in CFG to correctly reflect that.
// { | ||
// ResourceType resource = expr; | ||
// try { statement; } | ||
// finally { if (resource.HasValue()) ((IDisposable)resource.GetValueOrDefault()).Dispose(); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finally { if (resource.HasValue()) ((IDisposable)resource.GetValueOrDefault()).Dispose(); } [](start = 21, length = 91)
Where do you see this in flow graph tests? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned above, we don't. The test shows something different: do we want to make the CFG represent the actual lowered value or what the spec says?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned above, we don't. The test shows something different: do we want to make the CFG represent the actual lowered value or what the spec says?
Does the comment accurately reflect CFG builder's behavior? If so, we don't want to make any changes for the comment. We also do not want to make any changes for scenarios that are not about pattern-based disposal, which is supposed to be the focus of this PR.
In reply to: 534616619 [](ancestors = 534616619)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes apologies. I was confused by your comment, I thought you were saying that the comment, and thus the tests are incorrect for those scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 26), assuming that we will deal with the arguments in a separate PR.
@dotnet/roslyn-compiler / @333fred for a second review please :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 26)
…c-foreach-binding * upstream/features/ioperation: Correctly record the dispose method to be invoked in operations (dotnet#49481)
Fixes #49267