Skip to content

Conversation

@sharwell
Copy link
Contributor

@sharwell sharwell commented May 11, 2018

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/604150

/cc @minestarks

Customer scenario

A customer attempts to reformat an HTML document, and Visual Studio crashes.

Bugs this fixes

https://devdiv.visualstudio.com/DevDiv/_workitems/edit/604150

Workarounds, if any

None.

Risk

Low. This change relaxes the preconditions of an internal fallback method to produce expected results when the input is unordered but also unambiguous.

Performance impact

Performance is not changed in normal usage scenarios.

Is this a regression from a previous update?

Yes, introduced by #25558.

Root cause analysis

See #26339 for the primary RCA.

This change corrects an additional edge case for ITextBuffer instances created before VisualStudioWorkspace attaches event handlers to ITextBufferFactoryService and IProjectionBufferFactoryService. This case is only believed to occur in IDE initialization scenarios where the workspace load is triggered by opening a document.

How was the bug found?

Watson.

Test documentation updated?

N/A

@sharwell sharwell added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 11, 2018
@sharwell sharwell added this to the 15.7 milestone May 11, 2018
@sharwell sharwell requested a review from a team as a code owner May 11, 2018 16:18
Copy link
Member

Choose a reason for hiding this comment

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

why the second clause?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ TestChangedTextWithReplaceBeforeInsertSamePosition

Copy link
Member

Choose a reason for hiding this comment

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

it's not clear ot me why this check is correct. why is it sufficient to only check the last change range?

Copy link
Member

Choose a reason for hiding this comment

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

I think the answer is because this is in a loop that goes through all of the changes.


In reply to: 187675569 [](ancestors = 187675569)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ changeRanges is always sorted (a condition that was true both before and after this change).

Copy link
Member

Choose a reason for hiding this comment

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

WithChanges [](start = 31, length = 11)

Do we have tests that demonstrate the new behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we have tests that demonstrate the new behavior?

