Skip to content

fix(memory): Fix various memory leaks (2)#2710

Open
Caball009 wants to merge 11 commits into
TheSuperHackers:mainfrom
Caball009:MEM_LEAK_FIXES
Open

fix(memory): Fix various memory leaks (2)#2710
Caball009 wants to merge 11 commits into
TheSuperHackers:mainfrom
Caball009:MEM_LEAK_FIXES

Conversation

@Caball009
Copy link
Copy Markdown

@Caball009 Caball009 commented May 14, 2026

This PR fixes 10 memory leaks (in order of commits):

  1. Potential memory leaks, but I've not observed any actual leaks here.
  2. BattlePlanUpdate::m_bonuses was never deallocated, so every time a USA Strategy Center was built, there'd be a leak.
  3. Observed when looking at the Particle Cannon beam. SegLineRendererClass::Get_Texture has a side effect as it increases the reference count, so shouldn't be used here.
  4. Player upgrades that were cancelled would leak. Can be reproduced by building a Barracks and starting the Capture Building upgrade and then cancelling it.
  5. Scripts that would load on map start could leak. I have a map that generates more than 90K of leaks because of this.
  6. Animation handle wasn't cleaned up even though that's expected. There's even a comment that says so... I think I observed a leak here when looking at a USA Strategy Center.
  7. It was possible that the quit menu window would not be deallocated. Can be reproduced by loading a save game or replay. Make sure to load the quit menu at least once (by pressing ESC), and make sure that the game ends with a victory / defeat screen, so not manually leaving the game.
  8. Pausing the game would leak audio events that were in the audio request container at the time.
  9. The static DynamicAudioEventInfo and everything it would allocate would not be deallocated before the game process ends, and show up as a leak.
  10. / 10. Loading a save game (skirmish or challenge) would allocate TheSkirmishGameInfo or TheChallengeGameInfo but not deallocate them.

TODO:

  • Replicate in Generals.

@Caball009 Caball009 added Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Memory Is memory related Fix Is fixing something, but is not user facing labels May 14, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

Greptile Summary

This PR fixes 10 memory leaks across the Zero Hour engine, covering animation channel loading, audio request lifecycle, script list management, upgrade cancellation, and save-game state objects. Most fixes are straightforward and correct; the TheSkirmishGameInfo/TheChallengeGameInfo deletion timing (already flagged in review threads) remains an open design question.

  • Audio request lifecycle (AudioRequest.cpp, MilesAudioManager.cpp): The destructor now owns m_pendingEvent cleanup, while processRequestList resets the flag after handing ownership to processRequest, correctly preventing both the pause-path leak and double-free.
  • Animation channel leaks (hcanim.cpp, hrawanim.cpp): Channel variables are now scoped to their switch cases and the allocated object is deleted when Load_W3D returns false, closing the leak on partial load failure.
  • Miscellaneous fixes (BattlePlanUpdate, seglinerenderer, Player, SidesList, ScriptDialog, GameStateMap, W3DModelDraw, Drawable): Each targeted leak is addressed with appropriate deleteInstance/delete[] calls at the correct ownership-transfer points.

Confidence Score: 3/5

Most leak fixes are safe, but the unconditional deletion of TheSkirmishGameInfo/TheChallengeGameInfo inside clearGameData breaks the challenge campaign Next Mission flow and score-screen music, a crash path acknowledged in the review thread but not yet resolved.

The vast majority of the 10 fixes are clean and correct — animation channel scoping, audio request lifecycle, upgrade deletion, script list cleanup, and map buffer error paths all look solid. The one unresolved problem is in GameLogicDispatch.cpp: deleting TheSkirmishGameInfo/TheChallengeGameInfo unconditionally whenever showScoreScreen is true means the challenge Next Mission click crashes because ScoreScreen.cpp asserts TheChallengeGameInfo != nullptr before reusing it, and skirmish score-screen music is silently broken.

Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp — the game info deletion block needs a guard so it only runs when the info was originally allocated during a save-game load, not during a live campaign or skirmish session.

Important Files Changed

Filename Overview
Core/GameEngine/Source/Common/Audio/AudioRequest.cpp Adds destructor to delete m_pendingEvent when m_usePendingEvent is true, fixing the leak when requests are discarded (e.g. on pause).
Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Restructures clearGameData to add destroyQuitMenu and TheSkirmishGameInfo/TheChallengeGameInfo deletion; the campaign Next Mission crash on deletion of TheChallengeGameInfo is acknowledged but unresolved.
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Resets m_usePendingEvent and m_pendingEvent to prevent double-free after processRequest takes ownership of the audio event.
Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp Moves getAnimHandle() call (which AddRefs) inside the m_renderObject null guard, preventing a ref-count leak when m_renderObject is null.
Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp Scopes tc_chan/ad_chan/newbitchan variables to their case blocks and deletes the channel object on Load_W3D failure, fixing leaks on partial animation load.
Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp Replaces Get_Texture() (which AddRefs) with Peek_Texture() in the debug warning path, preventing an unmatched reference count increase.
GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp Adds deleteInstance(upgrade) inside the upgrade guard and moves markUIDirty() inside the same guard; correctly fixes the cancel-upgrade leak.
GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp Adds delete[] buffer before each error-path throw in embedPristineMap, embedInUseMap, and extractAndSaveMap; all three sites have a non-null buffer at those points.
GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp Replaces the heap-allocated singleton DynamicAudioEventInfo marker with a static subclass instance (DynamicAudioEventInfoStatic), avoiding both the static-init-order fiasco and the leak.
GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp Adds deleteInstance for scripts that cannot be matched to a skirmish side, preventing a leak during prepareForMP_or_Skirmish.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp Adds deleteInstance(m_bonuses) in the destructor, fixing the per-Strategy-Center build leak.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp Scopes newchan/newbitchan to their case blocks and correctly deletes the channel on Load_W3D failure, fixing leaks on partial animation load.
GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp Adds deleteInstance for scripts that cannot be matched to a player side or whose target side has no existing script list, preventing leaks during script import.

