Skip to content

Conversation

@g-k-s-03
Copy link

@g-k-s-03 g-k-s-03 commented Dec 8, 2025

closes #1528
Description:
Previously, window_handle_ was set only in WndProc during WM_NCCREATE. This caused a race condition if methods like Show(), SetChildContent(), or GetClientArea() were called immediately after CreateWindow(), potentially leading to crashes.

Changes:
Set window_handle_ immediately after CreateWindow() in Win32Window::Create().
Added a safeguard in WndProc to ensure window_handle_ is set.
Updated dark mode handling to check Windows version before applying attributes.

Impact:
Calls to window methods are now safe immediately after creation.
Prevents crashes due to uninitialized window handle.

Testing:
Verified Show(), SetChildContent(), GetClientArea(), and Destroy() immediately after creation.
Dark mode behavior tested on supported Windows builds.

Summary by Sourcery

Resolve race conditions around Win32 window handle initialization and make dark mode handling conditional on OS support.

Bug Fixes:

  • Initialize the Win32 window handle immediately after window creation and guard usages to prevent race-condition crashes when calling window methods early.
  • Avoid calling window APIs like Show(), SetChildContent(), and GetClientArea() when the window handle is not yet available.
  • Prevent use of immersive dark mode attributes on Windows versions that do not support them.

Enhancements:

  • Show and update the Win32 window immediately upon creation instead of relying solely on later Show() calls.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 8, 2025

Reviewer's Guide

Ensures Win32Window has a valid window_handle_ immediately after CreateWindow and adds defensive checks around window usage, while gating dark mode window attributes behind an OS version check to avoid crashes on unsupported Windows builds.

Sequence diagram for Win32Window creation and safe early usage

sequenceDiagram
  actor App
  participant Win32Window
  participant Win32API as Win32_API
  participant WndProc

  App->>Win32Window: Create(title, origin, size)
  Win32Window->>Win32API: CreateWindow(window_class, title, styles, this)
  Win32API-->>Win32Window: HWND window
  Win32Window->>Win32Window: window_handle_ = window
  Win32Window->>Win32API: ShowWindow(window, SW_SHOWNORMAL)
  Win32Window->>Win32API: UpdateWindow(window)
  Win32Window->>Win32Window: UpdateTheme(window)
  Win32Window-->>App: bool created

  Note over Win32API,WndProc: During CreateWindow, WM_NCCREATE is sent
  Win32API->>WndProc: WM_NCCREATE(window, lpCreateParams=this)
  WndProc->>Win32Window: that = lpCreateParams
  alt handle not yet set
    WndProc->>Win32Window: that->window_handle_ = window
  end

  App->>Win32Window: Show()
  Win32Window->>Win32Window: if (!window_handle_) return false
  Win32Window->>Win32API: ShowWindow(window_handle_, SW_SHOWNORMAL)

  App->>Win32Window: SetChildContent(content)
  Win32Window->>Win32Window: if (!window_handle_) return
  Win32Window->>Win32Window: child_content_ = content
  Win32Window->>Win32API: SetParent(content, window_handle_)
  Win32Window->>Win32Window: frame = GetClientArea()

  App->>Win32Window: GetClientArea()
  Win32Window->>Win32Window: if (window_handle_) GetClientRect(window_handle_)
  Win32Window-->>App: RECT frame
Loading

Class diagram for updated Win32Window handle and theme management

classDiagram
  class Win32Window {
    HWND window_handle_
    HWND child_content_

    bool Create(std_wstring title, Point origin, Size size)
    bool Show()
    void SetChildContent(HWND content)
    RECT GetClientArea()
    void OnDestroy()
    void UpdateTheme(HWND window)
    static Win32Window* GetThisFromHandle(HWND window)
  }

  class GlobalHelpers {
    +void EnableFullDpiSupportIfAvailable(HWND hwnd)
    +bool IsDarkModeSupported()
  }

  Win32Window ..> GlobalHelpers : uses
  Win32Window ..> HWND : manages
  Win32Window ..> RECT : returns
Loading

File-Level Changes

