{"record":{"id":"81389ee9cf6466fe","repo":"AvaloniaUI/Avalonia","slug":"not-all-states-are-disposed","errorCode":null,"errorMessage":"Not all states are disposed","messagePattern":"Not all states are disposed","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Avalonia.Base/Media/PlatformDrawingContext.cs","lineNumber":132,"sourceCode":"    protected override void PopTextOptionsCore() => _impl.PopTextOptions();\n\n    /// <inheritdoc />\n    protected override void PopEffectCore()\n    {\n        if (_impl is IDrawingContextImplWithEffects effectImpl)\n        {\n            effectImpl.PopEffect();\n        }\n    }\n\n    protected override void DisposeCore()\n    {\n        if (_ownsImpl)\n            _impl.Dispose();\n        if (_transforms != null)\n        {\n            if (_transforms.Count != 0)\n                throw new InvalidOperationException(\"Not all states are disposed\");\n            TransformStackPool.ReturnAndSetNull(ref _transforms);\n        }\n    }\n\n    public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)\n    {\n        if (_impl is IDrawingContextWithAcrylicLikeSupport idc)\n            idc.DrawRectangle(material, rect);\n        else\n            DrawRectangle(new ImmutableSolidColorBrush(material.FallbackColor), null, rect);\n    }\n}\n","sourceCodeStart":114,"sourceCodeEnd":145,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Avalonia.Base/Media/PlatformDrawingContext.cs#L114-L145","documentation":"Thrown by DisposeCore when the _transforms stack still has entries at dispose time. Each PushTransformCore pushes the current transform; each PopTransformCore pops it. A non-empty stack at disposal means more pushes than pops occurred — i.e. the caller left one or more transform states on the stack, corrupting the saved-state invariant.","triggerScenarios":"A PushTransform without a matching PopTransform before the drawing context is disposed (using block ends). Each unbalanced push leaves one entry, so N unbalanced pushes leave N entries.","commonSituations":"Custom render code that pushes transforms inside a loop/branch but only pops on the happy path; an exception between Push and Pop that skips the Pop; forgetting to pop after a conditional push.","solutions":["Balance every PushTransform with a PopTransform; use try/finally so the Pop always executes.","Audit render overrides for any early return/continue/break that occurs after a push but before its pop.","If you wrap push/pop in a helper or using-expression, ensure it disposes on all exit paths."],"exampleFix":"// before\nforeach (var m in matrices)\n{\n    context.PushTransform(m);\n    Draw(context);\n    // forgot to pop\n}\n\n// after\nforeach (var m in matrices)\n{\n    context.PushTransform(m);\n    try { Draw(context); }\n    finally { context.PopTransform(); }\n}","handlingStrategy":"validation","validationCode":"// Track push/pop depth and assert it returns to 0 before dispose.\nDebug.Assert(transformDepth == 0, \"Unbalanced transforms before dispose\");","typeGuard":null,"tryCatchPattern":"try { /* render block with balanced push/pop */ }\nfinally { /* pop all remaining transforms */ }","preventionTips":["Ensure every PushTransform has a matching PopTransform on all code paths.","Use try/finally (or a using-expression helper) around render blocks.","Add a debug assertion that transform depth is zero at scope exit."],"tags":["avalonia","drawing-context","transform","unbalanced","dispose","lifecycle"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}