From 51f57cef001d95d7c6ff7ffecc12663558fc7782 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Wed, 29 Jul 2026 14:20:58 -0700 Subject: [PATCH 1/2] plot_card: share plot stream ownership via shared_ptr c_plot_card is copied when a PL card replaces nec_context::plot_card and when nec_radiation_pattern captures the active card, giving the plot stream shared lifetime across value copies. The prior raw FILE* member let each copy's destructor call fclose independently, producing a duplicate fclose reached while destroying results for decks with PL cards. - store the FILE handle in std::shared_ptr with fclose as the deleter, so temporary assignment, context storage, and radiation-pattern storage retain the same stream and only the final reference closes it - obtain the raw handle with get() only at C standard I/O API call sites - default the destructor since the shared_ptr deleter now owns the close Signed-off-by: Eric Wheeler --- src/c_plot_card.cpp | 36 +++++++++++++++--------------------- src/c_plot_card.h | 3 ++- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/c_plot_card.cpp b/src/c_plot_card.cpp index 06487f3..42b4afd 100644 --- a/src/c_plot_card.cpp +++ b/src/c_plot_card.cpp @@ -30,43 +30,37 @@ c_plot_card::c_plot_card() p2 = 0; p3 = 0; p4 = 0; - - plot_fp = NULL; } - + c_plot_card::c_plot_card(const c_plot_card& p) { p1 = p.p1; p2 = p.p2; p3 = p.p3; p4 = p.p4; - + plot_fp = p.plot_fp; } - + c_plot_card::c_plot_card(int itmp1, int itmp2, int itmp3, int itmp4, string& filename) { p1 = itmp1; p2 = itmp2; p3 = itmp3; p4 = itmp4; - - plot_fp = NULL; - + /* Open plot file */ - if ( (plot_fp = fopen(filename.c_str(), "w")) == NULL ) + FILE* file = fopen(filename.c_str(), "w"); + if (file == NULL) { throw 100; } + plot_fp = std::shared_ptr(file, fclose); } -c_plot_card::~c_plot_card() -{ - if ( plot_fp != NULL ) - fclose( plot_fp ); -} +c_plot_card::~c_plot_card() = default; -bool c_plot_card::is_valid() const { return (NULL != plot_fp); } +bool c_plot_card::is_valid() const { return (nullptr != plot_fp); } bool c_plot_card::storing() const { return (p1 != 0); } bool c_plot_card::currents() const { return (p1 == 1); } @@ -85,23 +79,23 @@ void c_plot_card::set_plot_real_imag_currents() void c_plot_card::plot_endl() const { - if (NULL == plot_fp) + if (nullptr == plot_fp) throw 100; - fprintf( plot_fp, "\n"); + fprintf( plot_fp.get(), "\n"); } void c_plot_card::plot_double(nec_float x) const { - if (NULL == plot_fp) + if (nullptr == plot_fp) throw 100; - fprintf( plot_fp, "%12.4E ", x ); + fprintf( plot_fp.get(), "%12.4E ", x ); } void c_plot_card::plot_complex(nec_complex x) const { - if (NULL == plot_fp) + if (nullptr == plot_fp) throw 100; switch( p2 ) @@ -156,7 +150,7 @@ void c_plot_card::plot_segments(int i, if (false == near_field()) return; - fprintf( plot_fp, "%12.4E %12.4E %12.4E " + fprintf( plot_fp.get(), "%12.4E %12.4E %12.4E " "%12.4E %12.4E %12.4E %12.4E %5d %5d %5d\n", x[i],y[i],z[i],si[i],xw2,yw2,bi[i],icon1[i],i+1,icon2[i] ); } diff --git a/src/c_plot_card.h b/src/c_plot_card.h index b452e18..7ab5988 100644 --- a/src/c_plot_card.h +++ b/src/c_plot_card.h @@ -19,6 +19,7 @@ #include "math_util.h" +#include #include #include @@ -87,5 +88,5 @@ class c_plot_card nec_float g_vert, nec_float g_horiz, nec_float g_tot); private: int p1, p2, p3, p4; - FILE* plot_fp; + std::shared_ptr plot_fp; }; From 4935c70742347c7fa1c6ee0accc20fd178df2a20 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Wed, 29 Jul 2026 14:21:29 -0700 Subject: [PATCH 2/2] fix: skip average-only radiation-pattern rows to avoid uninitialized read The RP card XNDA field selects power averaging via its fourth digit. Mode A=2 computes only the average power gain and intentionally leaves per-angle presentation arrays unpopulated, but write_to_file_aux() still iterated the full phi range and read those arrays. - add named constant RP_POWER_AVERAGE_ONLY for the A=2 magic value - bound the per-angle output loop to zero iterations when A=2, so the heading and average print without reading an uninitialized polarization-sense value - replace the prior inline magic-number comparison with the named constant for the average-only guard in analyze() Signed-off-by: Eric Wheeler --- src/nec_radiation_pattern.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nec_radiation_pattern.cpp b/src/nec_radiation_pattern.cpp index b9aa9f9..9604d37 100644 --- a/src/nec_radiation_pattern.cpp +++ b/src/nec_radiation_pattern.cpp @@ -21,6 +21,10 @@ #include "c_geometry.h" #include "nec_exception.h" +namespace { +constexpr int RP_POWER_AVERAGE_ONLY = 2; +} + int nec_radiation_pattern::get_index(int theta_index, int phi_index) const { if (theta_index >= n_theta) throw nec_exception("nec_radiation_pattern: Theta index too large"); @@ -131,7 +135,7 @@ void nec_radiation_pattern::write_to_file_aux(ostream& os) nec_float phi = m_phi_start- delta_phi; - for (int kph = 0; kph < n_phi; kph++ ) { + for (int kph = 0; (kph < n_phi) && (m_rp_power_average != RP_POWER_AVERAGE_ONLY); kph++ ) { phi += delta_phi; nec_float thet= m_theta_start- delta_theta; @@ -383,7 +387,7 @@ void nec_radiation_pattern::analyze(nec_context* m_context) da *=.5; pint += tstor1 * da; - if ( m_rp_power_average == 2) // do not print the power gain values (just compute the average) + if ( m_rp_power_average == RP_POWER_AVERAGE_ONLY) // do not print the power gain values (just compute the average) continue; }