Change Details Files
Initialize and use window_handle_ immediately after window creation to avoid race conditions.
  • Set window_handle_ directly after CreateWindow returns in Win32Window::Create.
  • Make the window visible from Create by adding WS_VISIBLE to the window style and explicitly calling ShowWindow/UpdateWindow.
  • Adjust Show() to early-return false if window_handle_ is not yet initialized.
windows/runner/win32_window.cpp
Harden window message and child-content handling against uninitialized window handles.
  • In WndProc WM_NCCREATE, set window_handle_ only if it has not already been set from Create().
  • In SetChildContent, guard against a null window_handle_ and no-op if it is unset.
  • In GetClientArea, handle a null window_handle_ by returning an empty RECT instead of calling GetClientRect.
windows/runner/win32_window.cpp
Gate dark mode window attributes behind a runtime OS version check.
  • Introduce IsDarkModeSupported helper that uses VerifyVersionInfoW to require Windows 10 build 17763 or later.
  • Update UpdateTheme to early-return on null HWND or unsupported Windows versions before calling DwmSetWindowAttribute for dark mode.
windows/runner/win32_window.cpp

Assessment against linked issues

Issue Objective Addressed Explanation
#1528 Ensure that window_handle_ is initialized and non-null immediately after Win32Window::Create (CreateWindow) returns, eliminating the race where it was only set in WndProc on WM_NCCREATE.
#1528 Ensure that methods using window_handle_ (Show(), SetChildContent(), GetClientArea(), Destroy()) are safe to call immediately after window creation and do not crash or exhibit undefined behavior due to a null window_handle_.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Changing the window style to WS_OVERLAPPEDWINDOW | WS_VISIBLE and calling ShowWindow/UpdateWindow inside Create alters the previous lifecycle contract where callers explicitly decided when to show the window; consider preserving the old behavior and only fixing the race on window_handle_ initialization.
  • The new guard clauses that silently no-op when window_handle_ is null (e.g., in Show, SetChildContent, GetClientArea) may hide logic errors; consider surfacing these as explicit failures (assert/log) so callers know they are using the window too early.
  • IsDarkModeSupported is called on every theme update and uses VerifyVersionInfoW; consider caching its result in a static or similar to avoid repeated version checks and keep the hot path lightweight.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Changing the window style to `WS_OVERLAPPEDWINDOW | WS_VISIBLE` and calling `ShowWindow`/`UpdateWindow` inside `Create` alters the previous lifecycle contract where callers explicitly decided when to show the window; consider preserving the old behavior and only fixing the race on `window_handle_` initialization.
- The new guard clauses that silently no-op when `window_handle_` is null (e.g., in `Show`, `SetChildContent`, `GetClientArea`) may hide logic errors; consider surfacing these as explicit failures (assert/log) so callers know they are using the window too early.
- `IsDarkModeSupported` is called on every theme update and uses `VerifyVersionInfoW`; consider caching its result in a `static` or similar to avoid repeated version checks and keep the hot path lightweight.

## Individual Comments

### Comment 1
<location> `windows/runner/win32_window.cpp:57-66` </location>
<code_context>
 }

+// Checks if the current Windows version supports dark mode
+bool IsDarkModeSupported() {
+  // DWMWA_USE_IMMERSIVE_DARK_MODE requires Windows 10 Build 17763 (version 1809) or higher
+  OSVERSIONINFOEXW osvi = {};
+  DWORDLONG const dwlConditionMask = VerSetConditionMask(
+      VerSetConditionMask(
+          VerSetConditionMask(
+              0, VER_MAJORVERSION, VER_GREATER_EQUAL),
+          VER_MINORVERSION, VER_GREATER_EQUAL),
+      VER_BUILDNUMBER, VER_GREATER_EQUAL);
+  
+  osvi.dwOSVersionInfoSize = sizeof(osvi);
+  osvi.dwMajorVersion = 10;
+  osvi.dwMinorVersion = 0;
+  osvi.dwBuildNumber = 17763;  // Windows 10 version 1809
+  
+  return VerifyVersionInfoW(&osvi, 
+                           VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER,
+                           dwlConditionMask) != FALSE;
+}
+
</code_context>

<issue_to_address>
**issue (bug_risk):** The explicit OS version check for dark mode support is fragile and may misreport capabilities.

