mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-12 00:07:24 -06:00
Support valid out-of-bounds access in utextAccess (#17361)
`utextAccess` apparently doesn't actually need to clamp the `chunkOffset` to be in range of the current chunk. Also, I missed to implement the part of the spec that says to leave the iterator on the first/last chunk of the `UText` in case of an out-of-bounds index. This PR fixes the issue by simply not returning early, doing a more liberal clamp of the offset, and then checking whether it was in range. As an aside, this also fixes a one-off bug when hovering URLs that end on the very last cell of the viewport (or are cut off). Closes #17343 ## Validation Steps Performed * Write an URL that wraps across the last 2 lines in the buffer * Scroll 1 line up * No assert ✅ * Hovering the URL shows the full, still visible parts of the URL ✅
This commit is contained in:
parent
ce0f8d6db2
commit
261a3fec7a
@ -186,12 +186,9 @@ catch (...)
|
|||||||
static UBool U_CALLCONV utextAccess(UText* ut, int64_t nativeIndex, UBool forward) noexcept
|
static UBool U_CALLCONV utextAccess(UText* ut, int64_t nativeIndex, UBool forward) noexcept
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (nativeIndex < 0)
|
|
||||||
{
|
|
||||||
nativeIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto neededIndex = nativeIndex;
|
auto neededIndex = nativeIndex;
|
||||||
|
// This will make it simpler for us to search the row that contains the nativeIndex,
|
||||||
|
// because we'll now only need to check for `start<=index<limit` and nothing else.
|
||||||
if (!forward)
|
if (!forward)
|
||||||
{
|
{
|
||||||
neededIndex--;
|
neededIndex--;
|
||||||
@ -199,10 +196,12 @@ try
|
|||||||
|
|
||||||
const auto& textBuffer = *static_cast<const TextBuffer*>(ut->context);
|
const auto& textBuffer = *static_cast<const TextBuffer*>(ut->context);
|
||||||
const auto range = accessRowRange(ut);
|
const auto range = accessRowRange(ut);
|
||||||
auto start = ut->chunkNativeStart;
|
const auto startOld = ut->chunkNativeStart;
|
||||||
auto limit = ut->chunkNativeLimit;
|
const auto limitOld = ut->chunkNativeLimit;
|
||||||
|
auto start = startOld;
|
||||||
|
auto limit = limitOld;
|
||||||
|
|
||||||
if (neededIndex < start || neededIndex >= limit)
|
if (neededIndex < startOld || neededIndex >= limitOld)
|
||||||
{
|
{
|
||||||
auto y = accessCurrentRow(ut);
|
auto y = accessCurrentRow(ut);
|
||||||
std::wstring_view text;
|
std::wstring_view text;
|
||||||
@ -215,8 +214,7 @@ try
|
|||||||
--y;
|
--y;
|
||||||
if (y < range.begin)
|
if (y < range.begin)
|
||||||
{
|
{
|
||||||
assert(false);
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& row = textBuffer.GetRowByOffset(y);
|
const auto& row = textBuffer.GetRowByOffset(y);
|
||||||
@ -235,8 +233,7 @@ try
|
|||||||
++y;
|
++y;
|
||||||
if (y >= range.end)
|
if (y >= range.end)
|
||||||
{
|
{
|
||||||
assert(false);
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& row = textBuffer.GetRowByOffset(y);
|
const auto& row = textBuffer.GetRowByOffset(y);
|
||||||
@ -249,38 +246,50 @@ try
|
|||||||
} while (neededIndex >= limit);
|
} while (neededIndex >= limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasWrapForced)
|
assert(start >= 0);
|
||||||
|
// If we have already calculated the total length we can also assert that the limit is in range.
|
||||||
|
assert(ut->p == nullptr || static_cast<size_t>(limit) <= accessLength(ut));
|
||||||
|
|
||||||
|
// Even if we went out-of-bounds, we still need to update the chunkContents to contain the first/last chunk.
|
||||||
|
if (limit != limitOld)
|
||||||
{
|
{
|
||||||
const auto newSize = text.size() + 1;
|
if (!wasWrapForced)
|
||||||
const auto buffer = RefcountBuffer::EnsureCapacityForOverwrite(accessBuffer(ut), newSize);
|
{
|
||||||
|
const auto newSize = text.size() + 1;
|
||||||
|
const auto buffer = RefcountBuffer::EnsureCapacityForOverwrite(accessBuffer(ut), newSize);
|
||||||
|
|
||||||
memcpy(&buffer->data[0], text.data(), text.size() * sizeof(wchar_t));
|
memcpy(&buffer->data[0], text.data(), text.size() * sizeof(wchar_t));
|
||||||
til::at(buffer->data, text.size()) = L'\n';
|
til::at(buffer->data, text.size()) = L'\n';
|
||||||
|
|
||||||
text = { &buffer->data[0], newSize };
|
text = { &buffer->data[0], newSize };
|
||||||
accessBuffer(ut) = buffer;
|
accessBuffer(ut) = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
accessCurrentRow(ut) = y;
|
accessCurrentRow(ut) = y;
|
||||||
ut->chunkNativeStart = start;
|
ut->chunkNativeStart = start;
|
||||||
ut->chunkNativeLimit = limit;
|
ut->chunkNativeLimit = limit;
|
||||||
ut->chunkLength = gsl::narrow_cast<int32_t>(text.size());
|
ut->chunkLength = gsl::narrow_cast<int32_t>(text.size());
|
||||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||||
ut->chunkContents = reinterpret_cast<const char16_t*>(text.data());
|
ut->chunkContents = reinterpret_cast<const char16_t*>(text.data());
|
||||||
ut->nativeIndexingLimit = ut->chunkLength;
|
ut->nativeIndexingLimit = ut->chunkLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto offset = gsl::narrow_cast<int32_t>(nativeIndex - start);
|
// The ICU documentation is a little bit misleading. It states:
|
||||||
|
// > @param forward [...] If true, start<=index<limit. If false, [...] start<index<=limit.
|
||||||
|
// but that's just for finding the target chunk. The chunkOffset is not actually constrained to that!
|
||||||
|
// std::clamp will perform a<=b<=c, which is what we want.
|
||||||
|
const auto clampedIndex = std::clamp(nativeIndex, start, limit);
|
||||||
|
auto offset = gsl::narrow_cast<int32_t>(clampedIndex - start);
|
||||||
// Don't leave the offset on a trailing surrogate pair. See U16_SET_CP_START.
|
// Don't leave the offset on a trailing surrogate pair. See U16_SET_CP_START.
|
||||||
// This assumes that the TextBuffer contains valid UTF-16 which may theoretically not be the case.
|
// This assumes that the TextBuffer contains valid UTF-16 which may theoretically not be the case.
|
||||||
if (offset > 0 && offset < ut->chunkLength && U16_IS_TRAIL(til::at(ut->chunkContents, offset)))
|
if (offset > 0 && offset < ut->chunkLength && U16_IS_TRAIL(til::at(ut->chunkContents, offset)))
|
||||||
{
|
{
|
||||||
offset--;
|
offset--;
|
||||||
}
|
}
|
||||||
|
|
||||||
ut->chunkOffset = offset;
|
ut->chunkOffset = offset;
|
||||||
return true;
|
|
||||||
|
return neededIndex >= start && neededIndex < limit;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -497,6 +497,10 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
|||||||
result = GetHyperlinkIntervalFromViewportPosition(viewportPos);
|
result = GetHyperlinkIntervalFromViewportPosition(viewportPos);
|
||||||
if (result.has_value())
|
if (result.has_value())
|
||||||
{
|
{
|
||||||
|
// GetPlainText and _ConvertToBufferCell work with inclusive coordinates, but interval's
|
||||||
|
// stop point is (horizontally) exclusive, so let's just update it.
|
||||||
|
result->stop.x--;
|
||||||
|
|
||||||
result->start = _ConvertToBufferCell(result->start);
|
result->start = _ConvertToBufferCell(result->start);
|
||||||
result->stop = _ConvertToBufferCell(result->stop);
|
result->stop = _ConvertToBufferCell(result->stop);
|
||||||
}
|
}
|
||||||
@ -522,10 +526,6 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
|||||||
// Case 2 - Step 2: get the auto-detected hyperlink
|
// Case 2 - Step 2: get the auto-detected hyperlink
|
||||||
if (result.has_value() && result->value == _hyperlinkPatternId)
|
if (result.has_value() && result->value == _hyperlinkPatternId)
|
||||||
{
|
{
|
||||||
// GetPlainText works with inclusive coordinates, but interval's stop
|
|
||||||
// point is (horizontally) exclusive, so let's just update it.
|
|
||||||
result->stop.x--;
|
|
||||||
|
|
||||||
return _activeBuffer().GetPlainText(result->start, result->stop);
|
return _activeBuffer().GetPlainText(result->start, result->stop);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user