fix(Core/Movement): Fix use-after-free in WaypointMovementGenerator (#25270)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Shauren <shauren.trinity@gmail.com>
Co-authored-by: ccrs <ccrs@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-03-27 16:26:24 -05:00
committed by GitHub
parent 3bdb072396
commit 1520a6f055
2 changed files with 35 additions and 21 deletions

View File

@@ -879,10 +879,10 @@ void MotionMaster::MoveDistract(uint32 timer)
void MotionMaster::Mutate(MovementGenerator* m, MovementSlot slot) void MotionMaster::Mutate(MovementGenerator* m, MovementSlot slot)
{ {
bool const delayed = (_cleanFlag & MMCF_UPDATE);
while (MovementGenerator* curr = Impl[slot]) while (MovementGenerator* curr = Impl[slot])
{ {
bool delayed = (_top == slot && (_cleanFlag & MMCF_UPDATE));
// clear slot AND decrease top immediately to avoid crashes when referencing null top in DirectDelete // clear slot AND decrease top immediately to avoid crashes when referencing null top in DirectDelete
Impl[slot] = nullptr; Impl[slot] = nullptr;
while (!empty() && !top()) while (!empty() && !top())

View File

@@ -157,27 +157,31 @@ void WaypointMovementGenerator<Creature>::ProcessWaypointArrival(Creature* creat
creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr); creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr);
} }
creature->UpdateWaypointID(waypoint.Id); // scripts can invalidate current path, store what we need
creature->UpdateCurrentWaypointInfo(waypoint.Id, i_path->Id); uint32 const waypointId = waypoint.Id;
uint32 const pathId = i_path->Id;
creature->UpdateWaypointID(waypointId);
creature->UpdateCurrentWaypointInfo(waypointId, pathId);
// Inform AI // Inform AI
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
{ {
AI->MovementInform(WAYPOINT_MOTION_TYPE, waypoint.Id); AI->MovementInform(WAYPOINT_MOTION_TYPE, waypointId);
AI->WaypointReached(waypoint.Id, i_path->Id); AI->WaypointReached(waypointId, pathId);
} }
if (Unit* owner = creature->GetCharmerOrOwner()) if (Unit* owner = creature->GetCharmerOrOwner())
{ {
if (UnitAI* AI = owner->GetAI()) if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId);
} }
else else
{ {
if (TempSummon* tempSummon = creature->ToTempSummon()) if (TempSummon* tempSummon = creature->ToTempSummon())
if (Unit* owner2 = tempSummon->GetSummonerUnit()) if (Unit* owner2 = tempSummon->GetSummonerUnit())
if (UnitAI* AI = owner2->GetAI()) if (UnitAI* AI = owner2->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypoint.Id); AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId);
} }
// Path end notifications fire after WaypointReached so that m_path_id // Path end notifications fire after WaypointReached so that m_path_id
@@ -187,11 +191,11 @@ void WaypointMovementGenerator<Creature>::ProcessWaypointArrival(Creature* creat
creature->UpdateCurrentWaypointInfo(0, 0); creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
AI->PathEndReached(i_path->Id); AI->PathEndReached(pathId);
// Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
AI->WaypointPathEnded(waypoint.Id, i_path->Id); AI->WaypointPathEnded(waypointId, pathId);
} }
// All hooks called and infos updated. Time to increment the waypoint node id // All hooks called and infos updated. Time to increment the waypoint node id
@@ -385,8 +389,15 @@ bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 di
WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode); WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode);
UpdateHomePosition(creature, passedWp); UpdateHomePosition(creature, passedWp);
creature->UpdateWaypointID(passedWp.Id);
creature->UpdateCurrentWaypointInfo(passedWp.Id, i_path->Id); // Save data before AI callbacks — they can invalidate the reference
uint32 const wpId = passedWp.Id;
uint32 const wpPathId = i_path->Id;
uint32 const wpDelay = passedWp.Delay;
std::optional<float> const wpOrientation = passedWp.Orientation;
creature->UpdateWaypointID(wpId);
creature->UpdateCurrentWaypointInfo(wpId, wpPathId);
if (passedWp.EventId && urand(0, 99) < passedWp.EventChance) if (passedWp.EventId && urand(0, 99) < passedWp.EventChance)
{ {
@@ -396,23 +407,24 @@ bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 di
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
{ {
AI->MovementInform(WAYPOINT_MOTION_TYPE, passedWp.Id); AI->MovementInform(WAYPOINT_MOTION_TYPE, wpId);
AI->WaypointReached(passedWp.Id, i_path->Id); AI->WaypointReached(wpId, wpPathId);
} }
// Advance node // Advance node
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size(); if (i_path && !i_path->Nodes.empty())
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size();
// If this waypoint has a delay, stop the spline and pause // If this waypoint has a delay, stop the spline and pause
if (passedWp.Delay > 0) if (wpDelay > 0)
{ {
creature->StopMoving(); creature->StopMoving();
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
_waypointDelay = passedWp.Delay; _waypointDelay = wpDelay;
_waypointReached = true; _waypointReached = true;
_smoothSplineLaunched = false; _smoothSplineLaunched = false;
if (passedWp.Orientation.has_value()) if (wpOrientation.has_value())
creature->SetFacingTo(*passedWp.Orientation); creature->SetFacingTo(*wpOrientation);
return true; return true;
} }
@@ -423,15 +435,17 @@ bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 di
if (!_repeating) if (!_repeating)
{ {
// Path ended // Path ended
uint32 const endWpId = i_path->Nodes.at(i_currentNode).Id;
uint32 const endPathId = i_path->Id;
_done = true; _done = true;
_smoothSplineLaunched = false; _smoothSplineLaunched = false;
creature->UpdateCurrentWaypointInfo(0, 0); creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
AI->PathEndReached(i_path->Id); AI->PathEndReached(endPathId);
// Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI // Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI
if (CreatureAI* AI = creature->AI()) if (CreatureAI* AI = creature->AI())
AI->WaypointPathEnded(i_path->Nodes.at(i_currentNode).Id, i_path->Id); AI->WaypointPathEnded(endWpId, endPathId);
} }
else else
{ {