Repository: microsoft/terminal (language: cpp)

Issue:
Terminal crashes Visual Studio while performing a resize
### Windows Terminal version

1.12.2203.31007

### Windows build number

10.0.19044.2130

### Other Software

Microsoft Visual Studio 17.3.32929.385

### Steps to reproduce

We've been getting watson reports from Visual Studio where a crash is being detected after a user resizes the integrated terminal and a FailFast is triggered. Repro steps are unknown, but the callstack shows user_resize() was triggered:

```
00 ucrtbase!abort(void)+0x4e [minkernel\crts\ucrt\src\appcrt\startup\abort.cpp @ 77] 
01 ucrtbase!terminate(void)+0x29 [minkernel\crts\ucrt\src\appcrt\misc\terminate.cpp @ 58] 
02 PublicTerminalCore!gsl::details::terminate(void)+0xa [C:\a\_work\1\s\dep\gsl\include\gsl\gsl_assert @ 116] 
03 PublicTerminalCore!til::basic_rle::resize_trailing_extent(unsigned short new_size = 0xaaaa) [C:\a\_work\1\s\src\inc\til\rle.h @ 514] 
04 PublicTerminalCore!ATTR_ROW::Resize(void)+0xb [C:\a\_work\1\s\src\buffer\out\AttrRow.cpp @ 37] 
05 PublicTerminalCore!TextBuffer::Reflow(class TextBuffer * oldBuffer = 0xaaaaaaaa`aaaaaaaa, class TextBuffer * newBuffer = 0xaaaaaaaa`aaaaaaaa, class std::optional<Microsoft::Console::Types::Viewport> * lastCharacterViewport = 0xaaaaaaaa`aaaaaaaa, class std::optional<std::reference_wrapper<TextBuffer::PositionInformation> > * positionInfo = 0x00000090`d9cfd840 {...})+0x92b [C:\a\_work\1\s\src\buffer\out\textBuffer.cpp @ 2432] 
06 PublicTerminalCore!Microsoft::Terminal::Core::Terminal::UserResize(struct _COORD viewportSize = struct _COORD)+0x294 [C:\a\_work\1\s\src\cascadia\TerminalCore\Terminal.cpp @ 285] 
07 PublicTerminalCore!HwndTerminal::Refresh(struct tagSIZE windowSize = <Value unavailable error>, struct _COORD * dimensions = 0x00000090`d9cfdae8 {0,0})+0x1db [C:\a\_work\1\s\src\cascadia\PublicTerminalCore\HwndTerminal.cpp @ 371] 
08 Microsoft_VisualStudio_Workspace_Implementation!DomainBoundILStubClass.IL_STUB_PInvoke(<Win32 error 0n318>)+0x95
09 Microsoft_Terminal_Wpf!Microsoft.Terminal.Wpf.TerminalContainer.TerminalContainer_MessageHook(<Win32 error 0n318>)+0x33b [C:\a\_work\1\s\src\cascadia\WpfTerminalControl\TerminalContainer.cs @ 377] 
0a PresentationFramework_ni!System.Windows.Interop.HwndHost.SubclassWndProc(<Win32 error 0n318>)+0xf007ae [f:\dd\wpf\src\Framework\System\Windows\Interop\HwndHost.cs @ 1380]
```

### Expected Behavior

_No response_

### Actual Behavior

Reports show that a FailFast was triggered and caused VS to close.
Watson link: https://watsonportal.microsoft.com/Failure?FailureSearchText=e4dbf733-a94b-b35e-6306-c92ca0f441ef&MaxRows=100
Internal bug link: [Bug 1677696](https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1677696): [Watson] bex64: FAIL_FAST_FATAL_APP_EXIT_c0000409_PublicTerminalCore.dll!gsl::details::terminate


Discussion / hints:
This crash can occur when the terminal's text buffer has a width of 0 columns:
https://github.com/microsoft/terminal/blob/704458ee0e2bb41008a83ee022941599a563c72c/src/inc/til/rle.h#L514

This is possible because all code including and between `PublicTerminalCore` and `TextBuffer` is missing guard clauses against resizing the buffer to a width/height of less than 1, despite this being an invalid state. Making this crash reproducible would definitely be helpful, to confirm this theory.
@lhecker - we haven't been able to reproduce it, but getting tons of Watson reports.  Here's a user who hit this, and I've just asked them to provide repro info: https://developercommunity.visualstudio.com/t/Visual-Studio-Crashing-While-running-a-A/10200774
> ```07 PublicTerminalCore!HwndTerminal::Refresh(struct tagSIZE windowSize = <Value unavailable error>, struct _COORD * dimensions = 0x00000090`d9cfdae8 {0,0})+0x1db [C:\a\_work\1\s\src\cascadia\PublicTerminalCore\HwndTerminal.cpp @ 371] ```

> ```dimensions = 0x00000090`d9cfdae8 {0,0}```

That would seemingly support the assumption that the window is being resized to 0x0. We can try and guard against that, as a hypothetical fix, though, this should probably never be happening in the first place, right?
We protect against resizing to {0,0} on our end but there might be a race condition when we initialize the terminal, and it gets the control size before the tool window is fully initialized which could lead up to the control being {0,0} when the renderer attempts to initialize.

But protecting on your end should fix the issue for us since once the control is initialized we would trigger another resize

Fix patch:
diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp
index 6103fa58cbd..2bbc2e20721 100644
--- a/src/buffer/out/textBuffer.cpp
+++ b/src/buffer/out/textBuffer.cpp
@@ -98,7 +98,7 @@ using PointTree = interval_tree::IntervalTree<til::point, size_t>;
 // Return Value:
 // - constructed object
 // Note: may throw exception
-TextBuffer::TextBuffer(const til::size screenBufferSize,
+TextBuffer::TextBuffer(til::size screenBufferSize,
                        const TextAttribute defaultAttributes,
                        const UINT cursorSize,
                        const bool isActiveBuffer,
@@ -108,6 +108,10 @@ TextBuffer::TextBuffer(const til::size screenBufferSize,
     _cursor{ cursorSize, *this },
     _isActiveBuffer{ isActiveBuffer }
 {
+    // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
+    screenBufferSize.width = std::max(screenBufferSize.width, 1);
+    screenBufferSize.height = std::max(screenBufferSize.height, 1);
+
     BufferAllocator allocator{ screenBufferSize };
 
     _storage.reserve(allocator.height());
@@ -963,8 +967,12 @@ void TextBuffer::Reset()
 // - newSize - new size of screen.
 // Return Value:
 // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
-[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept
+[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept
 {
+    // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
+    newSize.width = std::max(newSize.width, 1);
+    newSize.height = std::max(newSize.height, 1);
+
     try
     {
         BufferAllocator allocator{ newSize };
diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
index f5ee0c4191f..2adecccfbaf 100644
--- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
+++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
@@ -344,15 +344,20 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen
     const auto viewInPixels = Viewport::FromDimensions(windowSize);
     const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
 
+    // Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support.
+    auto size = vp.Dimensions();
+    size.width = std::max(size.width, 1);
+    size.height = std::max(size.height, 1);
+
     // If this function succeeds with S_FALSE, then the terminal didn't
     //      actually change size. No need to notify the connection of this
     //      no-op.
     // TODO: MSFT:20642295 Resizing the buffer will corrupt it
     // I believe we'll need support for CSI 2J, and additionally I think
     //      we're resetting the viewport to the top
-    RETURN_IF_FAILED(_terminal->UserResize({ vp.Width(), vp.Height() }));
-    dimensions->width = vp.Width();
-    dimensions->height = vp.Height();
+    RETURN_IF_FAILED(_terminal->UserResize(size));
+    dimensions->width = size.width;
+    dimensions->height = size.height;
 
     return S_OK;
 }
@@ -452,7 +457,7 @@ HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordTyp
 {
     const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
 
-    const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
+    const auto viewInPixels = Viewport::FromDimensions({ width, height });
     const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
 
     dimensions->width = viewInCharacters.Width();


Test patch: