Skip to content

Enhancement-567: an initialisation file no longer kills the run at start-up — inp_source hands com_source a copy it may free (bug hunt F6)

Choose a tag to compare

an initialisation file no longer kills the run at start-up — inp_source hands com_source a copy it may free (bug hunt F6)

Scope: finding F6 of the
2026-09-06 KLU/Sparse bug hunt,
the one that was not a solver defect: inp_source() in src/frontend/inp.c. ngspice
only.

Suites: new initfile_examples (5 checks per solver,
both solvers); Enhancement-558's rawfstring and paramgiven pass; solvercore,
ctrlnode pass; full sweep 466 of 466 on both solvers. The bug-hunt write-up's F6
section is rewritten with the real cause.

What was wrong

Every .spiceinit or spice.rc that ngspice found — in the deck's directory, in the
current directory, or in $HOME — ended the run before the first line of output:

ngspice(...) malloc: *** error for object 0x...: pointer being freed was not allocated

exit 134, under either solver, whatever the file contained (a single set foo=1 was
enough) and whatever the deck was. The hunt met it as an XSPICE deck that "aborted on
exit", because the failing decks happened to live in a scratch directory holding a
.spiceinit written to load the code models, and the variants that "worked" had been
written elsewhere; guard-malloc placed nothing, and under MallocStackLogging the double
free went unnoticed and the deck ran. Rebuilding main.o with line information put the
bad free at read_initialisation_file() in main.c, called at start-up.

The mechanism is a one-day-old regression. inp_source(file) builds a stack wordlist
whose single word is borrowed from its caller — its own comment says "nothing in it
should be freed" — and calls com_source(). Enhancement-558 (2026-09-05, hunt F10) made
com_source() unquote every word it is given by freeing the word and installing the
unquoted copy. read_initialisation_file() passes its tprintf'd path, com_source()
frees it, the caller frees it again. The other callers of inp_source() — tclspice,
sharedspice, cpitf's SPICE_SCRIPTS handling — pass string literals or their own
buffers and were freed underneath them the same way.

What changed

inp_source() copies the file name onto the heap before calling com_source() and
frees whatever word com_source() leaves in the list afterwards — the original if
unquoting left it alone, the unquoted copy if not. Nothing else moves: com_source()
still unquotes (E-558's behaviour is kept, its suites pass), the borrowed-word
convention is simply no longer relied upon. Nutmeg's nutinp_source()/nutcom_source()
pair does not unquote and needed nothing.

Verification

check result
.spiceinit with set foo=1 beside a trivial RC deck; in the current directory; in $HOME (HOME= override) exit 134 before (the version12 binary), exit 0 and v(out) = 1 after, all three, both solvers
the F6 deck (two XSPICE code models, op then ac) with the analog library loaded from a .spiceinit beside it exit 0, v(mid) = 0.5,
the F6 deck under guard-malloc clean
rawfstring_examples, paramgiven_examples (Enhancement-558) pass on both solvers
initfile_examples; full sweep 5 / 5 both solvers; 466 of 466

Write-up: enhancements_doc/Enhancement-567.md.