From c0183eaa88c16ba10890407ab143413006d312fb Mon Sep 17 00:00:00 2001 From: Darren Bruning Date: Fri, 4 Sep 2026 06:57:31 +0000 Subject: [PATCH] Fix fatal NRE when the Win32 input pane is disposed during window close (#22138) * Fix fatal NRE when the Win32 input pane is disposed during window close WindowsInputPane.Dispose set _windowImpl to null before calling IFrameworkInputPane.Unadvise. Unadvise can deliver a final Showing/Hiding notification as it unwinds, and the shell may also call back after teardown. Either path reaches OnStateChanged -> ScreenRectToClient, which dereferences the now-null _windowImpl. Because that frame is entered from native code through a COM callback, the resulting NullReferenceException cannot unwind into managed code: the process is terminated with 0xC0000005 instead of raising a catchable exception. Two changes, both minimal: - Release _windowImpl only after Unadvise has returned, so the field stays valid for the whole of the teardown during which it is read. - Return early from OnStateChanged once _disposed is set, so a notification arriving during or after Dispose is ignored rather than acted on. Either change alone closes the reported race; together they also cover a callback that arrives after Dispose has completed. * Rewrite input pane comment to be more concise --------- Co-authored-by: Max Katz --- src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs b/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs index 79e67ecc82..a639c05844 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs @@ -52,6 +52,11 @@ internal unsafe class WindowsInputPane : InputPaneBase, IDisposable private void OnStateChanged(bool showing, UnmanagedMethods.RECT? prcInputPaneScreenLocation) { + // Unadvise can deliver last notification while it unwinds, and the shell can call back after teardown. + // Either would dereference a disposed _windowImpl, crashing the process. + if (_disposed) + return; + var oldState = (OccludedRect, State); OccludedRect = prcInputPaneScreenLocation.HasValue ? ScreenRectToClient(prcInputPaneScreenLocation.Value) @@ -76,7 +81,6 @@ internal unsafe class WindowsInputPane : InputPaneBase, IDisposable if (_disposed) return; _disposed = true; - _windowImpl = null!; if (_inputPane is not null) { if (_cookie != 0) @@ -87,6 +91,11 @@ internal unsafe class WindowsInputPane : InputPaneBase, IDisposable _inputPane.Dispose(); _inputPane = null; } + + // Released only once Unadvise has returned, so the field stays valid for the whole of the + // teardown it is read during. + _windowImpl = null!; + // Suppress finalization. GC.SuppressFinalize(this); }