No. Local install was broken due to NRT preview install so I had to fix that, and now am working on tests. Hence the PR for Personal Review Only label. 😄

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry. The label is not in a visible location for me :( That's my bad for not checking. I'm a fan of "WIP" in teh title to help indicate this more obviously.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ Tests are added

Copy link
Member

Choose a reason for hiding this comment

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

WIP in the title is a bad idea at the moment because of an Outlook threading bug that triggers when you change the title (e.g. to remove WIP later). I'm working with them to get that fixed.

Copy link
Member

Choose a reason for hiding this comment

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

ArgumentException [](start = 30, length = 17)

Are there no tests for the exceptional behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ Tests are included for both normal and exceptional behavior

Copy link
Member

Choose a reason for hiding this comment

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

Can you please point me to tests for the exceptional behavior? Are those existing tests?


In reply to: 187682548 [](ancestors = 187682548)

Copy link
Member

Choose a reason for hiding this comment

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

The only test I could find is one existing test TestChangedTextWithMultipleOverlappingChanges


In reply to: 188384892 [](ancestors = 188384892,187682548)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter Yes, that is the test for the condition which is still a failure

@sharwell sharwell force-pushed the unordered-changes branch from 0e9e405 to a744edd Compare May 11, 2018 17:28
@sharwell sharwell added Area-Compilers and removed PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. labels May 11, 2018
@minestarks
Copy link

@sharwell thanks for the fix!
Does the order of application of the TextChanges matter? I thought the start/end indices of a change was based on the state of the text after the previous TextChanges were applied.

@sharwell
Copy link
Contributor Author

Does the order of application of the TextChanges matter? I thought the start/end indices of a change was based on the state of the text after the previous TextChanges were applied.

All the changes are non-overlapping changes on the original text. The order only matters for sequences of insertions at the same position (empty old span, non-empty new text).

Copy link
Member

Choose a reason for hiding this comment

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

Other code in this method assumes that NewText can be null. Is the check to ensure the span isn't empty a sufficient guard against this case here?

@sharwell sharwell force-pushed the unordered-changes branch from a744edd to 0ffe920 Compare May 15, 2018 17:07
Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

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

:shipit:

Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

I worry that this is an interesting kind of breaking change: since this type is something we expect people hosting Roslyn to derive from, we've now changed their contract on their derived type which says they now must support unordered changes. If VS for Mac, for example, isn't expecting this, we might still be broken.

I would have guessed we would just fix this by changing whatever code is passing unordered spans and sorting before calling WithChanges. Did that have some other complexity I'm not imagining here?

changes = from c in changes
where !c.Span.IsEmpty || c.NewText?.Length > 0
orderby c.Span.Start, c.Span.End
select c;
Copy link
Member

Choose a reason for hiding this comment

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

Note that this isn't being realized, and the recursive call is going to do .Any() and then an enumeration over the changes again. Will that result in multiple sorts?

Copy link
Member

Choose a reason for hiding this comment

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

That's a good point. Sort requires a full iteration to resolve even Any. That will mean that we sort this at least twice: changes.Any and foreach (var change in changes)


In reply to: 188428290 [](ancestors = 188428290)

Copy link
Contributor Author

@sharwell sharwell May 15, 2018

Choose a reason for hiding this comment

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

That will mean that we sort this at least twice: changes.Any and foreach (var change in changes)

Currently, yes. However, this is really a fallback handler for a condition we try to avoid in practice (a bad outcome of a race condition during package initialization). I could call ToList() if you prefer.

Copy link
Member

Choose a reason for hiding this comment

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

I don't consider is a "fallback handler due to package initialization" -- the compiler layer knows not what package initialization is. 😄 If I were you, I'd consider adding a .ToList(), but potentially at the start of the method. Even not in the fallback case, we enumerate this twice which is potentially bad, but potentially not.

Copy link
Contributor Author

@sharwell sharwell May 15, 2018

Choose a reason for hiding this comment

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

If I were you, I'd consider adding a .ToList(), but potentially at the start of the method. Even not in the fallback case, we enumerate this twice which is potentially bad, but potentially not.

It seems risky to add to the beginning of the method. If most of the inputs are collections with low-overhead enumeration, then calling ToList every time could cause a performance regression. Currently this code change only touches a path that previously threw an exception. Any objection to just calling ToList on the result of the LINQ query?

Copy link
Member

Choose a reason for hiding this comment

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

That's fine too.

{
changes = from c in changes
where !c.Span.IsEmpty || c.NewText?.Length > 0
orderby c.Span.Start, c.Span.End
Copy link
Member

Choose a reason for hiding this comment

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

Why the secondary sort?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handles an incorrectly-ordered zero-length insertion. See #26798 (comment) which points to TestChangedTextWithReplaceBeforeInsertSamePosition.

Copy link
Member

Choose a reason for hiding this comment

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

If @CyrusNajmabadi and I both had to ask, please add a comment explaining why. I admit within a minute or two of thinking it's not obvious to me why not having this breaks that other test (I'd expect this to fail either way), so a comment would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ Fixed in af5afd7

@jasonmalinowski
Copy link
Member

And to clarify my earlier comment: this is a change that makes the world a better place for consumers of the compiler type because yes, this really wasn't a requirement we had to have. So I'm for the change, but we might want to check with VS for Mac and VS Code first to see if they'll need any updates made.

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

:shipit:

@sharwell
Copy link
Contributor Author

since this type is something we expect people hosting Roslyn to derive from, we've now changed their contract on their derived type which says they now must support unordered changes.

It's actually the other way around. One of the derived types already supported unordered changes, and consumers were depending on that behavior. However, we can no longer guarantee that derived implementation will be used, and in some edge cases this base implementation was called instead.

I would have guessed we would just fix this by changing whatever code is passing unordered spans and sorting before calling WithChanges. Did that have some other complexity I'm not imagining here?

Yes, this was one of the alternatives discussed. The offending input is in the TypeScript code, and we could still decide to resolve this issue by requesting the input be ordered.

@agocke agocke merged commit 20b7e22 into dotnet:dev15.7.x May 16, 2018
@sharwell sharwell deleted the unordered-changes branch May 16, 2018 02:29
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.

8 participants