From 269689703ddcc573c6e456281eb081c034f45a90 Mon Sep 17 00:00:00 2001 From: Boondorl Date: Mon, 7 Jul 2025 21:23:16 -0400 Subject: [PATCH] Clear references to map data on level change These shouldn't be left as they'll now point towards potentially invalid memory and also cause errors with serializing. Arrays and maps holding them are cleared. Also unlinks and relinks inventory items correctly from the hashmap on traveling. --- src/common/objects/dobject.cpp | 61 ++++++++++++++++++++++++++++++++++ src/common/objects/dobject.h | 3 ++ src/common/utility/tarray.h | 4 +-- src/g_level.cpp | 8 ++++- src/namedef_custom.h | 7 ++++ src/p_setup.cpp | 7 ++++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/common/objects/dobject.cpp b/src/common/objects/dobject.cpp index 5b2658a70..b9817a919 100644 --- a/src/common/objects/dobject.cpp +++ b/src/common/objects/dobject.cpp @@ -417,6 +417,67 @@ size_t DObject::PropagateMark() // //========================================================================== +void DObject::ClearNativePointerFields(const TArray& types) +{ + auto cls = GetClass(); + if (cls->VMType == nullptr) + return; + + auto it = cls->VMType->Symbols.GetIterator(); + TMap::Pair* sym = nullptr; + while (it.NextPair(sym)) + { + auto field = dyn_cast(sym->Value); + if (field == nullptr) + continue; + + PType* base = field->Type; + PType* t = base; + if (base->isArray() && !base->isStaticArray()) + t = static_cast(base)->ElementType; + else if (base->isDynArray()) + t = static_cast(base)->ElementType; + else if (base->isMap()) + t = static_cast(base)->ValueType; + + if (!t->isRealPointer()) + continue; + + auto pType = static_cast(t)->PointedType; + if (!pType->isStruct() || !static_cast(pType)->isNative || types.Find(static_cast(pType)->TypeName) >= types.Size()) + continue; + + if (base->isArray() && !base->isStaticArray()) + { + auto arr = (void**)ScriptVar(sym->Key, nullptr); + const size_t count = static_cast(base)->ElementCount; + for (size_t i = 0u; i < count; ++i) + arr[i] = nullptr; + } + else if (base->isDynArray()) + { + static_cast*>(ScriptVar(sym->Key, nullptr))->Clear(); + } + else if (base->isMap()) + { + if (static_cast(base)->BackingClass == PMap::MAP_I32_PTR) + static_cast*>(ScriptVar(sym->Key, nullptr))->Clear(); + else + static_cast*>(ScriptVar(sym->Key, nullptr))->Clear(); + } + else + { + PointerVar(sym->Key) = nullptr; + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + template static void MapPointerSubstitution(M *map, size_t &changed, DObject *old, DObject *notOld, const bool shouldSwap) { diff --git a/src/common/objects/dobject.h b/src/common/objects/dobject.h index 9925cb88e..3b414bdd9 100644 --- a/src/common/objects/dobject.h +++ b/src/common/objects/dobject.h @@ -245,6 +245,9 @@ public: template T*& PointerVar(FName field); inline int* IntArray(FName field); + // Make sure native data is wiped correctly since it has no read barriers. + void ClearNativePointerFields(const TArray& types); + // This is only needed for swapping out PlayerPawns and absolutely nothing else! virtual size_t PointerSubstitution (DObject *old, DObject *notOld, bool nullOnFail); diff --git a/src/common/utility/tarray.h b/src/common/utility/tarray.h index 3a6bd76e9..f1cfc4f7f 100644 --- a/src/common/utility/tarray.h +++ b/src/common/utility/tarray.h @@ -234,8 +234,8 @@ public: TArray (std::initializer_list list) { - Most = list.size; - Count = list.size; + Most = list.size(); + Count = list.size(); if (Count > 0) { diff --git a/src/g_level.cpp b/src/g_level.cpp index 9ea40c929..438d6ce29 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1646,6 +1646,9 @@ void FLevelLocals::StartTravel () inv->UnlinkFromWorld (nullptr); inv->UnlinkBehaviorsFromLevel(); inv->DeleteAttachedLights(); + tid = inv->tid; + inv->SetTID(0); + inv->tid = tid; } } } @@ -1751,7 +1754,7 @@ int FLevelLocals::FinishTravel () pawn->LinkBehaviorsToLevel(); pawn->ClearInterpolation(); pawn->ClearFOVInterpolation(); - const int tid = pawn->tid; // Save TID (actor isn't linked into the hash chain yet) + int tid = pawn->tid; // Save TID (actor isn't linked into the hash chain yet) pawn->tid = 0; // Reset TID pawn->SetTID(tid); // Set TID (and link actor into the hash chain) pawn->SetState(pawn->SpawnState); @@ -1763,6 +1766,9 @@ int FLevelLocals::FinishTravel () inv->LinkToWorld (nullptr); P_FindFloorCeiling(inv, FFCF_ONLYSPAWNPOS); inv->LinkBehaviorsToLevel(); + tid = inv->tid; + inv->tid = 0; + inv->SetTID(tid); IFVIRTUALPTRNAME(inv, NAME_Inventory, Travelled) { diff --git a/src/namedef_custom.h b/src/namedef_custom.h index a8ae6414c..87675346b 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -568,6 +568,13 @@ xx(Offsety) xx(Texturetop) xx(Texturebottom) xx(Texturemiddle) +xx(SectorPortal) +xx(LinePortal) +xx(Vertex) +xx(Side) +xx(Line) +xx(SecPlane) +xx(F3DFloor) xx(Sector) xx(Heightfloor) xx(Heightceiling) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index b698b3eec..070de05e4 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -293,9 +293,16 @@ void FLevelLocals::ClearLevelData(bool fullgc) auto it = GetThinkerIterator(NAME_None, STAT_TRAVELLING); for (AActor *actor = it.Next(); actor != nullptr; actor = it.Next()) { + actor->BlockingMobj = nullptr; actor->BlockingLine = actor->MovementBlockingLine = nullptr; actor->BlockingFloor = actor->BlockingCeiling = actor->Blocking3DFloor = nullptr; } + + // Make sure map data gets cleared appropriately so any leftover Objects aren't pointing + // towards anything invalid. + TArray fieldTypes = { NAME_SectorPortal, NAME_LinePortal, NAME_Vertex, NAME_Side, NAME_Line, NAME_SecPlane, NAME_F3DFloor, NAME_Sector }; + for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext) + probe->ClearNativePointerFields(fieldTypes); } interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level.