Skip to content

ImGui: guard ShowCursor against a torn-down context - #267

Draft
Kheartz wants to merge 1 commit into
MafiaHub:developfrom
Kheartz:null_check
Draft

ImGui: guard ShowCursor against a torn-down context#267
Kheartz wants to merge 1 commit into
MafiaHub:developfrom
Kheartz:null_check

Conversation

@Kheartz

@Kheartz Kheartz commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

ImGUI::Wrapper::ShowCursor() was the only public entry point on the wrapper
that did not check isContextInitialized before touching ImGui state. A client
teardown path (disconnect handling → release control locks → ShowCursor(false))
ran after Wrapper::Shutdown() and dereferenced a destroyed ImGui context.

Found while symbolizing a user-submitted crash dump from a Release build of a
UE 4.27.2 title on the D3D12 backend.

Crash signature

ExceptionCode:  c0000005 (Access violation)
Operation:      write to 0x0000000000000078
Faulting insn:  mov byte ptr [rax+50h], bl
Registers:      rax=0x28  rbx=0  rcx=0  rbp=0
Thread:         GameThread

ImGui::GetIO() resolves to GImGui->IO. With a null context that is
nullptr + offsetof(ImGuiContext, IO) = 0x28 (matches rax), and
ImGuiIO::MouseDrawCursor at +0x50 puts the store at 0x78 (matches the
faulting address). rbx=0 is the false being written.

Release-only in practice: ImGui::GetIO() normally carries
IM_ASSERT(GImGui != NULL), which /DNDEBUG removes, degrading the guard into
a raw null dereference.

Two defects fixed

  1. ShowCursor never checked the flag. Every other gated entry point
    (Init, Shutdown, Render, OnDeviceLost, OnDeviceReset) does.

  2. Shutdown() cleared isContextInitialized after DestroyContext().
    That left a window in which the flag claimed the context was live while the
    backends were already shut down and the context destroyed — so the guard in
    (1) alone would not cover a caller landing inside it. The flag is now cleared
    up front, immediately after the early-out.

Investigation notes: GPU was ruled out, not a factor

Recording this so it is not re-chased. The reporting machine is a hybrid-graphics
laptop, and the crash context showed the title rendering on the integrated
GPU (Misc.PrimaryGPUBrand = Intel iGPU; only the Intel D3D12 UMD was loaded in
the process, the NVIDIA D3D UMD never was) despite a discrete GPU being present.

That initially looked causal — a renderer hook failing to attach would leave
isContextInitialized == false and reach the same defect. It was ruled out:

  • Forcing the discrete GPU (Windows per-app graphics preference and the NVIDIA
    control panel) did not stop the crash.
  • The reporter confirmed the mod UI was visible before the disconnect, so
    Init had succeeded and the context genuinely existed.

Conclusion: this is a teardown-ordering bug, not a renderer-init bug. The GPU
selection oddity is real but separate, and is being tracked downstream.

Secondary observation (not addressed here)

CMAKE_CXX_FLAGS_RELEASE is /O2 /Ob2 /DNDEBUG with no /DEBUG at link, so
Release builds ship without a PDB and user crash dumps cannot be symbolized
without reproducing the exact toolchain. Adding /Zi plus
/DEBUG:FULL /OPT:REF /OPT:ICF /PDBALTPATH:%_PDB% was measured on a real build
of this framework to cost exactly one 4 KB page of SizeOfImage while leaving
.text byte-identical. /OPT:REF /OPT:ICF must be explicit — /DEBUG silently
defaults them off, which would change code layout. Happy to open a separate PR
if that is wanted.

Testing

Not reproduced locally — the crash requires the teardown ordering seen on the
reporting machine. The fix was verified by inspection against the symbolized
stack, and the register arithmetic above accounts for the faulting address
exactly. No behavioural change when the context is live; when it is not,
ShowCursor becomes a no-op, which is correct (no cursor to draw).

Summary by CodeRabbit

  • Bug Fixes
    • Improved shutdown handling to prevent invalid context state during teardown.
    • Prevented cursor display operations from accessing unavailable UI resources when initialization has not completed.

ShowCursor was the only entry point not checking isContextInitialized;
a disconnect path calling it after Shutdown() crashed on a null context.
Shutdown() also cleared the flag after DestroyContext(); clear it first.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c42e882-f2e3-44f2-86ae-33b9c2076ff7

📥 Commits

Reviewing files that changed from the base of the PR and between fc6ccfa and 39fe427.

📒 Files selected for processing (1)
  • code/framework/src/external/imgui/wrapper.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The ImGui wrapper now marks the context as uninitialized before shutdown operations and avoids ImGui::GetIO() when initialization has not completed.

Changes

ImGui context safety

Layer / File(s) Summary
Shutdown and cursor guards
code/framework/src/external/imgui/wrapper.cpp
Shutdown() clears isContextInitialized before backend and context teardown. ShowCursor() returns early when the context is not initialized.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Merge Risk: ⚪ Minimal · up to 39fe4

This localized change prevents cursor updates after ImGui teardown and closes the shutdown ordering window; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: segfaultd

Poem

A rabbit checks the context state,
Before the shutdown closes the gate.
No cursor call when none is near,
The ImGui path stays calm and clear.
Hop, hop, safe teardown here!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: protecting ShowCursor() from use after the ImGui context is torn down.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Kheartz
Kheartz requested a review from Segfaultd August 31, 2026 15:51
@Kheartz Kheartz mentioned this pull request Aug 31, 2026
@Kheartz

Kheartz commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

User's workaround was to force GPU for both HogwartsLegacy.exe and HogwartsLegacyLauncher.exe.

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.

1 participant