Skip to content

fix: update on_message_ to nullptr after window destroy so that dart gets destroy message - #185807

Merged
auto-submit[bot] merged 10 commits into
flutter:masterfrom
rkishan516:null-pointer-window-checks
Aug 4, 2026
Merged

auto-submit[bot] merged 10 commits into
flutter:masterfrom
rkishan516:null-pointer-window-checks

Conversation

@rkishan516

@rkishan516 rkishan516 commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

This PR moves setting on_message_ callback to nullptr after window destroy

@rkishan516
rkishan516 requested a review from a team as a code owner April 30, 2026 05:48
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Apr 30, 2026
@flutter-dashboard

Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-windows Building on or for Windows specifically a: desktop Running on desktop team-windows Owned by the Windows platform team labels Apr 30, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces null checks for the HostWindow pointer in the tooltip and popup position update functions to prevent potential null pointer dereferences. Feedback was provided to replace reinterpret_cast with static_cast for downcasting from the base class to derived classes, in accordance with the Google C++ Style Guide.

Comment on lines +271 to +272
flutter::HostWindowTooltip* tooltip_window =
reinterpret_cast<flutter::HostWindowTooltip*>(window);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use static_cast instead of reinterpret_cast for downcasting from a base class to a derived class. static_cast is safer as it provides compile-time checks that the types are related, which aligns with the Google C++ Style Guide.

    flutter::HostWindowTooltip* tooltip_window =
        static_cast<flutter::HostWindowTooltip*>(window);
References
  1. C++ code should follow the Google C++ Style Guide (as noted in the Repository Style Guide, line 48), which prefers static_cast for downcasts over reinterpret_cast. (link)

Comment on lines +281 to +282
flutter::HostWindowPopup* popup_window =
reinterpret_cast<flutter::HostWindowPopup*>(window);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use static_cast instead of reinterpret_cast for downcasting from a base class to a derived class. static_cast is safer as it provides compile-time checks that the types are related, which aligns with the Google C++ Style Guide.

    flutter::HostWindowPopup* popup_window =
        static_cast<flutter::HostWindowPopup*>(window);
References
  1. C++ code should follow the Google C++ Style Guide (as noted in the Repository Style Guide, line 48), which prefers static_cast for downcasts over reinterpret_cast. (link)

@rkishan516
rkishan516 force-pushed the null-pointer-window-checks branch from 126f6c7 to 82533d8 Compare April 30, 2026 11:25
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 30, 2026
@rkishan516
rkishan516 force-pushed the null-pointer-window-checks branch from 82533d8 to 7ede607 Compare April 30, 2026 11:26
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Apr 30, 2026

@mattkae mattkae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, since I imagine that this would be a weird case that we'd want logs for.

flutter::HostWindowTooltip* tooltip_window =
reinterpret_cast<flutter::HostWindowTooltip*>(window);
tooltip_window->UpdatePosition();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log a warning if the window doesn't exist?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do.

flutter::HostWindowPopup* popup_window =
reinterpret_cast<flutter::HostWindowPopup*>(window);
popup_window->UpdatePosition();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log a warning if the window doesn't exist?

@rkishan516
rkishan516 force-pushed the null-pointer-window-checks branch from 7ede607 to b2807e4 Compare May 5, 2026 02:09
@github-actions github-actions Bot removed the CICD Run CI/CD label May 5, 2026
@rkishan516 rkishan516 added the CICD Run CI/CD label May 5, 2026
mattkae
mattkae previously approved these changes May 5, 2026

@mattkae mattkae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks :)

@github-actions github-actions Bot removed the CICD Run CI/CD label May 5, 2026
@rkishan516 rkishan516 added the CICD Run CI/CD label May 5, 2026
mattkae
mattkae previously approved these changes May 5, 2026
@rkishan516 rkishan516 added the autosubmit Merge PR when tree becomes green via auto submit App label May 6, 2026
@loic-sharma
loic-sharma requested a review from knopp May 6, 2026 20:08
flutter::HostWindowTooltip* tooltip_window =
reinterpret_cast<flutter::HostWindowTooltip*>(window);
tooltip_window->UpdatePosition();
if (window) {

@knopp knopp May 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly does this happen? What is the reason for not being able to to get the window for handle? Perhaps we have a race condition somewhere? I'd like to know more about the situation that causes this rather than blindly added nullchecks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, sorry for the band-aid feel. Looking at it more carefully, I see two real ways the handle ends up stale by the time it reaches GetThisFromHandle:

  1. In OnDestroyWindow, we call SetParent(flutter_view_handle, nullptr) before DestroyWindow(hwnd). Once the FlutterView is reparented to null it becomes top-level, so GetAncestor(view, GA_ROOT) (used by GetTopLevelWindowHandle) starts returning the FlutterView's HWND instead of the host window's. That HWND has a different class name, so GetThisFromHandle correctly returns null. If anything on the Dart side calls updatePosition in that small window (before our WM_DESTROY handler sets _destroyed = true), we hit the crash. (So, definately this is a race condition as you think).
  2. After WM_NCDESTROY runs, the HostWindow has been erased from active_windows_, the view-controller is destroyed, and RemoveView has cleared views_. From there, engine->view(view_id) returns null and GetTopLevelWindowHandle returns nullptr. So Dart can hand us a null HWND. Same issue if a plugin's window-proc delegate handles WM_DESTROY before us — Dart never learns the window is gone, but the C++ side has already torn it down.

So the null check isn't covering up a corruption — it's the legitimate case where Dart still thinks the popup is alive but the native HostWindow has already been destroyed. Happy to also tighten things on the native side if you'd prefer (e.g. clear GWLP_USERDATA in ~HostWindow) or maybe reorder destroy (not sure about this).

Also one more thing, During engine shutdown, WindowManager::OnEngineShutdown sets on_message_ = nullptr before destroying windows, so Dart never gets WM_DESTROY → _destroyed stays false. Pending Dart callbacks (frame callbacks, microtasks) running before isolate shutdown can still invoke updatePosition.

@knopp knopp May 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ad 1) What dart code will call updatePosition while destroying the window? It seems to be that the only place for it would be from handling WM_DESTROY, which right now would need custom window subclass (we don't expose custom message handlers). Can you provide a realistic scenario how this would happen?

Ad 2) How exactly can plugin handle the WM_DESTROY?

