-
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
Avoid stack overflow due to deep recursion on long chain of calls. #67913
Conversation
@dotnet/roslyn-compiler Please review |
@dotnet/roslyn-compiler Please review |
2 similar comments
@dotnet/roslyn-compiler Please review |
@dotnet/roslyn-compiler Please review |
node = receiver2; | ||
} | ||
|
||
TakeIncrementalSnapshot(node); // Visit does this before visiting each node |
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.
nit: consider moving this snapshot just before first push (calls.Push(node);
on line 5719), so that for each receiver we do snapshot&push together.
nit: then the logic on expressionIsRead could be moved here (just before visitAndCheckReceiver
) since the snapshot&push doesn't care about that.
(I understand that you maintained exactly the original ordering, so those are just nits to consider)
} | ||
else | ||
{ | ||
TypeWithState receiverType = visitAndCheckReceiver(node); |
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.
Why don't we need to update/track the expressionIsRead value when dealing with a single invocation? #Resolved
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.
Why don't we need to update/track the expressionIsRead value when dealing with a single invocation?
Because the calling Visit does that. Above we are setting it for nested calls.
} | ||
|
||
CheckReceiverIfField(node.ReceiverOpt); | ||
this.Visit(node.ReceiverOpt); |
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.
The ordering for these two calls (CheckReceiverIfField(node.ReceiverOpt)
and this.Visit(node.ReceiverOpt)
) seems a bit different than in the original code. I'm not sure if it matters...
Is that intentional?
In comparison, the order for single invocation case below matches the previous order:
- VisitCall
- CheckReceiverIfField
- CheckReferenceToMethodIfLocalFunction
- Visit receiver
- Visit arguments #Resolved
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 far as I understand there is no order dependency between Visit and Check methods.
invocations.Push(node); | ||
|
||
node = nested; | ||
if (receiverIsInvocation(node, out nested)) |
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 like we'll push at most two nodes into the invocations
queue. Should this be a while
loop instead? #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.
Should this be a while loop instead?
Yes, this is a typo.
src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs
Outdated
Show resolved
Hide resolved
|
||
while (node.ReceiverOpt is BoundCall receiver2) | ||
{ | ||
TakeIncrementalSnapshot(node); // Visit does this before visiting each node |
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 have a general concern about the repetition that occurs in this PR: If we make a change to a method that is referenced by a "// Method does this before visiting each node", I'm concerned that we will not catch all these other places that should also be updated.
I don't have a great solution for this, but maybe it would be useful to add cref comments from those methods back to all the places that duplicate some of the logic.
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 guess there's also the broader question of whether the gain is worth all this additional complexity in the codebase. I'm curious what the team thinks (@dotnet/roslyn-compiler for thoughts).
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 have a general concern about the repetition that occurs in this PR: If we make a change to a method that is referenced by a "// Method does this before visiting each node", I'm concerned that we will not catch all these other places that should also be updated.
We already have to face the same for visiting binary expressions
Debug.Assert(node.Expression.Kind() is SyntaxKind.SimpleMemberAccessExpression); | ||
var memberAccess = (MemberAccessExpressionSyntax)node.Expression; | ||
analyzedArguments.Clear(); | ||
VerifyUnchecked(nested, diagnostics, result); |
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 didn't follow where this came from. May be worth a comment #Resolved
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 didn't follow where this came from. May be worth a comment
Added a comment
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 Thanks (iteration 4) but would like to get team's opinion
@dotnet/roslyn-compiler For the second review |
@dotnet/roslyn-compiler For the second review |
1 similar comment
@dotnet/roslyn-compiler For the second review |
invocations.Push(node); | ||
|
||
node = nested; | ||
if (receiverIsInvocation(node, out nested)) |
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.
Shouldn't this be while
not if
? #Resolved
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.
Shouldn't this be while not if?
Yes, the typo has been fixed already
@@ -240,9 +241,49 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) | |||
return; | |||
} | |||
|
|||
base.VisitInvocationExpression(node); | |||
if (receiverIsInvocation(node, out InvocationExpressionSyntax? nested)) |
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.
Seems like this is roughly the same code on several derivations of CSharpSyntaxWalker
? Did you consider putting a helper directly there that we could access? Probably cost us a Delegate
allocation but maybe that's not too much. #Resolved
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.
? Did you consider putting a helper directly there that we could access?
There are only two of them and they are slightly different. I do not think it is worth adding an abstraction across them
node = receiver2; | ||
} | ||
|
||
VisitReceiver(node); |
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.
Why isn't this inside the do
loop below? Seems like we miss intermediate receiver
calls as written. #Resolved
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.
Why isn't this inside the
do
loop below? Seems like we miss intermediatereceiver
calls as written.
The intermediate receiver
is the previous call and we already visited all interesting parts of it. It is our intention to not call VisitReceiver for them. I'll add a comment.
* upstream/main: (2060 commits) implement code folding for anonymous objects Cleanup/perf in creating member maps. (dotnet#67997) Address feedback, clean the use of ILegacyGlobalOptionsWorkspaceService Update Publish.json Semantic snippets: Add inline statement snippets (dotnet#67819) Update VSSDK Build tools version Update doc comment Address feedback Move declaration near reference Address feedback Avoid stack overflow due to deep recursion on long chain of calls. (dotnet#67913) Check ILegacyGlobalOptionsWorkspaceService is null test scout queue add test update editor "Where" clause typo fix (dotnet#68002) Use `Keyword` helper method instead of hardcoding keyword help terms Move the SyntaxTree and SemanticModel action based analyzers to respect context.FilterSpan EnC refactoring: Align wrappers with contracts better (dotnet#67967) Include EA.RazorCompiler in source build (dotnet#67996) ...
The EndToEnd OverflowOnFluentCall test is fixed in #67913, but would be a significant change to backport.
Fixes #67912.