AvaloniaUI/Avalonia · error · InvalidOperationException
Wrong Push/Pop state order
Error message
Wrong Push/Pop state order
What it means
PushedState.Dispose throws InvalidOperationException("Wrong Push/Pop state order") when the state's _level does not equal the context's _currentLevel. PushedState objects form a stack; disposing them in non-LIFO order (popping a state while a later-pushed state is still active) corrupts the level counter.
Source
Thrown at src/Avalonia.Base/Media/ImmediateDrawingContext.cs:243
{
if (context._states is null)
throw new ObjectDisposedException(nameof(ImmediateDrawingContext));
_context = context;
_type = type;
_matrix = matrix;
_level = context._currentLevel += 1;
context._states.Push(this);
}
public void Dispose()
{
if (_type == PushedStateType.None)
return;
if (_context._states is null || _context._transformContainers is null)
throw new ObjectDisposedException(nameof(DrawingContext));
if (_context._currentLevel != _level)
throw new InvalidOperationException("Wrong Push/Pop state order");
_context._currentLevel--;
_context._states.Pop();
if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix;
else if (_type == PushedStateType.Clip)
_context.PlatformImpl.PopClip();
else if (_type == PushedStateType.Opacity)
_context.PlatformImpl.PopOpacity();
else if (_type == PushedStateType.GeometryClip)
_context.PlatformImpl.PopGeometryClip();
else if (_type == PushedStateType.OpacityMask)
_context.PlatformImpl.PopOpacityMask();
else if (_type == PushedStateType.MatrixContainer)
{
var cont = _context._transformContainers.Pop();
_context._currentContainerTransform = cont.ContainerTransform;
_context.CurrentTransform = cont.LocalTransform;
}View on GitHub (pinned to 11c5427268)
Solutions
- Always dispose PushedState in strict LIFO order using using/using-declarations.
- Avoid storing PushedState in fields; keep push/pop lexically nested.
- Ensure exception handlers do not reorder disposals.
Example fix
// before - out of order
var a = ctx.PushTransform(m);
var b = ctx.PushClip(r);
a.Dispose(); // wrong: b is on top
b.Dispose();
// after
using (ctx.PushTransform(m))
using (ctx.PushClip(r)) { /* draw */ } Defensive patterns
Strategy: validation
Validate before calling
// Always dispose PushedState in LIFO order.
using (ctx.PushTransform(m))
using (ctx.PushClip(r)) { /* draw */ } // compiler guarantees correct ordering Prevention
- Use using/using-declarations so the compiler emits LIFO disposal.
- Never store and manually dispose PushedState in non-nested order.
- Ensure exception paths do not reorder disposal.
When it happens
Trigger: Disposing PushedState disposables out of nesting order — e.g. disposing an outer transform before an inner clip, or partially disposing one branch of a forked draw. Also triggered when a using-declaration is reordered relative to siblings.
Common situations: Storing and manually disposing PushedState structs instead of using using; conditional dispose paths that skip a pop; exception paths that dispose states in the wrong sequence.
Related errors
- Transform container stack is non-empty
- ImmediateDrawingContext
- DrawingContext
- Unable to obtain ANativeWindow
- TopLevel.InternalView was not expected to be null.
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/9696a6f046fa6e72.
Report an issue: GitHub.