Also one more thing, During engine shutdown, WindowManager::OnEngineShutdown sets on_message_ = nullptr before destroying windows, so Dart never gets WM_DESTROY → _destroyed stays false. Pending Dart callbacks (frame callbacks, microtasks) running before isolate shutdown can still invoke updatePosition.

This seems like a valid point. Would be nice to have testcase for this.

@rkishan516 rkishan516 May 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: Ad 1) and Ad 2) — yes, plugins absolutely can and do observe these messages. Two paths:

  1. FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate (flutter_windows.cc:318) is a public C ABI entry that dispatches every top-level window message — including WM_DESTROY — to plugin-registered delegates. Custom flutter_inappwebview uses this (flutter_inappwebview_windows_plugin.cpp:39).
  2. comctl32 SetWindowSubclass, used by WebView2 (which flutter_inappwebview embeds) on its host window. This is the path actually triggering the crashes.

So UpdatePopupPosition is being called synchronously, on the UI thread, from inside a comctl32 subclass dispatch. This isn't a hypothetical Dart-shutdown race anymore; it's a reentrant call during normal message processing on a real plugin's host window.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added tests for engine shutdown race condition.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugins generally don't call dart code from these handlers. That is a hypothetical situation. window_toolbox does let use handle the message from dart, but this would imply that whoever handles the message is handling aWM_DESTROY, from which it tries to position the popup. During destruction. That clearly seems like a very bad idea when crash seems completely warranted.

@rkishan516 rkishan516 May 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is happening in an app already, so not so hypothetical situation. But I still feel, engine shutdown race condition is general probable case (I have added tests for that).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make separate PR for the engine shutdown thing? Or remove the null checks from this one and update the PR. I don't one to fix the null without a repro but the engine shutdown seems like a pretty obvious problem.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will update this PR

@knopp knopp removed the autosubmit Merge PR when tree becomes green via auto submit App label May 6, 2026
@knopp

knopp commented May 7, 2026

Copy link
Copy Markdown
Member

There are definitely cases where we're going to have crashes. But I don't want to fix them with ad-hoc null check without knowing what exactly is going on, because it's possible that we have a bigger assumption wrong somewhere. That's why an actual reproducible crash is desirable.

@rkishan516

Copy link
Copy Markdown
Contributor Author

There are definitely cases where we're going to have crashes. But I don't want to fix them with ad-hoc null check without knowing what exactly is going on, because it's possible that we have a bigger assumption wrong somewhere. That's why an actual reproducible crash is desirable.

I agree, we shouldn't do ad-hoc null checks without knowing what exactly is going on, because it might end up resulting in something weird behaviour.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 21, 2026
@rkishan516 rkishan516 added the CICD Run CI/CD label Jun 21, 2026
@rkishan516
rkishan516 force-pushed the null-pointer-window-checks branch from fbf0def to 9c78eb8 Compare June 21, 2026 11:18
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 21, 2026
@rkishan516 rkishan516 added the CICD Run CI/CD label Jun 21, 2026
@rkishan516
rkishan516 requested a review from loic-sharma June 23, 2026 05:15
@loic-sharma

Copy link
Copy Markdown
Member

@knopp Are you OK with landing this now?

@rkishan516

Copy link
Copy Markdown
Contributor Author

@loic-sharma any update here?

@loic-sharma

Copy link
Copy Markdown
Member

@knopp re-ping :)

@rkishan516 rkishan516 added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 3, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 3, 2026
@auto-submit

auto-submit Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/185807, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: desktop Running on desktop CICD Run CI/CD engine flutter/engine related. See also e: labels. framework flutter/packages/flutter repository. See also f: labels. platform-windows Building on or for Windows specifically team-windows Owned by the Windows platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants