Skip to content

bugfix(contain): Restore retail compatibility after crash fix in OpenContain::killAllContained#2439

Open
Caball009 wants to merge 3 commits intoTheSuperHackers:mainfrom
Caball009:fix_OpenContain_killAllContained
Open

bugfix(contain): Restore retail compatibility after crash fix in OpenContain::killAllContained#2439
Caball009 wants to merge 3 commits intoTheSuperHackers:mainfrom
Caball009:fix_OpenContain_killAllContained

Conversation

@Caball009
Copy link

@Caball009 Caball009 commented Mar 11, 2026

The way the crash fix was implemented in #921 assumes that the logic behavior doesn't change if m_containList / m_containListSize is already empty with the first iteration. This is not the case in the attached replay in #2215.

If m_containList / m_containListSize is empty, it'll make garrisoned civilian buildings unoccupied on the first iteration instead of the last iteration, and the GLA Demolition upgrade would cause damage to the building for each occupant instead of just the last one when hit with a neutron shell.

garrisoned_gla_demolition_upgrade_neutron_shell_incorrect.mp4
garrisoned_gla_demolition_upgrade_neutron_shell_correct.mp4

Pay attention to the health of the civilian building. In the correct version, only the detonation of the last GLA RPG Trooper causes damage instead of every one.

TODO:

  • Polish comments.

@Caball009 Caball009 added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour ThisProject The issue was introduced by this project, or this task is specific to this project labels Mar 11, 2026
@greptile-apps
Copy link

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR fixes a behaviour regression introduced by PR #921's crash fix in OpenContain::killAllContained. PR #921 guarded against recursive list modification by swapping m_containList out immediately, but that caused GarrisonContain::onRemoving to always see an empty list, making every occupant appear to be the "last" one. This broke civilian-building garrison ownership and made the GLA Demolition upgrade deal full damage for every occupant instead of only the last.

The new approach erase-before-notify: each element is erased from m_containList (and m_containListSize is decremented) before onRemoving is called, so the callback sees the correct shrinking list size. After rider->kill(), the iterator is unconditionally reset to m_containList.begin(), which is safe regardless of whether kill() recurses into the same function or not. A final safety clear() and m_containListSize = 0 guard against any unexpected leftover state.

Key points:

  • The iterator invalidation concern raised on PR [ZH] Fix crash in OpenContain::killAllContained() when killed occupants kill the host container #921 is now fully resolved: it is never used between erase(it) and it = m_containList.begin().
  • The sequential order (erase → onRemovingonRemovedFromkill() → reset iterator) correctly mirrors the pattern already used in harmAndForceExitAllContained.
  • Null elements (should never occur in practice) are skipped with ++it and cleaned up by the final clear(); m_containListSize is reset to 0 unconditionally, so no size mismatch persists.

Confidence Score: 4/5

  • This PR is safe to merge; it correctly restores intended retail behaviour while preserving the original crash fix's safety guarantees.
  • The logic is sound: erasing each element before invoking callbacks gives onRemoving the correct list size at every step, and resetting the iterator to begin() after kill() handles all recursive-drain scenarios without touching an invalidated iterator. The final clear() / m_containListSize = 0 are harmless safety resets. No custom instruction violations were found in the changed lines.
  • No files require special attention.

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Reverts the swap-to-empty approach from PR #921 and replaces it with an in-place erase-then-reset-iterator loop, so that onRemoving sees the live (shrinking) list size during each iteration — restoring correct "last occupant" semantics for GarrisonContain. Logic is sound; null-element cleanup is deferred to the final safety clear().

Sequence Diagram

sequenceDiagram
    participant KC as killAllContained
    participant CL as m_containList
    participant OC as onRemoving
    participant RD as rider

    KC->>CL: it = begin()
    loop while it != end()
        KC->>CL: rider = *it
        KC->>CL: erase(it) / --m_containListSize
        KC->>OC: onRemoving(rider)
        Note over OC: Sees updated list size<br/>(correct "last occupant" check)
        KC->>RD: onRemovedFrom(container)
        KC->>RD: kill()
        Note over KC,RD: kill() may recursively<br/>drain m_containList
        KC->>CL: it = begin()
    end
    KC->>CL: ASSERT empty, then clear() / size=0
Loading

Last reviewed commit: "Updated comments."

@Caball009 Caball009 force-pushed the fix_OpenContain_killAllContained branch from 04af4f6 to 341c271 Compare March 11, 2026 08:57
@Caball009 Caball009 changed the title bugfix(opencontain): Restore retail compatibility after crash fix in OpenContain::killAllContained bugfix(contain): Restore retail compatibility after crash fix in OpenContain::killAllContained Mar 11, 2026
@Mauller
Copy link

Mauller commented Mar 11, 2026

Does this still prevent the crashing with tech terrors?

@Caball009
Copy link
Author

Does this still prevent the crashing with tech terrors?

Yes, the original crash is still fixed. I made sure of that, and also created a reproduction of it in the crash fix PR.

Copy link

@Skyaero42 Skyaero42 left a comment

Choose a reason for hiding this comment

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

This looks correct to me.

Copy link

@Mauller Mauller left a comment

Choose a reason for hiding this comment

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

Looks good to me too

Copy link

@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.

Logic looks plausible. Comments need polishing.

// on the death of the host container. This is reproducible by shooting with
// Neutron Shells on a GLA Technical containing GLA Terrorists.
// Neutron Shells on a GLA Technical containing GLA Toxin Terrorists
// with the Chem_SuicideWeapon upgrade, which is automatically granted by the GLA Toxin Command Center.
Copy link

Choose a reason for hiding this comment

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

Too much additional information in my opinion.

Copy link
Author

Choose a reason for hiding this comment

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

I removed most of it.

DEBUG_ASSERTCRASH( rider, ("Contain list must not contain null element"));
if ( rider )
{
// TheSuperHackers @bugfix Caball009 11/03/2026 The contain list must be updated while iterating over it.
Copy link

Choose a reason for hiding this comment

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

I suggest merge this description with the one above.

Copy link
Author

Choose a reason for hiding this comment

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

I could use some suggestions as to what information you want to retain from your own comment.

Copy link

Choose a reason for hiding this comment

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

The important part is the repro and the technical error it caused.

@Caball009
Copy link
Author

Caball009 commented Mar 20, 2026

Updated the comments.

I got rid of the old 'Neutron Shells + GLA Technical containing GLA Terrorists' issue reproduction description that seems too detailed to me.

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

Labels

Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore retail compatibility after crash fix in OpenContain::killAllContained

4 participants