Using `VerifyVersionInfoW` here is fragile: it relies on correct Windows 10+ manifesting and on version helper APIs that are effectively deprecated. If the manifest isn’t aligned, this can report that the OS doesn’t meet the version even when `DWMWA_USE_IMMERSIVE_DARK_MODE` is available, so dark mode never gets enabled. Instead, consider calling `DwmSetWindowAttribute` (optionally after a one-time runtime probe) and checking its return value; older builds will just ignore or fail the call safely.
</issue_to_address>

### Comment 2
<location> `windows/runner/win32_window.cpp:159-160` </location>
<code_context>
   double scale_factor = dpi / 96.0;

+  // Create window with pointer to this instance
   HWND window = CreateWindow(
-      window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
       Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
       Scale(size.width, scale_factor), Scale(size.height, scale_factor),
</code_context>

<issue_to_address>
**issue (bug_risk):** Making the window visible in CreateWindow changes the lifecycle and may cause unexpected early showing.

This also causes the window to be shown twice: once by `WS_VISIBLE` during `CreateWindow`, and again in `Create()`/`Show()`. That can break callers that rely on `OnCreate` finishing or on explicitly deciding when to show the window, and may introduce flicker. Consider either removing `WS_VISIBLE` and keeping the explicit `ShowWindow` calls, or keeping `WS_VISIBLE` and making `Show()` a no-op when the window is already visible.
</issue_to_address>

### Comment 3
<location> `windows/runner/win32_window.cpp:169-176` </location>
<code_context>
+  // Set window_handle_ immediately after CreateWindow returns
+  window_handle_ = window;
+
+  // Show the window immediately
+  ShowWindow(window, SW_SHOWNORMAL);
+  UpdateWindow(window);
+
   UpdateTheme(window);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Showing/updating the window inside Create() may not align with callers’ expectations about when the window becomes visible.

Having `Create()` also call `ShowWindow`/`UpdateWindow` removes the caller’s ability to control when the window becomes visible and may introduce re-entrancy if messages are processed during initialization. If `Create()` previously only established the handle and `Show()` managed visibility, consider preserving that contract and keeping the window hidden here, or making `Show()` a no-op that doesn’t perform additional work beyond what `Create()` already did.

```suggestion
  // Set window_handle_ immediately after CreateWindow returns
  window_handle_ = window;

  UpdateTheme(window);
```
</issue_to_address>

### Comment 4
<location> `windows/runner/win32_window.cpp:291-293` </location>
<code_context>

 RECT Win32Window::GetClientArea() {
   RECT frame;
-  GetClientRect(window_handle_, &frame);
+  if (window_handle_) {
+    GetClientRect(window_handle_, &frame);
+  } else {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Returning a zero rect for a null window_handle_ might mask incorrect call sites.

This avoids a null dereference but may hide invalid state and let callers proceed with a bogus size. Consider making the invalid state explicit (e.g., a debug assert or documented precondition that `window_handle_` must be valid) so layout logic doesn’t silently treat the client area as legitimately empty.

Suggested implementation:

```cpp
RECT Win32Window::GetClientArea() {
  // GetClientArea assumes that window_handle_ has been created and is valid.
  // A null window_handle_ indicates a usage error at the call site.
  RECT frame{0, 0, 0, 0};

  assert(window_handle_ && "GetClientArea called with null window_handle_");

  if (window_handle_) {
    GetClientRect(window_handle_, &frame);
  }

  return frame;
}

```

1. Ensure `#include <cassert>` (or your project's equivalent debug/assert header) is present near the top of `windows/runner/win32_window.cpp` so that `assert` is available.
2. Optionally, update any documentation or comments for `Win32Window` to state that `GetClientArea` requires a valid `window_handle_` and should only be called after the window has been created.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@g-k-s-03 g-k-s-03 force-pushed the fix-win32-window-handle-race branch from 6959ed6 to 923dea0 Compare December 8, 2025 18:27
@g-k-s-03 g-k-s-03 changed the title Fix race condition for window_handle_ in Win32Window fix(win32): remove WS_VISIBLE and add assert in GetClientArea Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Win32Window::window_handle_ may be nullptr immediately after CreateWindow

1 participant