Skip to content

Set the caller's pointer to NULL in the destroy functions - #2386

Open
karpovantonme wants to merge 2 commits into
DLR-AMR:mainfrom
karpovantonme:fix/destroy-nulls-caller-pointer
Open

Set the caller's pointer to NULL in the destroy functions#2386
karpovantonme wants to merge 2 commits into
DLR-AMR:mainfrom
karpovantonme:fix/destroy-nulls-caller-pointer

Conversation

@karpovantonme

@karpovantonme karpovantonme commented Aug 3, 2026

Copy link
Copy Markdown

Closes #2387

t8_stash_destroy and t8_cmesh_trees_destroy both take a pointer to the caller's handle so that they can clear it after freeing, and both headers say so:

/** Free all memory associated in a stash structure.
 * \param [in,out]  pstash  A pointer to the stash to be destroyed.
 *                  The pointer is set to NULL after the function call.
 */
/** \param [in,out]  trees The tree structure to be destroyed. Set to NULL on output. */

Neither does it. Both assign to the parameter instead of dereferencing it:

T8_FREE (stash);
pstash = NULL;      /* clears the local copy, not the caller's handle */
T8_FREE (trees);
ptrees = nullptr;

So the caller is left holding a dangling pointer, and the whole reason for taking a double pointer is lost.

Why this is not only cosmetic

The callers are written as if the contract held.

t8_cmesh_commit.cxx:626 guards the destroy on the handle being non-NULL:

if (cmesh->stash != nullptr) {
  t8_stash_destroy (&cmesh->stash);
}

and line 145 of the same file reads through the same guard:

if (cmesh->stash != nullptr && cmesh->stash->classes.elem_count > 0) {

Since cmesh->stash is never set to NULL, that second check passes on a freed pointer and dereferences it. t8_cmesh_helpers.cxx:263-264 and t8_cmesh_readmshfile.cxx:1596 read cmesh->stash->... as well.

t8_cmesh_bcast (t8_cmesh.cxx:792) is the clearest case: it destroys the stash and sets cmesh_out->committed = 1, and the cmesh keeps living with a dangling stash.

In the teardown path (t8_cmesh_destroy) the dangling pointer is harmless, since the cmesh is freed right after. It is the commit and bcast paths that matter.

The change

Two characters, one in each function: pstash = NULL becomes *pstash = NULL, ptrees = nullptr becomes *ptrees = nullptr. Nothing else touched.

I have not added a regression test. The natural one would assert that cmesh->stash == NULL after commit, but I did not want to guess where it belongs in your test layout. Happy to add it if you point me at the right file.

Found with a cppcheck --enable=warning sweep over src/ (uselessAssignmentPtrArg at t8_cmesh_stash.c:70 and t8_cmesh_trees.cxx:1295), then traced through the callers by hand.

Author file added as doc/author_karpov.txt per CONTRIBUTING, and both commits are signed off.

t8_stash_destroy and t8_cmesh_trees_destroy take a pointer to the
caller's handle so they can clear it after freeing, and the header
documents exactly that: "The pointer is set to NULL after the function
call." Both assign to the parameter itself instead of dereferencing it,
so the caller keeps a dangling pointer.

This is not only theoretical. t8_cmesh_commit checks
`cmesh->stash != nullptr` before destroying it, and again at
t8_cmesh_commit.cxx:145 before reading `cmesh->stash->classes`, so the
guard is written against a NULL that never arrives. t8_cmesh_bcast frees
the stash and marks the cmesh committed while the cmesh stays alive.

Signed-off-by: Anton Karpov <karpovantonme@gmail.com>
Signed-off-by: Anton Karpov <karpovantonme@gmail.com>
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.

Set the caller's pointer to NULL in the destroy functions

1 participant