Skip to content

Commit 01adaff

Browse files
committed
renderer: Force a viewport resync in EnablePainting (#20269)
EnablePainting() assigns _viewport = _pData->GetViewport() directly, but unlike _CheckViewportAndScroll() - the only other writer of _viewport - it neither calls UpdateViewport() nor sets _forceUpdateViewport. UpdateViewport() is the sole writer of _api.s->viewportCellCount, which sizes the engine's row buffer (AtlasEngine's _p.rows). If the viewport grows while painting is disabled (e.g. the render-failure recovery path, ResumeRendering, or init/relayout), EnablePainting() latches _viewport to the new size. The next _CheckViewportAndScroll() then early-returns because srOldViewport == srNewViewport (both already the new size) and _forceUpdateViewport is false, so UpdateViewport() is skipped and the engine viewport - and _p.rows - stay at the old, smaller size. _updateCursorInfo() derives coordCursor.y from the larger _viewport and still reports the cursor as in-viewport, so PaintCursor() indexes _p.rows past its end -> out-of-bounds read and an access violation (GH#20269). A full-heap crash dump confirms the mechanism: at the fault, coordCursor.y = 67 while _p.rows holds 65 fully-valid entries (every in-bounds slot points into _p.unorderedRows), and _p.rows[67] reads past the array into adjacent heap. Set _forceUpdateViewport = true in EnablePainting() so the next _CheckViewportAndScroll() runs UpdateViewport() and resizes the backing buffer to match _viewport before the cursor is painted - keeping the resize on the same render-thread, lock-held path as the cursor move rather than masking the symptom at the read site.
1 parent dc4ce1c commit 01adaff

1 file changed

Lines changed: 11 additions & 0 deletions

File tree

src/renderer/base/renderer.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ void Renderer::EnablePainting()
5858
// but once EnablePainting is called it should be safe to retrieve.
5959
_viewport = _pData->GetViewport();
6060

61+
// _viewport feeds the cursor coordinate (_updateCursorInfo), while the engine's
62+
// backing buffer (e.g. AtlasEngine's _p.rows) is sized from viewportCellCount,
63+
// which only UpdateViewport() writes. If the viewport grew while painting was
64+
// disabled, assigning _viewport here without forcing a resync would let the next
65+
// _CheckViewportAndScroll() early-return (srOldViewport == srNewViewport) and skip
66+
// UpdateViewport(), leaving the engine viewport - and thus the row buffer - behind
67+
// _viewport. The cursor could then be reported as in-viewport at a row past the end
68+
// of the buffer (GH#20269). Force the resync so the backing buffer is resized to
69+
// match before the next cursor move is painted.
70+
_forceUpdateViewport = true;
71+
6172
_enable.SetEvent();
6273

6374
if (const auto guard = _threadMutex.lock_exclusive(); !_thread)

0 commit comments

Comments
 (0)