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

Fix a11y crash in alt buffer apps #13250

Merged
2 commits merged into from
Jun 10, 2022
Merged

Conversation

carlos-zamora
Copy link
Member

@carlos-zamora carlos-zamora commented Jun 8, 2022

Summary of the Pull Request

This fixes the crashes caused by using a screen reader when in an app that uses the alt buffer via two changes:

  1. Fix Terminal::ViewEndIndex()
    • UiaTextRangeBase receives a coordinate that is outside of the bounds of the text buffer via the following chain of functions... _getDocumentEnd() --> GetLastNonSpaceCharacter() --> _getOptimizedBufferSize() --> GetTextBufferEndPoisition() --> ViewEndIndex()
    • Since support for the alt buffer was added recently, ViewEndIndex() was recently changed, so that explains why this issue came up recently. We were accidentally setting the view end index to height instead of height-1. Thanks @j4james for finding this!
    • The UIA code would get the "exclusive end" of the alt buffer. Since it was using ViewEndIndex() to calculate that, it was one more than it should be. The UIA code has explicit allowance for "one past the end of the viewport" in its IsInBounds() check. Since the ViewEndIndex() is way beyond that, it's not allowed, hitting the fail fast.
  2. Replace FAIL_FAST_IF with assert
    • These fail fast calls have caused so many issues with our UIA code. Those checks still provide value, but they shouldn't take the whole app down. This change replaces the Viewport and UiaTextRangeBase fail fasts with asserts to still perform those checks, but not take down the entire app in release builds.

Closes #13183

Validation Steps Performed

While using Narrator...

  • opened nano in bash
  • generated text and scrolled in nano
  • generated text and scrolled in PowerShell

@ghost ghost added Area-Accessibility Issues related to accessibility Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-0 Bugs that we consider release-blocking/recall-class (P0) Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree. Severity-Crash Crashes are real bad news. labels Jun 8, 2022
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Sure. When I was originally testing this, I found another crash after "moving the cursor down some rows" (presumably, in vim).

Did we ever figure out why that caused the out of bounds? or are we just leaning on the FAIL_FAST -> assert to silently ignore?

@carlos-zamora
Copy link
Member Author

Sure. When I was originally testing this, I found another crash after "moving the cursor down some rows" (presumably, in vim).
Just tested it in vim as well, and I can't repro the crash. Not sure how you got it to crash in the past :/.

Did we ever figure out why that caused the out of bounds? or are we just leaning on the FAIL_FAST -> assert to silently ignore?

oh geez, I meant to include that in the PR body. Adding it now. But here's what I'm adding for quick reference:

The UIA code would get the "exclusive end" of the alt buffer. Since it was using ViewEndIndex() to calculate that, it was one more than it should be. The UIA code has explicit allowance for "one past the end of the viewport" in its IsInBounds() check. Since the ViewEndIndex() is way beyond that, it's not allowed, hitting the fail fast.

Also relevant, this is really just a backport-able solution for 1.14. The better solution is #13244, but since Leonard's sweeping til::point PR isn't in 1.14, we need to make it not crash. #13244 straight up removes uses of CompareInBounds which is responsible for most of our UIA-related crashes. Leveraging til::point's < and > gives us the same benefit but none of the crashing!

@carlos-zamora carlos-zamora added the Needs-Second It's a PR that needs another sign-off label Jun 8, 2022
@ghost ghost requested review from PankajBhojwani, DHowett and lhecker June 8, 2022 21:08
FAIL_FAST_IF(!(newViewport.Top >= topRow));
FAIL_FAST_IF(!(newViewport.Bottom <= bottomRow));
FAIL_FAST_IF(!(_getViewportHeight(oldViewport) == _getViewportHeight(newViewport)));
assert(newViewport.Top >= topRow);
Copy link
Member

Choose a reason for hiding this comment

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

@lhecker, can you suggest something better than just assert? If there is anything better. Otherwise, let's just do this.

Copy link
Member Author

Choose a reason for hiding this comment

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

The thread I dug up from like 3 years ago had Michael suggesting telemetry asserts. Unfortunately, that requires a decent amount of work to set up.

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 alternative to telemetry asserts that come to my mind is WI_ASSERT.

@@ -992,7 +992,7 @@ int Terminal::ViewStartIndex() const noexcept

