-
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
Improve detection of receiver cloning in ref safety analysis #67736
Conversation
After learning more about behavior of In reply to: 1505177626 Refers to: src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs:2091 in 42498e4. [](commit_id = 42498e4, deletion_comment = False) |
any particular reason this is in draft? |
yes, it's broken, need to refactor slightly |
6c9adcd
to
53ca90e
Compare
Does this PR address #67435? In reply to: 1513588074 |
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 23)
@dotnet/roslyn-compiler for a second review, thanks |
@dotnet/roslyn-compiler for a second review, thanks |
@dotnet/roslyn-compiler for a second review please |
@dotnet/roslyn-compiler for the second review, thanks |
@jcouv PTAL |
Is this PR still planned for 17.8? |
@RikkiGibson Yes, this bug fix is still planned for 17.8. I have merged main here, so it's up to date. |
return BoundCall.Synthesized( | ||
syntax, | ||
expression, | ||
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, |
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.
Is this method ever called with a value besides Unknown here? Could we eliminate a lot of the churn by removing the parameter and possibly adding an overload to synthesize a call with non-Unknown value if needed?
Not blocking this PR
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.
Is this method ever called with a value besides Unknown here?
It is, for example:
initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(collectionExpr, nullableValueGetter), |
@@ -25672,6 +25672,225 @@ class B2 : A<string> | |||
Diagnostic(ErrorCode.ERR_UnscopedRefAttributeUnsupportedMemberTarget, "UnscopedRef").WithLocation(17, 6)); | |||
} | |||
|
|||
[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/67697")] | |||
public void UnscopedRefAttribute_NestedAccess_MethodOrProperty(bool firstMethod, bool secondMethod) |
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.
consider naming parameters something like firstIsMethod
, secondIsMethod
@@ -1982,6 +1988,7 @@ bool isRefEscape | |||
GetInvocationArgumentsForEscape( | |||
symbol, | |||
receiver: null, // receiver handled explicitly below | |||
receiverIsSubjectToCloning: ThreeState.Unknown, |
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.
ah, passing Unknown is fine here only because receiver is null.
@@ -363,7 +363,7 @@ private bool IsCountableAndIndexable(SyntaxNode node, TypeSymbol inputType, out | |||
hasErrors |= !TryGetSpecialTypeMember(Compilation, SpecialMember.System_Array__Length, node, diagnostics, out PropertySymbol lengthProperty); | |||
if (lengthProperty is not null) | |||
{ | |||
lengthAccess = new BoundPropertyAccess(node, receiverPlaceholder, lengthProperty, LookupResultKind.Viable, lengthProperty.Type) { WasCompilerGenerated = true }; | |||
lengthAccess = new BoundPropertyAccess(node, receiverPlaceholder, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, lengthProperty, LookupResultKind.Viable, lengthProperty.Type) { WasCompilerGenerated = 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.
I thought we should pass False
when receiver is a placeholder.
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.
That makes sense to me, will fix.
@@ -634,6 +634,7 @@ private void VisitArgumentsAndGetArgumentPlaceholders(BoundExpression? receiverO | |||
|
|||
protected override void VisitArguments(BoundCall node) | |||
{ | |||
Debug.Assert(node.InitialBindingReceiverIsSubjectToCloning != ThreeState.Unknown); |
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.
There's def a part of me that feels like, man, instead of this flag I wish we could just wrap the receiver expression into the BoundCapturedReceiverPlaceholder
node at initial binding time. After all, what we are doing in ref safety analysis is simply to introduce this node late into the tree and then analyze that.
I know that change has the tendency for unpleasant ripple effects through other areas of the compiler. Flow analysis does already have mechanisms for "seeing through" placeholders and so on, but they can be unpleasant to use, and I'm sure more than that would be impacted. Also, I think we've solved the problem with the current change and I don't want to belabor that unnecessarily.
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.
Interesting idea, I haven't tried that, but I can imagine it could be an even more invasive change.
@@ -9234,7 +9234,7 @@ private void VisitThisOrBaseReference(BoundExpression node) | |||
// when binding initializers, we treat assignments to auto-properties or field-like events as direct assignments to the underlying field. | |||
// in order to track member state based on these initializers, we need to see the assignment in terms of the associated member | |||
case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol autoProperty } } fieldAccess: | |||
left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); | |||
left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); | |||
break; | |||
case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: EventSymbol @event } } fieldAccess: | |||
left = new BoundEventAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, @event, isUsableAsField: true, LookupResultKind.Viable, @event.Type, fieldAccess.HasErrors); |
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.
Do events also need this new field? Or no, because we don't allow implicit cloning for the receiver of an event anyway?
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 think not, because events need to be of a delegate type, and implicit cloning happens only for struct
s.
@@ -9234,7 +9234,7 @@ private void VisitThisOrBaseReference(BoundExpression node) | |||
// when binding initializers, we treat assignments to auto-properties or field-like events as direct assignments to the underlying field. | |||
// in order to track member state based on these initializers, we need to see the assignment in terms of the associated member | |||
case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol autoProperty } } fieldAccess: | |||
left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); | |||
left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); |
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.
as you can see, rewriting the bound tree while we are visiting it is kind of a tradition :P
Fixes #67697.
Fixes #67626.
The regression was likely introduced in #64890.