Summary
A model with axisymmetric elements (CAX*) and a node-to-surface contact pair aborts (SIGABRT, exit status 134, no message) at the end of the first increment that writes results to the .frd file, when the step requests both
*EL FILEwithSorERR(section 7.52 of the 2.23 manual: “Selection of S automatically triggers output of the error estimator ERR, unless NOE is selected after S”), and*CONTACT FILEwithCDISorCSTR.
Each request on its own runs, and so do *CONTACT PRINT instead of *CONTACT FILE, S, NOE, a SURFACE TO SURFACE pair, and the same model with CPE4 elements. The abort comes from a double free in frdcyc.c, the .frd writer that ccx uses for axisymmetric models.
Version
ccx 2.23 (macOS arm64) and current master (537aa81, 2 Nov 2026).
Already reported or fixed?
Not reported: I found no GitHub issue or pull request in Dhondtguido/CalculiX about frdcyc.c or about contact output of axisymmetric models, and no matching calculix.discourse.group topic title (searched 3 Nov 2026). Not fixed: frdcyc.c lines 150-157 and 440-446 are unchanged on master.
Related: on 2.23 the same abort also occurs without any contact definition (one CAX4 element with *EL FILE S and *CONTACT FILE CDIS). Master no longer shows that case, since noelfiles.f (79de9de) removes a contact output request when no contact pair is defined. That is a separate path; the deck below has a pair.
Also related: Fix: SFREE macro should set pointer to NULL after freeing · Issue #300 · Dhondtguido/CalculiX · GitHub (open) proposes that SFREE set the pointer to NULL. With that change the second SFREE(stit) here would free a NULL pointer and the abort would disappear, but the first allocation would still leak; the fault is the double allocation in frdcyc.c.
Minimal input
cax_cont.inp:
** Two CAX4 rings (r = 1..2) stacked at z = 1 with a node-to-surface
** contact pair; the upper one is pushed down 0.001 at its top.
** Output: stresses (S) and contact output (CDIS) in the .frd file.
*NODE, NSET=NALL
1, 1., 0.
2, 2., 0.
3, 2., 1.
4, 1., 1.
5, 1., 1.
6, 2., 1.
7, 2., 2.
8, 1., 2.
*ELEMENT, TYPE=CAX4, ELSET=LOW
1, 1, 2, 3, 4
*ELEMENT, TYPE=CAX4, ELSET=UP
2, 5, 6, 7, 8
*NSET, NSET=BOT
1, 2
*NSET, NSET=TOP
7, 8
*MATERIAL, NAME=M
*ELASTIC
1000., 0.3
*SOLID SECTION, ELSET=LOW, MATERIAL=M
*SOLID SECTION, ELSET=UP, MATERIAL=M
*SURFACE, NAME=LTOP
LOW, S3
*SURFACE, NAME=UBOT
UP, S1
*SURFACE INTERACTION, NAME=SI
*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR
1.E5
*CONTACT PAIR, INTERACTION=SI, TYPE=NODE TO SURFACE
UBOT, LTOP
*BOUNDARY
BOT, 1, 2
TOP, 1, 1
*STEP
*STATIC
1., 1.
*BOUNDARY
TOP, 2, 2, -0.001
*EL FILE
S
*CONTACT FILE
CDIS
*END STEP
How to run
ccx cax_cont
Expected
“Job finished” and a complete .frd file with STRESS, CONTACT and ERROR blocks.
Actual
ccx 2.23 and master: SIGABRT (exit status 134) right after the increment’s output is written. No *ERROR, no “Job finished”. The .frd file holds the STRESS, CONTACT and ERROR blocks, but not its closing 9999 line. A backtrace (master build with a signal handler) shows the abort in the macOS allocator (free_medium_botch), reached through u_free() from frdcyc(), called from nonlingeo().
| variant (one change to the deck) | 2.23 | master |
|---|---|---|
as above (S + CDIS) |
SIGABRT | SIGABRT |
ERR instead of S |
SIGABRT | SIGABRT |
S only (no *CONTACT FILE) |
runs | runs |
E instead of S |
runs | runs |
S, NOE instead of S |
runs | runs |
*CONTACT PRINT instead of *CONTACT FILE |
runs | runs |
TYPE=SURFACE TO SURFACE |
runs | runs |
| CPE4 (thickness 1.) instead of CAX4 | runs | runs |
Where it goes wrong
An axisymmetric section sets up a cyclic symmetry for the 2° segment (solidsections.f line 526, mcs=1), so nonlingeo.c writes the results through frdcyc(). src/frdcyc.c (master) allocates the work array stit once for the error estimator and once more for node-to-surface contact output:
if((strcmp1(&filab[1044],"ZZS ")==0)||(strcmp1(&filab[1044],"ERR ")==0))
NNEW(stit,double,6*mi[0]**ne*ngraph); /* line 151 */
if(strcmp1(&filab[2175],"CONT")==0){
if(*mortar==1){
NNEW(cdnt,double,6**nk*ngraph);
}else{
NNEW(stit,double,6*mi[0]**ne*ngraph); /* line 156 */
}
}
and frees it once for each request:
if((strcmp1(&filab[1044],"ZZS ")==0)||(strcmp1(&filab[1044],"ERR ")==0)) SFREE(stit); /* line 440 */
if(strcmp1(&filab[2175],"CONT")==0){
if(*mortar==1){
SFREE(cdnt);
}else{
SFREE(stit); /* line 445 */
}
}
With ERR (or ZZS) and node-to-surface contact output both requested, the first allocation leaks and the second pointer is freed twice. Both requests fill stit with the same copy of sti (lines 270 and 275), so one array serves both. Face-to-face penalty contact (mortar=1) uses cdnt instead, which is why it runs. frdcyc() is also the writer for *CYCLIC SYMMETRY MODEL decks, which should hit the same double free with node-to-surface contact output and ERR; I did not test that.
With CCX_LOG_ALLOC=1, master logs ALLOCATION of variable stit, file frdcyc.c, line=151 and line=156 (two different addresses), then FREEING of variable stit, file frdcyc.c, line=440 with the second address, and aborts in the next free (line 445).
With the allocation at line 156 skipped when stit is already allocated, and the second free skipped accordingly, the deck runs on master and writes all three blocks.
Fix
Allocate and free stit once when either request needs it, e.g.
if((strcmp1(&filab[1044],"ZZS ")==0)||(strcmp1(&filab[1044],"ERR ")==0)||
((strcmp1(&filab[2175],"CONT")==0)&&(*mortar!=1)))
NNEW(stit,double,6*mi[0]**ne*ngraph);
with the same condition for the copy and for SFREE(stit).
Workaround: S, NOE on the *EL FILE card (no error estimator), or *CONTACT PRINT for the contact output.