-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Handle infinite cycles through params collection initializers #74899
Handle infinite cycles through params collection initializers #74899
Conversation
593527d
to
59f91fa
Compare
59f91fa
to
d6fc080
Compare
if (CompilationExtensions.EnableVerifyIOperation && | ||
ExecutionConditionUtil.Configuration == ExecutionConfiguration.Debug) | ||
{ | ||
// A debug assert fails but an invalid operation is correctly created otherwise. |
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 we have a work item tracking this?
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, I will create one, thanks.
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.
On a second thought, maybe it's better to just remove the assert.
using System.Collections; | ||
using System.Collections.Generic; | ||
|
||
public class Base : IEnumerable<object> |
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 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.
Let's also test a generic collection type. Perhaps this existing test could be modified to combine base and derived, and test a generic type:
public class MyCollection1<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(params MyCollection1<T> c) {}
}
public class C
{
MyCollection1<object> M() => [1];
}
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 testing:
public class MyCollection1<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(params MyCollection1<object> c) {}
}
public class C
{
MyCollection1<int> M() => [1];
}
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.
Should we test overloads, such as the following? (It looks like this should succeed.)
public class MyCollection1<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(T x) {}
public void Add(params MyCollection1<T> y) {}
}
public class C
{
MyCollection1<object> M() => [1];
}
while (current?.Flags.Includes(BinderFlags.CollectionInitializerAddMethod) == true) | ||
{ | ||
if (current is CollectionInitializerAddMethodBinder binder && | ||
binder.Syntax.FullSpan.Equals(syntax.FullSpan) && |
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 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'm not sure if the syntax nodes are guaranteed to be the same reference?
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.
But it seems it should, and I see other places in roslyn using that, so I will switch to reference equality as well, thanks.
@jjonescz Is it a good idea to also test different call-sites? It is leading to VS crash as well.
|
@@ -6585,7 +6585,6 @@ public override IOperation VisitInstanceReference(IInstanceReferenceOperation op | |||
} | |||
else | |||
{ |
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'd still like to understand how we're getting here, as well as having explicit ioperation tests for it.
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 we are getting here because of the cycle - the _currentImplicitInstance
is unset because the receiver is the same collection expression as the one being visited. I will add tests.
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'm not comfortable with getting rid of this assert entirely. Let's assert the specific case we're talking about, so we still catch any other violations.
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod); | ||
if (!elements.IsDefaultOrEmpty && HasCollectionInitializerTypeInProgress(syntax, targetType)) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_CollectionInitializerInfiniteChainOfAddCalls, syntax, targetType); |
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 are handling cycles through constructors (ERR_ParamsCollectionInfiniteChainOfConstructorCalls) elsewhere and with a different approach. I assume that at the time that handling was introduced, there was no way to get into a cycle through an Add
method. It feels like we should unify and combine both checks, i.e. do them in the same place and use similar approaches for both. Also, we should confirm that there is no way to get into a cycle when constructors and Add
methods are involved at the same time, otherwise we might be missing a guard. #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.
Also, we should confirm that there is no way to get into a cycle when constructors and
Add
methods are involved at the same time, otherwise we might be missing a guard.
I didn't think of a way that would be possible without hitting the existing guards. I can add some more tests though.
It feels like we should unify and combine both checks, i.e. do them in the same place and use similar approaches for both.
The constructor check is separate and happens before binding the conversion - I considered implementing the Add check at the same place but that would require duplicating the complicated Add binding logic. The other way around (moving the existing constructor check) could be probably done - but it seems orthogonal to this bug fix, perhaps we can just file an issue. Also I think we would need another intermediate binder so it wouldn't share code with the Add check, i.e., maybe there are no advantages to moving the constructor check.
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.
perhaps we can just file an issue
I think this should be sufficient for now.
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.
To avoid error CS8103: Combined length of user strings used by the program exceeds allowed limit.
Fixes #74734.