Skip to content

Commit 0c056f0

Browse files
authored
Merge pull request #40211 from CyrusNajmabadi/asyncAwaitIterative
Switch to iterative algorithm to prevent stack overflows.
2 parents 6feed58 + e65968f commit 0c056f0

File tree

1 file changed

+79
-71
lines changed

1 file changed

+79
-71
lines changed

src/EditorFeatures/CSharp/Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
using System.Collections.Generic;
44
using System.ComponentModel.Composition;
5-
using System.Linq;
65
using System.Threading;
6+
using Microsoft.CodeAnalysis;
77
using Microsoft.CodeAnalysis.CSharp;
88
using Microsoft.CodeAnalysis.CSharp.Extensions;
99
using Microsoft.CodeAnalysis.CSharp.Syntax;
1010
using Microsoft.CodeAnalysis.Editor.Implementation.Highlighting;
11+
using Microsoft.CodeAnalysis.PooledObjects;
1112
using Microsoft.CodeAnalysis.Text;
1213

1314
namespace Microsoft.CodeAnalysis.Editor.CSharp.KeywordHighlighting.KeywordHighlighters
1415
{
1516
[ExportHighlighter(LanguageNames.CSharp)]
1617
internal class AsyncAwaitHighlighter : AbstractKeywordHighlighter
1718
{
19+
private static readonly ObjectPool<Stack<SyntaxNode>> s_stackPool
20+
= new ObjectPool<Stack<SyntaxNode>>(() => new Stack<SyntaxNode>());
21+
1822
[ImportingConstructor]
1923
public AsyncAwaitHighlighter()
2024
{
@@ -26,93 +30,97 @@ protected override bool IsHighlightableNode(SyntaxNode node)
2630
protected override IEnumerable<TextSpan> GetHighlightsForNode(SyntaxNode node, CancellationToken cancellationToken)
2731
{
2832
var spans = new List<TextSpan>();
29-
HighlightRelatedKeywords(node, spans);
33+
34+
foreach (var current in WalkChildren(node))
35+
{
36+
cancellationToken.ThrowIfCancellationRequested();
37+
HighlightRelatedKeywords(current, spans);
38+
}
39+
3040
return spans;
3141
}
3242

33-
private static void HighlightRelatedKeywords(SyntaxNode node, List<TextSpan> spans)
43+
private IEnumerable<SyntaxNode> WalkChildren(SyntaxNode node)
3444
{
35-
// Highlight async keyword
36-
switch (node)
45+
using var pooledObject = s_stackPool.GetPooledObject();
46+
47+
var stack = pooledObject.Object;
48+
stack.Push(node);
49+
50+
while (stack.Count > 0)
3751
{
38-
case MethodDeclarationSyntax methodDeclaration:
39-
{
40-
var asyncModifier = methodDeclaration.Modifiers.FirstOrDefault(m => m.Kind() == SyntaxKind.AsyncKeyword);
41-
if (asyncModifier.Kind() != SyntaxKind.None)
42-
{
43-
spans.Add(asyncModifier.Span);
44-
}
45-
break;
46-
}
47-
case LocalFunctionStatementSyntax localFunction:
48-
{
49-
var asyncModifier = localFunction.Modifiers.FirstOrDefault(m => m.Kind() == SyntaxKind.AsyncKeyword);
50-
if (asyncModifier.Kind() != SyntaxKind.None)
51-
{
52-
spans.Add(asyncModifier.Span);
53-
}
54-
break;
55-
}
56-
case AnonymousFunctionExpressionSyntax anonymousFunction:
57-
if (anonymousFunction.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword)
58-
{
59-
spans.Add(anonymousFunction.AsyncKeyword.Span);
60-
}
61-
break;
62-
63-
case AwaitExpressionSyntax awaitExpression:
64-
// Note if there is already a highlight for the previous token, merge it
65-
// with this span. That way, we highlight nested awaits with a single span.
66-
var handled = false;
67-
var awaitToken = awaitExpression.AwaitKeyword;
68-
var previousToken = awaitToken.GetPreviousToken();
69-
if (!previousToken.Span.IsEmpty)
70-
{
71-
var index = spans.FindIndex(s => s.Contains(previousToken.Span));
72-
if (index >= 0)
73-
{
74-
var span = spans[index];
75-
spans[index] = TextSpan.FromBounds(span.Start, awaitToken.Span.End);
76-
handled = true;
77-
}
78-
}
52+
var current = stack.Pop();
53+
yield return current;
7954

80-
if (!handled)
55+
// 'Reverse' isn't really necessary, but it means we walk the nodes in document
56+
// order, which is nicer when debugging and understanding the results produced.
57+
foreach (var child in current.ChildNodesAndTokens().Reverse())
58+
{
59+
if (child.IsNode)
8160
{
82-
spans.Add(awaitToken.Span);
83-
}
84-
break;
61+
var childNode = child.AsNode();
8562

86-
case UsingStatementSyntax usingStatement:
87-
if (usingStatement.AwaitKeyword.Kind() == SyntaxKind.AwaitKeyword)
88-
{
89-
spans.Add(usingStatement.AwaitKeyword.Span);
63+
// Only process children if they're not the start of another construct
64+
// that async/await would be related to.
65+
if (!childNode.IsReturnableConstruct())
66+
{
67+
stack.Push(childNode);
68+
}
9069
}
91-
break;
70+
}
71+
}
72+
}
9273

93-
case LocalDeclarationStatementSyntax localDeclaration:
94-
if (localDeclaration.AwaitKeyword.Kind() == SyntaxKind.AwaitKeyword && localDeclaration.UsingKeyword.Kind() == SyntaxKind.UsingKeyword)
95-
{
96-
spans.Add(localDeclaration.AwaitKeyword.Span);
97-
}
98-
break;
74+
private static bool HighlightRelatedKeywords(SyntaxNode node, List<TextSpan> spans)
75+
=> node switch
76+
{
77+
MethodDeclarationSyntax methodDeclaration => TryAddAsyncModifier(methodDeclaration.Modifiers, spans),
78+
LocalFunctionStatementSyntax localFunction => TryAddAsyncModifier(localFunction.Modifiers, spans),
79+
AnonymousFunctionExpressionSyntax anonymousFunction => TryAddAsyncOrAwaitKeyword(anonymousFunction.AsyncKeyword, spans),
80+
UsingStatementSyntax usingStatement => TryAddAsyncOrAwaitKeyword(usingStatement.AwaitKeyword, spans),
81+
LocalDeclarationStatementSyntax localDeclaration =>
82+
localDeclaration.UsingKeyword.Kind() == SyntaxKind.UsingKeyword && TryAddAsyncOrAwaitKeyword(localDeclaration.AwaitKeyword, spans),
83+
CommonForEachStatementSyntax forEachStatement => TryAddAsyncOrAwaitKeyword(forEachStatement.AwaitKeyword, spans),
84+
AwaitExpressionSyntax awaitExpression => TryAddAsyncOrAwaitKeyword(awaitExpression.AwaitKeyword, spans),
85+
_ => false,
86+
};
9987

100-
case CommonForEachStatementSyntax forEachStatement:
101-
if (forEachStatement.AwaitKeyword.Kind() == SyntaxKind.AwaitKeyword)
102-
{
103-
spans.Add(forEachStatement.AwaitKeyword.Span);
104-
}
105-
break;
88+
private static bool TryAddAsyncModifier(SyntaxTokenList modifiers, List<TextSpan> spans)
89+
{
90+
foreach (var mod in modifiers)
91+
{
92+
if (TryAddAsyncOrAwaitKeyword(mod, spans))
93+
{
94+
return true;
95+
}
10696
}
10797

108-
foreach (var child in node.ChildNodes())
98+
return false;
99+
}
100+
101+
private static bool TryAddAsyncOrAwaitKeyword(SyntaxToken mod, List<TextSpan> spans)
102+
{
103+
if (mod.IsKind(SyntaxKind.AsyncKeyword, SyntaxKind.AwaitKeyword))
109104
{
110-
// Only recurse if we have anything to do
111-
if (!child.IsReturnableConstruct())
105+
// Note if there is already a highlight for the previous token, merge it with this
106+
// span. That way, we highlight nested awaits with a single span.
107+
108+
if (spans.Count > 0)
112109
{
113-
HighlightRelatedKeywords(child, spans);
110+
var previousToken = mod.GetPreviousToken();
111+
var lastSpan = spans[spans.Count - 1];
112+
if (lastSpan == previousToken.Span)
113+
{
114+
spans[spans.Count - 1] = TextSpan.FromBounds(lastSpan.Start, mod.Span.End);
115+
return true;
116+
}
114117
}
118+
119+
spans.Add(mod.Span);
120+
return true;
115121
}
122+
123+
return false;
116124
}
117125
}
118126
}

0 commit comments

Comments
 (0)