diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 03ab7371863e3..3e6f947065a1d 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -215,7 +215,7 @@ private static ImmutableArray GetMatchingNodes( try { - recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + processCompilationUnit(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); if (results.Length == 0) return ImmutableArray.Empty; @@ -229,7 +229,21 @@ private static ImmutableArray GetMatchingNodes( seenNames.Dispose(); } - void recurse( + void processCompilationUnit( + SyntaxNode compilationUnit, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + syntaxHelper.AddAliases(compilationUnit, ref localAliases, global: false); + + processCompilationOrNamespaceMembers(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + + void processCompilationOrNamespaceMembers( SyntaxNode node, ref Aliases localAliases, ref ValueListBuilder seenNames, @@ -238,70 +252,100 @@ void recurse( { cancellationToken.ThrowIfCancellationRequested(); - if (node is ICompilationUnitSyntax) + foreach (var child in node.ChildNodesAndTokens()) { - syntaxHelper.AddAliases(node, ref localAliases, global: false); - - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + if (child.IsNode) + { + var childNode = child.AsNode()!; + if (syntaxHelper.IsAnyNamespaceBlock(childNode)) + processNamespaceBlock(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + else + processMember(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } } - else if (syntaxHelper.IsAnyNamespaceBlock(node)) - { - var localAliasCount = localAliases.Length; - syntaxHelper.AddAliases(node, ref localAliases, global: false); + } + + void processNamespaceBlock( + SyntaxNode namespaceBlock, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + var localAliasCount = localAliases.Length; + syntaxHelper.AddAliases(namespaceBlock, ref localAliases, global: false); - // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. - localAliases.Length = localAliasCount; - } - else if (syntaxHelper.IsAttributeList(node)) + processCompilationOrNamespaceMembers( + namespaceBlock, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. + localAliases.Length = localAliasCount; + } + + void processMember( + SyntaxNode member, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + // nodes can be arbitrarily deep. Use an explicit stack over recursion to prevent a stack-overflow. + var nodeStack = new ValueListBuilder(Span.Empty); + nodeStack.Append(member); + + try { - foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) + while (nodeStack.Length > 0) { - // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. - // e.g. if there is [X] then we have to lookup with X and with XAttribute. - var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( - syntaxHelper.GetNameOfAttribute(attribute)).ValueText; - if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || - matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) - { - attributeTargets.Length = 0; - syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + var node = nodeStack.Pop(); - foreach (var target in attributeTargets.AsSpan()) + if (syntaxHelper.IsAttributeList(node)) + { + foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) { - if (predicate(target, cancellationToken)) - results.Append(target); + // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. + // e.g. if there is [X] then we have to lookup with X and with XAttribute. + var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( + syntaxHelper.GetNameOfAttribute(attribute)).ValueText; + if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) + { + attributeTargets.Length = 0; + syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + + foreach (var target in attributeTargets.AsSpan()) + { + if (predicate(target, cancellationToken)) + results.Append(target); + } + + break; + } } - return; + // attributes can't have attributes inside of them. so no need to recurse when we're done. + } + else + { + // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot + // terminate the search anywhere as attributes may be found on things like local functions, and that + // means having to dive deep into statements and expressions. + foreach (var child in node.ChildNodesAndTokens().Reverse()) + { + if (child.IsNode) + nodeStack.Append(child.AsNode()!); + } } - } - // attributes can't have attributes inside of them. so no need to recurse when we're done. - } - else - { - // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot - // terminate the search anywhere as attributes may be found on things like local functions, and that - // means having to dive deep into statements and expressions. - recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } } - - return; - - void recurseChildren( - SyntaxNode node, - ref Aliases localAliases, - ref ValueListBuilder seenNames, - ref ValueListBuilder results, - ref ValueListBuilder attributeTargets) + finally { - foreach (var child in node.ChildNodesAndTokens()) - { - if (child.IsNode) - recurse(child.AsNode()!, ref localAliases, ref seenNames, ref results, ref attributeTargets); - } + nodeStack.Dispose(); } }