From 901da640532954dda89e99b0c2471ece288c897d Mon Sep 17 00:00:00 2001 From: Bill Hlavacek Date: Sat, 23 May 2026 23:04:12 -0600 Subject: [PATCH] bngsim: fix reactant-selector teardown double-free in TransformationSet TransformationSet::~TransformationSet() deleted every TemplateMolecule held in each ReactantFilter's parsedTemplates map. Those templates are produced by NFinput::readPattern() while parsing include_reactants()/exclude_reactants() and are registered with their MoleculeType, which already frees them when System deletes all MoleculeTypes on teardown. Deleting them again in the TransformationSet destructor is a second free of the same objects. The double-free fires intermittently when a single selector-bearing model is torn down and deterministically once two selector-bearing simulations run in one process (SIGABRT from the allocator, or SIGSEGV). Library consumers that create many NFsim sessions per process (e.g. parameter fitting) crash on any model that uses reactant selectors. The selector enforcement itself (upstream PR #23) is unaffected: removing the delete only stops the redundant free; MoleculeType remains the sole owner. Reported and validated in PyBNF-Private#60. Candidate to push upstream. --- .../transformations/transformationSet.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/NFreactions/transformations/transformationSet.cpp b/src/NFreactions/transformations/transformationSet.cpp index c1356540..08c7c207 100644 --- a/src/NFreactions/transformations/transformationSet.cpp +++ b/src/NFreactions/transformations/transformationSet.cpp @@ -99,13 +99,15 @@ TransformationSet::~TransformationSet() delete t; } - for (auto &rf : reactantFilters) { - // Clean up all templates generated for this filter pattern - for (auto &kv : rf.parsedTemplates) { - delete kv.second; - } - // Note: we don't delete rf.pattern directly since it is one of the parsedTemplates and handled above - } + // Do NOT delete rf.parsedTemplates here. Every TemplateMolecule produced by + // readPattern() for a reactant selector is registered with its MoleculeType + // and freed when System deletes all MoleculeTypes (see system.cpp, + // "Delete all MoleculeTypes (which deletes all molecules and templates)"). + // Deleting them here is a second free of the same objects, which double-frees + // across the System teardown — intermittently on a single selector session, + // deterministically once two selector-bearing sessions share a process + // (SIGABRT/SIGSEGV). rf.pattern is one of these templates, so it must not be + // deleted here either. reactantFilters.clear(); delete [] transformations;