Skip to content

memory safety: prevent duplicate close and invalid pattern reads - #123

Open
KJ7LNW wants to merge 2 commits into
tmolteno:masterfrom
KJ7LNW:fix-double-free-null-dereference
Open

memory safety: prevent duplicate close and invalid pattern reads#123
KJ7LNW wants to merge 2 commits into
tmolteno:masterfrom
KJ7LNW:fix-double-free-null-dereference

Conversation

@KJ7LNW

@KJ7LNW KJ7LNW commented Jul 29, 2026

Copy link
Copy Markdown

Description

Copied plot cards shared a raw FILE*, causing multiple destructors to close the same stream. Average-only radiation patterns also emitted per-angle rows from arrays that mode intentionally leaves uninitialized.

This change gives copied plot cards shared ownership of their stream and suppresses per-angle output when the RP card requests average-only reporting, while preserving the heading and average summary.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Implementation Details

  • Store plot streams in std::shared_ptr<FILE> with fclose as the deleter.
  • Access the raw stream only at C standard I/O boundaries.
  • Name the RP average-only mode and omit its unpopulated per-angle rows.

Testing

  • CMake Debug build completed.
  • CTest passed necpp_unit and necpp_smoke_hertzian_dipole (2/2).
  • All three regression decks exited normally under GDB after the fix.

20-12.nec: plot-current stream ownership

  • Before: Copied plot cards retained the same raw stream, so result destruction called fclose more than once on it.
  • After: The deck exits normally, writes both plot files, and the final shared owner closes each stream once.
CM Currents and radiation patterns
CM PL demonstration 1
CE
GW 1 11 0 -.2375 0 0 .2375 0 .001
GE
FR 0 1 0 0 299.7925 1
EX 0 1 6 00 1 0
PL 1 2 0 m20-12-currents.dat
XQ
PL 3 2 0 3 m20-12-rp0.dat
RP 0 1 361 1000 90 0 1.00000 1.00000
EN

20-13.nec: near-field stream ownership

  • Before: The near-field result retained a copied raw stream, so teardown called fclose after another plot-card copy had closed it.
  • After: The deck exits normally, writes near-field plot output, and the final shared owner closes the stream once.
CM PL demonstration 1
CM Near fields
CE
GW 1 11 0 -.2375 0 0 .2375 0 .001
GE
FR 0 1 0 0 299.7925 1
EX 0 1 6 00 1 0
PL 2 1 4 0 m20-13-NF.dat
NE 0 11 1 1 -5.0 -5.0 0.0 1.0 1.0 1.0
NH 0 11 1 1 -5.0 -5.0 0.0 1.0 1.0 1.0
EN

15-9a.nec: average-only radiation pattern

  • Before: A=2 left per-angle arrays unpopulated, but report generation still iterated those rows and read an uninitialized polarization-sense index.
  • After: The deck exits normally, emits no per-angle rows, and retains the average power and solid-angle summary.
CM 1 wl above Average Ground
CM Copper #12 Dipole
CE
GW 1 41 0 -100 420 0 100 420 0.0404331
GS 0 0 .02540
GE 1 -1 0
GN 2 0 0 0 13 .005
EX 0 1 21 0 1 0
LD 5 1 1 41 5.8001E7
FR 0 1 0 0 28.5 1
RP 0 19 73 1002 -90 0 5 5
EN

Expected average-only summary:

AVERAGE POWER GAIN: 1.4811E+00
SOLID ANGLE USED IN AVERAGING: (2.0000)*PI STERADIANS

Eric Wheeler added 2 commits July 29, 2026 14:20
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<FILE> 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 <necpp@z.ewheeler.org>
…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 <necpp@z.ewheeler.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant