Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/NFcore/moleculeType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
15 changes: 12 additions & 3 deletions src/NFcore/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading