Summary
A heuristic scan of src/**/*.fpp finds that 64 of 223 distinct @:ALLOCATEd array names (~29%) are never @:DEALLOCATEd anywhere in the source tree. This contradicts the documented convention — "Every @:ALLOCATE(...) MUST have a matching @:DEALLOCATE(...) in finalization" — and currently makes it impossible to statically enforce allocate/deallocate pairing.
Why it matters
- Clean shutdown / leak tooling. Program-exit leaks add noise under valgrind /
compute-sanitizer / GPU memory tools, masking real leaks.
- GPU memory hygiene.
@:DEALLOCATE also emits GPU_EXIT_DATA(delete=...); skipping it leaves device allocations live until process teardown.
- Unblocks a static lint. A
lint_source.py check for an @:ALLOCATE with no matching @:DEALLOCATE is impossible today — it would flag these 64 as false positives. Once everything is paired (or explicitly exempted), a precheck lint could catch a forgotten deallocation in milliseconds instead of via review or a GPU run. This is exactly the class of bug that is otherwise invisible locally.
Methodology / caveat
Names were extracted by regex from @:ALLOCATE(...) / @:DEALLOCATE(...) calls (base identifier, %-members kept, &-continuations joined), then set-differenced across all of src. This is approximate — some entries may already be freed via a containing object (e.g. a vector_field's component fields), reallocated in place, or be intentionally program-lifetime. Each should be either deallocated in the relevant s_finalize_*_module or explicitly documented as exempt.
Per-file breakdown (allocated here, deallocated nowhere in src)
src/common/m_boundary_common.fpp: bc_buffers
src/common/m_helper.fpp: Re_trans_T, pb0, weight
src/common/m_model.fpp: gpu_boundary_edge_count, gpu_boundary_v, gpu_ntrs, gpu_total_vertices, gpu_trs_n, gpu_trs_v
src/common/m_mpi_common.fpp: buff_send
src/common/m_variables_conversion.fpp: Res_vc
src/post_process/m_start_up.fpp: En, En_real, data_cmplx, data_cmplx_y, data_cmplx_z, data_in, data_out
src/simulation/m_acoustic_src.fpp: E_src, loc_acoustic, mass_src, mom_src, source_spatials, source_spatials_num_points
src/simulation/m_bubbles_EE.fpp: bub_adv_src, bub_m_src, bub_p_src, bub_r_src, bub_v_src, divu%sf, ms, ps, rs, vs
src/simulation/m_derived_variables.fpp: accel_mag, fd_coeff_x, fd_coeff_y, fd_coeff_z, x_accel, y_accel, z_accel
src/simulation/m_global_parameters.fpp: ptil
src/simulation/m_hyperelastic.fpp: Gs_hyper
src/simulation/m_ibm.fpp: ghost_points, models
src/simulation/m_qbmm.fpp: bubmoms, momrhs
src/simulation/m_rhs.fpp: alf_sum%sf, blkmod1, mom_3d, mom_sp, qL_prim, qR_prim
src/simulation/m_riemann_solvers.fpp: Gs_rs, Res_gs
src/simulation/m_time_steppers.fpp: bc_type, max_dt, mv_ts, pb_ts, q_T_sf%sf, rhs_mv, rhs_pb, rk_coef
Proposed resolution
- Audit each name above: add a matching
@:DEALLOCATE in the module's s_finalize_*_module (conditional allocation → conditional deallocation), or annotate the genuine program-lifetime exceptions.
- Add a
lint_source.py check that flags an @:ALLOCATEd name with no corresponding @:DEALLOCATE, wired into precheck, so the invariant can't regress.
Summary
A heuristic scan of
src/**/*.fppfinds that 64 of 223 distinct@:ALLOCATEd array names (~29%) are never@:DEALLOCATEd anywhere in the source tree. This contradicts the documented convention — "Every@:ALLOCATE(...)MUST have a matching@:DEALLOCATE(...)in finalization" — and currently makes it impossible to statically enforce allocate/deallocate pairing.Why it matters
compute-sanitizer/ GPU memory tools, masking real leaks.@:DEALLOCATEalso emitsGPU_EXIT_DATA(delete=...); skipping it leaves device allocations live until process teardown.lint_source.pycheck for an@:ALLOCATEwith no matching@:DEALLOCATEis impossible today — it would flag these 64 as false positives. Once everything is paired (or explicitly exempted), aprechecklint could catch a forgotten deallocation in milliseconds instead of via review or a GPU run. This is exactly the class of bug that is otherwise invisible locally.Methodology / caveat
Names were extracted by regex from
@:ALLOCATE(...)/@:DEALLOCATE(...)calls (base identifier,%-members kept,&-continuations joined), then set-differenced across all ofsrc. This is approximate — some entries may already be freed via a containing object (e.g. avector_field's component fields), reallocated in place, or be intentionally program-lifetime. Each should be either deallocated in the relevants_finalize_*_moduleor explicitly documented as exempt.Per-file breakdown (allocated here, deallocated nowhere in
src)src/common/m_boundary_common.fpp:bc_bufferssrc/common/m_helper.fpp:Re_trans_T,pb0,weightsrc/common/m_model.fpp:gpu_boundary_edge_count,gpu_boundary_v,gpu_ntrs,gpu_total_vertices,gpu_trs_n,gpu_trs_vsrc/common/m_mpi_common.fpp:buff_sendsrc/common/m_variables_conversion.fpp:Res_vcsrc/post_process/m_start_up.fpp:En,En_real,data_cmplx,data_cmplx_y,data_cmplx_z,data_in,data_outsrc/simulation/m_acoustic_src.fpp:E_src,loc_acoustic,mass_src,mom_src,source_spatials,source_spatials_num_pointssrc/simulation/m_bubbles_EE.fpp:bub_adv_src,bub_m_src,bub_p_src,bub_r_src,bub_v_src,divu%sf,ms,ps,rs,vssrc/simulation/m_derived_variables.fpp:accel_mag,fd_coeff_x,fd_coeff_y,fd_coeff_z,x_accel,y_accel,z_accelsrc/simulation/m_global_parameters.fpp:ptilsrc/simulation/m_hyperelastic.fpp:Gs_hypersrc/simulation/m_ibm.fpp:ghost_points,modelssrc/simulation/m_qbmm.fpp:bubmoms,momrhssrc/simulation/m_rhs.fpp:alf_sum%sf,blkmod1,mom_3d,mom_sp,qL_prim,qR_primsrc/simulation/m_riemann_solvers.fpp:Gs_rs,Res_gssrc/simulation/m_time_steppers.fpp:bc_type,max_dt,mv_ts,pb_ts,q_T_sf%sf,rhs_mv,rhs_pb,rk_coefProposed resolution
@:DEALLOCATEin the module'ss_finalize_*_module(conditional allocation → conditional deallocation), or annotate the genuine program-lifetime exceptions.lint_source.pycheck that flags an@:ALLOCATEd name with no corresponding@:DEALLOCATE, wired intoprecheck, so the invariant can't regress.