copyNumpyArray() causes heap corruption crash when Script TOP reuses numpy array buffers across cooks

Hello! I ran into a bug that Claude 4.6 is helping me describe and work around. Please let me know if you find this kind of reporting useful.

TouchDesigner Version: 2023.12370 (macOS, ARM64)

OS: macOS 26.4 (25E5233c), MacBookPro18,2 (M1 Max)

Summary:

A Script TOP that reuses a cached numpy array across multiple cook() calls — mutating it in-place and passing it to
scriptOp.copyNumpyArray() each frame — causes TouchDesigner to crash with a malloc heap corruption error. The crash occurs either
during normal operation or during shutdown ([NSApplication terminate:]), with the following signature:

___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED

The crash originates in libDM.dylib during memory cleanup (__cxa_finalize_ranges → exit).

Steps to Reproduce:

  1. Create a Script TOP with a callbacks DAT containing roughly this pattern:

import numpy as np

Array allocated once, reused every cook

_cached_arr = np.zeros((100, 100, 4), dtype=np.float32)

def cook(scriptOp):
_cached_arr[:, :, 0] = 0.5 # mutate in place
_cached_arr[:, :, 3] = 1.0
scriptOp.copyNumpyArray(_cached_arr) # pass same buffer each frame

  1. Let the Script TOP cook for some number of frames (may happen quickly or after several minutes).
  2. TouchDesigner either crashes during operation or crashes on quit.

Expected Behavior:

copyNumpyArray() should either:

  • Copy the array data internally (so the caller is free to reuse/mutate the source array), or
  • Document that the caller must not reuse or mutate the array after passing it to copyNumpyArray()

Actual Behavior:

TouchDesigner appears to hold an internal reference to the numpy array’s underlying memory buffer after copyNumpyArray() is called.
When the Script TOP cooks again and the same array is mutated and passed again, this creates a conflict — the old buffer reference
becomes invalid or is double-freed during cleanup, leading to heap corruption.

Workaround:

Allocate a fresh numpy array on every cook() call instead of reusing a cached one:

import numpy as np

def cook(scriptOp):
arr = np.zeros((100, 100, 4), dtype=np.float32) # fresh allocation each frame
arr[:, :, 0] = 0.5
arr[:, :, 3] = 1.0
scriptOp.copyNumpyArray(arr)

Alternatively, call .copy() on a cached template before passing it:

scriptOp.copyNumpyArray(cached_arr.copy())

Additional Context:

This was encountered while building a Script TOP that renders an LED pixel map preview at 180x110 resolution, cooking every frame.
The Script TOP used cached numpy arrays for both the output image and an intermediate color lookup table to maximize performance. The
crash was intermittent — sometimes it would crash TD immediately, other times it would crash only on quit. Once the arrays were
changed to fresh allocations per cook, the crash stopped entirely.

Crash signature from report:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib __pthread_kill + 8
1 libsystem_pthread.dylib pthread_kill + 296
2 libsystem_c.dylib abort + 148
3 libsystem_malloc.dylib malloc_vreport + 892
4 libsystem_malloc.dylib malloc_report + 64
5 libsystem_malloc.dylib ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED + 76
6 libDM.dylib 0x1079ec000 + 933188
7 libDM.dylib 0x1079ec000 + 953180
8 libSI.dylib 0x107878000 + 371520
9 libsystem_c.dylib __cxa_finalize_ranges + 416
10 libsystem_c.dylib exit + 44
11 AppKit -[NSApplication terminate:] + 2004

Hi @hncc

We are unable to reproduce so far.

Can you please share a sample file showcasing the issue? or the project.

Thanks,
Michel

@hncc While we’d still like to see your example/sample project to look into this, we also recommend you update to the latest 2023 build available, 2023.12600. There were 2 updates released after the build you are using (2023.12370) which account for dozens of bug fixes.