int Terminal::ViewEndIndex() const noexcept
{
return _inAltBuffer() ? _altBufferSize.height : _mutableViewport.BottomInclusive();
return _inAltBuffer() ? _altBufferSize.height - 1 : _mutableViewport.BottomInclusive();
Copy link
Member

Choose a reason for hiding this comment

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

@zadjii-msft are there any alt buffer conformance scenarios you'd like us to check before committing to 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.

This I checked myself before I left last week, and seemed like it was just a typo tbh. Like, there's no reason to not have the -1

@@ -432,7 +433,7 @@ bool Viewport::WalkInBounds(til::point& pos, const WalkDir dir, bool allowEndExc
bool Viewport::WalkInBoundsCircular(til::point& pos, const WalkDir dir, bool allowEndExclusive) const noexcept
{
// Assert that the position given fits inside this viewport.
FAIL_FAST_IF(!IsInBounds(pos, allowEndExclusive));
assert(IsInBounds(pos, allowEndExclusive));
Copy link
Member

Choose a reason for hiding this comment

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

What happens down the line when UIA tries to use this new coordinate that is provably out of bounds? What happens when we Walk it in bounds circular?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm honestly stumped here. We shouldn't get to that point, but history shows that's never the case haha.

What if I clamp it when we get something out of bounds? Specifically, something like this...

til::point MyClamp(til::point pos, Viewport size)
{
   if (pos < size.Origin())
      return size.Origin(); // clamp to origin
   else if (pos > size.ExclusiveEnd())
      return size.ExclusiveEnd(); // clamp to exclusive end
   else if (pos.x > size.RightInclusive())
      return {size.Left(), pos.y+1}; // too far right --> assume we wrapped to next line
   else if (pos.x < size.Left())
      return {size.RightInclusive(), pos.y-1};// too far left --> assume we wrapped to prev line
}

This would ensure we're always in a valid space. Even if we kept the FailFast calls, we would never hit them because now we'd be clamped into a valid position.

The main sources of these issues I can think of are switching to/from the alt buffer, because we can literally switch to a buffer with a different size entirely. Clamping would still return an incorrect answer but (1) we 100% wouldn't crash and (2) it would be a stepping stone until #5406 get done.

Thoughts?

Copy link
Member

@DHowett DHowett Jun 9, 2022

Choose a reason for hiding this comment

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

Hmmmmmmm.

I guess the problem with MyClamp is that if we knew when to use it, we wouldn't have had this problem in the first place. Right?

Copy link
Member

@lhecker lhecker Jun 9, 2022

Choose a reason for hiding this comment

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

I personally would say that we should use fail-fasts or exceptions for security or safety relevant checks (e.g. boundary checks for array access). For anything else I'd use debug assertions + clamping/truncation/etc. in release on a "lazy" best-effort basis.

The suggested code above would be overkill already for that purpose IMO. It's not just stopping incorrect behavior, but also tries to correct the position in a smart manner. But how do we know that this improves the behavior? It shouldn't pass incorrect positions in the first place after all!

As such I'd just use std::clamp 4 times for numeric parameters like that.
(I don't think this is necessary for this PR however. Or is it?)

@@ -365,11 +365,12 @@ bool Viewport::DecrementInBoundsCircular(til::point& pos) const noexcept
// - This is so you can do s_CompareCoords(first, second) <= 0 for "first is left or the same as second".
// (the < looks like a left arrow :D)
// - The magnitude of the result is the distance between the two coordinates when typing characters into the buffer (left to right, top to bottom)
#pragma warning(suppress : 4100)
Copy link
Member

Choose a reason for hiding this comment

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

???

Copy link
Member Author

Choose a reason for hiding this comment

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

It's dumb. allowEndExclusive is now a param that's not referenced anywhere... but like, it is, it's just hidden behind an assert.

@zadjii-msft zadjii-msft added the AutoMerge Marked for automatic merge by the bot when requirements are met label Jun 10, 2022
@ghost
Copy link

ghost commented Jun 10, 2022

Hello @zadjii-msft!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 94e1697 into main Jun 10, 2022
@ghost ghost deleted the dev/cazamor/a11y/bugfix-altbuffercrash branch June 10, 2022 18:09
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

FWIW we automerged this without addressing exactly what happens when an out of bounds coordinate escapes through the assert since it's not built in Release

@ghost ghost removed the Needs-Second It's a PR that needs another sign-off label Jun 10, 2022
DHowett pushed a commit that referenced this pull request Jun 30, 2022
This fixes the crashes caused by using a screen reader when in an app that uses the alt buffer via two changes:
1. Fix `Terminal::ViewEndIndex()`
   - `UiaTextRangeBase` receives a coordinate that is outside of the bounds of the text buffer via the following chain of functions... `_getDocumentEnd()` --> `GetLastNonSpaceCharacter()` --> `_getOptimizedBufferSize()` --> `GetTextBufferEndPoisition()` --> `ViewEndIndex()`
   - Since support for the alt buffer was added recently, `ViewEndIndex()` was recently changed, so that explains why this issue came up recently. We were accidentally setting the view end index to `height` instead of `height-1`. Thanks @j4james for finding this!
   - The UIA code would get the "exclusive end" of the alt buffer. Since it was using `ViewEndIndex()` to calculate that, it was one more than it should be. The UIA code has explicit allowance for "one past the end of the viewport" in its `IsInBounds()` check. Since the `ViewEndIndex()` is way beyond that, it's not allowed, hitting the fail fast.
2. Replace `FAIL_FAST_IF` with `assert`
   - These fail fast calls have caused so many issues with our UIA code. Those checks still provide value, but they shouldn't take the whole app down. This change replaces the `Viewport` and `UiaTextRangeBase` fail fasts with asserts to still perform those checks, but not take down the entire app in release builds.

Closes #13183

While using Narrator...
- opened nano in bash
- generated text and scrolled in nano
- generated text and scrolled in PowerShell

(cherry picked from commit 94e1697)
Service-Card-Id: 82972865
Service-Version: 1.14
@ghost
Copy link

ghost commented Jul 6, 2022

🎉Windows Terminal v1.14.186 has been released which incorporates this pull request.:tada:

Handy links:

@ghost
Copy link

ghost commented Jul 6, 2022

🎉Windows Terminal Preview v1.15.186 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Accessibility Issues related to accessibility AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-0 Bugs that we consider release-blocking/recall-class (P0) Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree. Severity-Crash Crashes are real bad news.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Terminal crashes when starting nano in bash when screen reader is enabled
4 participants