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

  1. Always dispose PushedState in strict LIFO order using using/using-declarations.
  2. Avoid storing PushedState in fields; keep push/pop lexically nested.
  3. 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

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


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/9696a6f046fa6e72. Report an issue: GitHub.