diff --git a/src/NFcore/moleculeType.cpp b/src/NFcore/moleculeType.cpp index 763de2a4..93249ec5 100644 --- a/src/NFcore/moleculeType.cpp +++ b/src/NFcore/moleculeType.cpp @@ -394,12 +394,19 @@ void MoleculeType::removeMoleculeFromRunningSystem(Molecule *&m) void MoleculeType::removeAllMolecules() { - // Iterate through all molecules and remove them - // We need to loop backwards because remove() removes by swapping with the last element + // Iterate through all molecules and remove them. Loop backwards because + // remove() removes by swapping with the last element. + // + // Do NOT delete the Molecule objects here: mList is a fixed-capacity pool + // that owns every Molecule across [0, capacity) and recycles them on + // genDefaultMolecule()/create(). remove() only unbinds, drops the molecule + // from observables/reactions, marks it dead, and decrements the live count + // — the object stays in the pool for reuse. Deleting it leaves a dangling + // pointer in the pool (use-after-free on the next create()) and a double + // free in ~MoleculeList(). This is the crash behind resetConcentrations(). for (int m = mList->size() - 1; m >= 0; m--) { Molecule *mol = mList->at(m); removeMoleculeFromRunningSystem(mol); - delete mol; // Free the memory to prevent leaks } } diff --git a/src/NFcore/system.cpp b/src/NFcore/system.cpp index 097232c5..4200cba0 100644 --- a/src/NFcore/system.cpp +++ b/src/NFcore/system.cpp @@ -1438,13 +1438,22 @@ void System::updateAllReactionPropensities() { void System::destroyAllMolecules() { invalidateStepToCache(); - // For each MoleculeType, remove all molecules + // For each MoleculeType, remove all molecules. removeAllMolecules() unbinds + // every molecule and decrements the per-type live count to zero, but leaves + // the Molecule objects in their MoleculeList pools for reuse (see the note + // there). Unbinding routes through Complex::updateComplexMembership(), which + // splits each former complex back into singletons, so the pooled molecules + // stay paired with valid, in-range complex IDs. for (auto molTypeIter = allMoleculeTypes.begin(); molTypeIter != allMoleculeTypes.end(); ++molTypeIter) { (*molTypeIter)->removeAllMolecules(); } - // Clear all complexes from the list - allComplexes.clearAllComplexes(); + // Deliberately do NOT clearAllComplexes() here. The Complex objects are + // referenced by the recycled pool molecules via ID_complex; deleting them + // would leave those molecules pointing at freed/out-of-range complexes the + // next time genDefaultMolecule() hands them back during restore. The complex + // list and its available-complex queue stay internally consistent across the + // destroy/recreate cycle on their own. // Reset all observable counts for (auto obsIter = obsToOutput.begin(); obsIter != obsToOutput.end(); ++obsIter) {