An address can outlive its object

Freeing memory does not clear every pointer that once referred to it. Copies of the address remain too. Old data may still be readable until another allocation reuses the storage, making an invalid use appear to work for a while. A use-after-free occurs when code uses an object after its lifetime has ended. The debugger’s ability to display bytes is different from the language-level validity of that access. Check who released the object and when, even if the contents look familiar.

Put creation, transfer, release, and use on a timeline

Record where an object is created, where its address is passed elsewhere, where its lifetime ends, and where it is used again. Container removal or object replacement can affect lifetime without an obvious free or delete beside the failing code. For callbacks that store an address, the eventual execution time matters more than registration time. Check whether an owner has already shut down while queued work still retains a reference. Looking at only the crash location often misses this relationship.

Stopping can change thread ordering

If release and use occur on different threads, a breakpoint can alter scheduling enough to hide the failure. A disappearing crash does not disprove the hypothesis. Combine reproduction work with logging or a suitable memory checker. DBG Desk currently shows the stopped thread’s stack, not every thread’s ownership relationships. Inspect shared access, reference management, locking, cancellation, and shutdown boundaries separately. Allocation and release traces from a checker become more useful when related to the debugger’s current stop.

Fix the lifetime rule

A non-null dangling pointer passes a null check. Clearing one variable after release does not update its copies. Decide who owns the object, what keeps it alive during asynchronous work, and how pending callbacks are handled at shutdown. Use the ownership mechanisms appropriate for the language and design, and avoid multiple independent release paths. Test fast cancellation, repeated creation and destruction, and cleanup after failures in addition to the normal path.

A late callback example

A background task may finish after a window object has been destroyed. Fast tasks succeed because delivery happens first; slow tasks expose the stale reference. Check whether cancellation guarantees completion or merely requests it, and whether already queued delivery can still run. Repeatedly opening and closing with a deliberately slow task can make this lifetime boundary easier to reproduce.