Files
terminal/src/cascadia/TerminalCore/terminalrenderdata.cpp
Leonard Hecker 4370da9549 Add missing lock guards on Terminal access (#15894)
`Terminal` is used concurrently by at least 4 threads. The table
below lists the class members and the threads that access them
to the best of my knowledge. Where:
* UI: UI Thread
* BG: Background worker threads (`winrt::resume_background`)
* RD: Render thread
* VT: VT connection thread

|                                    | UI | BG | RD | VT |
|------------------------------------|----|----|----|----|
| `_pfnWriteInput`                   | x  | x  |    | x  |
| `_pfnWarningBell`                  |    |    |    | x  |
| `_pfnTitleChanged`                 |    |    |    | x  |
| `_pfnCopyToClipboard`              |    |    |    | x  |
| `_pfnScrollPositionChanged`        | x  | x  |    | x  |
| `_pfnCursorPositionChanged`        |    |    |    | x  |
| `_pfnTaskbarProgressChanged`       |    |    |    | x  |
| `_pfnShowWindowChanged`            |    |    |    | x  |
| `_pfnPlayMidiNote`                 |    |    |    | x  |
| `_pfnCompletionsChanged`           |    |    |    | x  |
| `_renderSettings`                  | x  |    | x  | x  |
| `_stateMachine`                    | x  |    |    | x  |
| `_terminalInput`                   | x  |    |    | x  |
| `_title`                           | x  |    | x  | x  |
| `_startingTitle`                   | x  |    | x  |    |
| `_startingTabColor`                | x  |    |    |    |
| `_defaultCursorShape`              | x  |    |    | x  |
| `_systemMode`                      |    | x  | x  | x  |
| `_snapOnInput`                     | x  | x  |    |    |
| `_altGrAliasing`                   | x  |    |    |    |
| `_suppressApplicationTitle`        | x  |    |    | x  |
| `_trimBlockSelection`              | x  |    |    |    |
| `_autoMarkPrompts`                 | x  |    |    |    |
| `_taskbarState`                    | x  |    |    | x  |
| `_taskbarProgress`                 | x  |    |    | x  |
| `_workingDirectory`                | x  |    |    | x  |
| `_fontInfo`                        | x  |    | x  |    |
| `_selection`                       | x  | x  | x  | x  |
| `_blockSelection`                  | x  | x  | x  |    |
| `_wordDelimiters`                  | x  | x  |    |    |
| `_multiClickSelectionMode`         | x  | x  | x  |    |
| `_selectionMode`                   | x  | x  | x  |    |
| `_selectionIsTargetingUrl`         | x  | x  | x  |    |
| `_selectionEndpoint`               | x  | x  | x  |    |
| `_anchorInactiveSelectionEndpoint` | x  | x  | x  |    |
| `_mainBuffer`                      | x  | x  | x  | x  |
| `_altBuffer`                       | x  | x  | x  | x  |
| `_mutableViewport`                 | x  |    | x  | x  |
| `_scrollbackLines`                 | x  |    |    |    |
| `_detectURLs`                      | x  |    |    |    |
| `_altBufferSize`                   | x  | x  | x  | x  |
| `_deferredResize`                  | x  |    |    | x  |
| `_scrollOffset`                    | x  | x  | x  | x  |
| `_patternIntervalTree`             | x  | x  | x  | x  |
| `_lastKeyEventCodes`               | x  |    |    |    |
| `_currentPromptState`              | x  |    |    | x  |

Only 7 members are specific to one thread and don't require locking.
All other members require some for of locking to be safe for use.

To address the issue this changeset adds `LockForReading/LockForWriting`
calls everywhere `_terminal` is accessed in `ControlCore/HwndTerminal`.
Additionally, to ensure these issues don't pop up anymore, it adds to
all `Terminal` functions a debug assertion that the lock is being held.

Finally, because this changeset started off rather modest, it contains
changes that I initially made without being aware about the extent of
the issue. It simplifies the access around `_patternIntervalTree` by
making `_InvalidatePatternTree()` directly use that member.
Furthermore, it simplifies `_terminal->SetCursorOn(!IsCursorOn())` to
`BlinkCursor()`, allowing the code to be shared with `HwndTerminal`.

Ideally `Terminal` should not be that much of a class so that we don't
need such coarse locking. Splitting out selection and rendering state
should allow deduplicating code with conhost and use finer locking.

Closes #9617

## Validation Steps Performed
I tried to use as many Windows Terminal features as I could and fixed
every occurrence of `_assertLocked()` failures.
2023-09-19 11:59:39 -05:00

228 lines
6.0 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Terminal.hpp"
#include <DefaultSettings.h>
using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Render;
Viewport Terminal::GetViewport() noexcept
{
return _GetVisibleViewport();
}
til::point Terminal::GetTextBufferEndPosition() const noexcept
{
// We use the end line of mutableViewport as the end
// of the text buffer, it always moves with the written
// text
return { _GetMutableViewport().Width() - 1, ViewEndIndex() };
}
const TextBuffer& Terminal::GetTextBuffer() const noexcept
{
return _activeBuffer();
}
const FontInfo& Terminal::GetFontInfo() const noexcept
{
_assertLocked();
return _fontInfo;
}
void Terminal::SetFontInfo(const FontInfo& fontInfo)
{
_assertLocked();
_fontInfo = fontInfo;
}
til::point Terminal::GetCursorPosition() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.GetPosition();
}
bool Terminal::IsCursorVisible() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.IsVisible() && !cursor.IsPopupShown();
}
bool Terminal::IsCursorOn() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.IsOn();
}
ULONG Terminal::GetCursorPixelWidth() const noexcept
{
return 1;
}
ULONG Terminal::GetCursorHeight() const noexcept
{
return _activeBuffer().GetCursor().GetSize();
}
CursorType Terminal::GetCursorStyle() const noexcept
{
return _activeBuffer().GetCursor().GetType();
}
bool Terminal::IsCursorDoubleWidth() const
{
const auto& buffer = _activeBuffer();
const auto position = buffer.GetCursor().GetPosition();
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
}
const std::vector<RenderOverlay> Terminal::GetOverlays() const noexcept
{
return {};
}
const bool Terminal::IsGridLineDrawingAllowed() noexcept
{
return true;
}
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const
{
return _activeBuffer().GetHyperlinkUriFromId(id);
}
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const
{
return _activeBuffer().GetCustomIdFromId(id);
}
// Method Description:
// - Gets the regex pattern ids of a location
// Arguments:
// - The location
// Return value:
// - The pattern IDs of the location
const std::vector<size_t> Terminal::GetPatternId(const til::point location) const
{
_assertLocked();
// Look through our interval tree for this location
const auto intervals = _patternIntervalTree.findOverlapping({ location.x + 1, location.y }, location);
if (intervals.size() == 0)
{
return {};
}
else
{
std::vector<size_t> result{};
for (const auto& interval : intervals)
{
result.emplace_back(interval.value);
}
return result;
}
return {};
}
std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept
{
return GetRenderSettings().GetAttributeColors(attr);
}
std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
try
{
std::vector<Viewport> result;
for (const auto& lineRect : _GetSelectionRects())
{
result.emplace_back(Viewport::FromInclusive(lineRect));
}
return result;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return {};
}
void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd)
{
#pragma warning(push)
#pragma warning(disable : 26496) // cpp core checks wants these const, but they're decremented below.
auto realCoordStart = coordStart;
auto realCoordEnd = coordEnd;
#pragma warning(pop)
auto notifyScrollChange = false;
if (coordStart.y < _VisibleStartIndex())
{
// recalculate the scrollOffset
_scrollOffset = ViewStartIndex() - coordStart.y;
notifyScrollChange = true;
}
else if (coordEnd.y > _VisibleEndIndex())
{
// recalculate the scrollOffset, note that if the found text is
// beneath the current visible viewport, it may be within the
// current mutableViewport and the scrollOffset will be smaller
// than 0
_scrollOffset = std::max(0, ViewStartIndex() - coordStart.y);
notifyScrollChange = true;
}
if (notifyScrollChange)
{
_activeBuffer().TriggerScroll();
_NotifyScrollEvent();
}
realCoordStart.y -= _VisibleStartIndex();
realCoordEnd.y -= _VisibleStartIndex();
SetSelectionAnchor(realCoordStart);
SetSelectionEnd(realCoordEnd, SelectionExpansion::Char);
}
const std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
_assertLocked();
if (_title.has_value())
{
return *_title;
}
return _startingTitle;
}
// Method Description:
// - Lock the terminal for reading the contents of the buffer. Ensures that the
// contents of the terminal won't be changed in the middle of a paint
// operation.
// Callers should make sure to also call Terminal::UnlockConsole once
// they're done with any querying they need to do.
void Terminal::LockConsole() noexcept
{
_readWriteLock.lock();
}
// Method Description:
// - Unlocks the terminal after a call to Terminal::LockConsole.
void Terminal::UnlockConsole() noexcept
{
_readWriteLock.unlock();
}
const bool Terminal::IsUiaDataInitialized() const noexcept
{
// GH#11135: Windows Terminal needs to create and return an automation peer
// when a screen reader requests it. However, the terminal might not be fully
// initialized yet. So we use this to check if any crucial components of
// UiaData are not yet initialized.
_assertLocked();
return !!_mainBuffer;
}