Reviews (4): Last reviewed commit: "Addressed feedback & made tweaks." | Re-trigger Greptile

Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp Outdated
Comment on lines +283 to +294
if (TheSkirmishGameInfo)
{
delete TheSkirmishGameInfo;
TheSkirmishGameInfo = nullptr;
TheGameInfo = nullptr;
}
else if (TheChallengeGameInfo)
{
delete TheChallengeGameInfo;
TheChallengeGameInfo = nullptr;
TheGameInfo = nullptr;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 TheChallengeGameInfo deletion breaks the challenge campaign "Next Mission" flow

clearGameData(TRUE) (which shows the score screen) now unconditionally deletes TheChallengeGameInfo. In ScoreScreen.cpp, startNextCampaignGame() immediately asserts TheChallengeGameInfo != nullptr (line 200) before re-configuring it for the next mission. Since clearGameData() runs before the score screen updates, TheChallengeGameInfo is already null by the time the player clicks "Next Mission", causing an assertion failure / crash on every challenge campaign mission transition.

The same applies to TheSkirmishGameInfo being set to null before ScoreScreenUpdate() uses TheGameInfo (line 428 of ScoreScreen.cpp) to start score-screen music, silently breaking score-screen music for all regular skirmish games.

A targeted fix would guard these deletions to only run when the game info was allocated during a save-game load (e.g. by checking an additional flag or by only deleting when isLoadingSave() was true).

Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
Line: 283-294

Comment:
**`TheChallengeGameInfo` deletion breaks the challenge campaign "Next Mission" flow**

`clearGameData(TRUE)` (which shows the score screen) now unconditionally deletes `TheChallengeGameInfo`. In `ScoreScreen.cpp`, `startNextCampaignGame()` immediately asserts `TheChallengeGameInfo != nullptr` (line 200) before re-configuring it for the next mission. Since `clearGameData()` runs _before_ the score screen updates, `TheChallengeGameInfo` is already null by the time the player clicks "Next Mission", causing an assertion failure / crash on every challenge campaign mission transition.

The same applies to `TheSkirmishGameInfo` being set to null before `ScoreScreenUpdate()` uses `TheGameInfo` (line 428 of `ScoreScreen.cpp`) to start score-screen music, silently breaking score-screen music for all regular skirmish games.

A targeted fix would guard these deletions to only run when the game info was allocated during a save-game load (e.g. by checking an additional flag or by only deleting when `isLoadingSave()` was true).

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point.

A targeted fix would guard these deletions to only run when the game info was allocated during a save-game load (e.g. by checking an additional flag or by only deleting when isLoadingSave() was true).

This doesn't fix the crash that happens when restarting a challenge match from the 'score screen'. I could use some suggestions where these deallocations are better suited to take place.

Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

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

I think it makes sense to split this into multiple reviews because of the review compexity of some.

(My review is incomplete)

Comment thread Core/GameEngine/Source/Common/Audio/AudioRequest.cpp
processRequest(req);

// TheSuperHackers @info Deallocating the audio event is no longer responsibility of this request,
// because it was just processed. Reset fields to avoid double free.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This does not fit under the umbrella of memory leaks, but memory corruptions.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is an essential part of fixing the memory leak.

Copy link
Copy Markdown

@xezon xezon May 17, 2026

Choose a reason for hiding this comment

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

A memory corruption typically classifies more severe than a memory leak. Therefore I would typically classify it as such first.

Comment thread GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp Outdated
Comment thread GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp Outdated
@Caball009
Copy link
Copy Markdown
Author

Caball009 commented May 15, 2026

I think it makes sense to split this into multiple reviews because of the review compexity of some.

Are there particular changes / commits that you'd like in a separate PR?

@xezon
Copy link
Copy Markdown

xezon commented May 17, 2026

Are there particular changes / commits that you'd like in a separate PR?

I would say any fixes that span multiple files are good candidates for separation.

// TheSuperHackers @info Deallocating the audio event is no longer responsibility of this request,
// because it was just processed. Reset fields to avoid double free.
req->m_usePendingEvent = false;
req->m_pendingEvent = nullptr;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Who is deallocating it?

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

Labels

Fix Is fixing something, but is not user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Memory Is memory related ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants