Skip to content

Fix GetNext/PreviousDirective's handling of duplicate trivia#62760

Merged
jasonmalinowski merged 1 commit intodotnet:release/dev17.3from
jasonmalinowski:fix-get-next-previous-directive
Jul 21, 2022
Merged

Fix GetNext/PreviousDirective's handling of duplicate trivia#62760
jasonmalinowski merged 1 commit intodotnet:release/dev17.3from
jasonmalinowski:fix-get-next-previous-directive

Conversation

@jasonmalinowski
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski commented Jul 18, 2022

GetNext/PreviousDirective work by walking the list of trivia and when they see the SyntaxTrivia that represents 'this', they set a flag and then continue to traverse and return the next directive found. There was a bug though in handling of duplicate trivia: if somebody were to create a node that has the same trivia twice, that may share the underlying green node. The boolean flag of "return next directive" was set if the green node matched, but no checking was done of the span or index to verify it was the correct instance of the green node. This meant that somebody calling GetNextDirective on the second directive would get the second directive back again, since internally the loop would see the first directive, go "oh, that's me!" and return the second one.

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1568850, or at least the fact it causes crashes. An IDE bug is causing us to accidentally introduce duplicate trivia, but in the process it's causing compiler functions that depend on GetNext/PreviousDirective to end up in a stack overflow, because they assume progress will be made by these functions.

@jasonmalinowski jasonmalinowski requested a review from a team as a code owner July 18, 2022 23:26
@ghost ghost added the Area-Compilers label Jul 18, 2022
@jasonmalinowski jasonmalinowski force-pushed the fix-get-next-previous-directive branch from 80a86ce to 51b6b69 Compare July 18, 2022 23:28
");
var c = tree.GetCompilationUnitRoot().Members[0];

// Duplicate the leading trivia on the class
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cute :)

@AlekseyTs
Copy link
Copy Markdown
Contributor

Done with review pass (commit 1)

GetNext/PreviousDirective work by walking the list of trivia and
when they see the SyntaxTrivia that represents 'this', they set a flag
and then continue to traverse and return the next directive found.
There was a bug though in handling of duplicate trivia: if somebody
were to create a node that has the same trivia twice, that may share
the underlying green node. The boolean flag of "return next directive"
was set if the green node matched, but no checking was done of the span
or index to verify it was the correct instance of the green node. This
meant that somebody calling GetNextDirective on the second directive
would get the second directive back again, since internally the loop
would see the first directive, go "oh, that's me!" and return the
second one.

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1568850,
or at least the fact it causes crashes. An IDE bug is causing us to
accidentally introduce duplicate trivia, but in the process it's causing
compiler functions that depend on GetNext/PreviousDirective to end up
in a stack overflow, because they assume progress will be made by these
functions.
@jasonmalinowski jasonmalinowski changed the base branch from main to release/dev17.3 July 19, 2022 18:50
@jasonmalinowski jasonmalinowski force-pushed the fix-get-next-previous-directive branch from 51b6b69 to b551bad Compare July 19, 2022 18:50
@jasonmalinowski
Copy link
Copy Markdown
Member Author

@AlekseyTs Feedback addressed. And generally to all, I rebased this against 17.3 since we'll be targeting that there; my apologies if that screws up any review tooling.

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 1)

if (tr.IsDirective)
{
if (tr.IsDirective)
var d = (DirectiveTriviaSyntax)tr.GetStructure()!;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(DirectiveTriviaSyntax)tr.GetStructure()

We're now calling SyntaxToken.GetStructure() for all directives before the target directive. Is that expensive? Same question for GetPreviousDirective().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlekseyTs suggested setting the "next" parameter by also doing an object equality check against the structure; at this point both branches were going to read this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second branch only needs the structure when we already think we have the previous token.

Is it worth using the original if cases and checking (object)tr.GetStructure() == this in the second branch, to avoid unnecessary calls to GetStructure()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cston I'd imagine that'd only be happening if you have multiple directives in the same block of trivia, which would be a very tiny amount of extra work. I'd aim for simpler code here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that'd only be happening if you have multiple directives in the same block of trivia

Thanks, I'd missed that.

@jasonmalinowski jasonmalinowski self-assigned this Jul 19, 2022
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.

4 participants