Skip to content
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

Merged
merged 6 commits into from
Apr 29, 2023

Conversation

AlekseyTs
Copy link
Contributor

Fixes #67912.

@AlekseyTs AlekseyTs requested a review from a team as a code owner April 21, 2023 11:49
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Apr 21, 2023
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

2 similar comments
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@jcouv jcouv self-assigned this Apr 25, 2023
@jcouv jcouv added this to the 17.7 milestone Apr 25, 2023
@jcouv jcouv removed the untriaged Issues and PRs which have not yet been triaged by a lead label Apr 25, 2023
node = receiver2;
}

TakeIncrementalSnapshot(node); // Visit does this before visiting each node
Copy link
Member

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);
Copy link
Member

@jcouv jcouv Apr 25, 2023

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

Copy link
Contributor Author

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);
Copy link
Member

@jcouv jcouv Apr 25, 2023

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:

  1. VisitCall
  2. CheckReceiverIfField
  3. CheckReferenceToMethodIfLocalFunction
  4. Visit receiver
  5. Visit arguments #Resolved

Copy link
Contributor Author

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))
Copy link
Member

@jcouv jcouv Apr 26, 2023

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

Copy link
Contributor Author

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.


while (node.ReceiverOpt is BoundCall receiver2)
{
TakeIncrementalSnapshot(node); // Visit does this before visiting each node
Copy link
Member

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.

Copy link
Member

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).

Copy link
Contributor Author

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);
Copy link
Member

@jcouv jcouv Apr 26, 2023

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

Copy link
Contributor Author

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

Copy link
Member

@jcouv jcouv left a 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

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For the second review

@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For the second review

1 similar comment
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler For the second review

invocations.Push(node);

node = nested;
if (receiverIsInvocation(node, out nested))
Copy link
Member

@jaredpar jaredpar Apr 21, 2023

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

Copy link
Contributor Author

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))
Copy link
Member

@jaredpar jaredpar Apr 21, 2023

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

Copy link
Contributor Author

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);
Copy link
Member

@jaredpar jaredpar Apr 21, 2023

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

Copy link
Contributor Author

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.

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.

@AlekseyTs AlekseyTs enabled auto-merge (squash) April 28, 2023 18:17
@AlekseyTs AlekseyTs merged commit 53ac77e into dotnet:main Apr 29, 2023
@ghost ghost modified the milestones: 17.7, Next Apr 29, 2023
333fred added a commit to 333fred/roslyn that referenced this pull request May 2, 2023
* 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)
  ...
@Cosifne Cosifne modified the milestones: Next, 17.7 P2 May 31, 2023
JoeRobich added a commit that referenced this pull request Jun 11, 2024
The EndToEnd OverflowOnFluentCall test is fixed in #67913, but would be a significant change to backport.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to compile project with large amount of grpc proto messages and services
4 participants