Bug
uart_write and generic_api_send_command acquire a buffer with y* but never release it. Several result-producing wrappers also omit PyBuffer_Release on success: get_device_status, get_hw_firmware_info, and get_dll_firmware_info. flash_accessory_firmware likewise leaves its parameter buffer exported.
Reviewed commit: 45167e09de6b886cd451a4245f842c8221349711 (current master on 2026-09-15).
Source: src/methods.cpp:5019; src/methods.cpp:5201; src/methods.cpp:3954
Reproduction
This reproducer uses a hardware-free DLL. Save this as mock.c:
#define API __declspec(dllexport)
typedef unsigned long long size_t;
API int icsneoUartWrite(void* h, int port, const void* data, size_t len, size_t* sent, unsigned char* flags) { *sent = len; return 1; }
API int icsneoGenericAPISendCommand(void* h, unsigned char a, unsigned char i, unsigned char f, void* data, unsigned int len, unsigned char* error) { *error = 0; return 1; }
API int icsneoGetDeviceStatus(void* h, void* status, size_t* size) { return 1; }
Build with LLVM on Windows x64:
clang-cl /nologo /c /GS- /Zl mock.c /Fomock.obj
lld-link /dll /noentry /nodefaultlib /out:mock.dll mock.obj
Then run:
import ics
import ctypes
from pathlib import Path
ics.override_library_name(str(Path('mock.dll').resolve()))
d = ics.PyNeoDeviceEx()
d._auto_handle_close = False
for func, args in [(ics.uart_write, (d, 0)), (ics.generic_api_send_command, (d, 0, 0, 0))]:
data = bytearray(b'abc')
func(*args, data)
try: data.extend(b'd')
except BufferError as e: print(func.__name__, e)
import weakref, gc
status = ics.get_device_status(d)
ref = weakref.ref(status)
del status
gc.collect()
print('status remains alive:', ref() is not None)
Observed / expected
Both successful writes leave BufferError: Existing exports of data: object cannot be re-sized. The returned status remains alive after deletion. The firmware-info getters were independently checked and also retain their results. Expected the write buffer to be resizable after the synchronous call and returned objects to be collectable.
Suggested fix and regression coverage
Use scoped Py_buffer ownership and release on every success/error/exception path. Check PyObject_GetBuffer return values before using the buffer. Add bytearray-resize and weak-reference collection tests for the affected wrappers.
Validation environment
Windows x64, CPython 3.14.5, extension rebuilt from the commit above. Hardware-dependent entry points below were exercised with a mock DLL, not a connected device. The existing 30 tests pass despite these findings.
Bug
uart_writeandgeneric_api_send_commandacquire a buffer withy*but never release it. Several result-producing wrappers also omit PyBuffer_Release on success: get_device_status, get_hw_firmware_info, and get_dll_firmware_info. flash_accessory_firmware likewise leaves its parameter buffer exported.Reviewed commit:
45167e09de6b886cd451a4245f842c8221349711(current master on 2026-09-15).Source: src/methods.cpp:5019; src/methods.cpp:5201; src/methods.cpp:3954
Reproduction
This reproducer uses a hardware-free DLL. Save this as
mock.c:Build with LLVM on Windows x64:
Then run:
Observed / expected
Both successful writes leave
BufferError: Existing exports of data: object cannot be re-sized. The returned status remains alive after deletion. The firmware-info getters were independently checked and also retain their results. Expected the write buffer to be resizable after the synchronous call and returned objects to be collectable.Suggested fix and regression coverage
Use scoped Py_buffer ownership and release on every success/error/exception path. Check PyObject_GetBuffer return values before using the buffer. Add bytearray-resize and weak-reference collection tests for the affected wrappers.
Validation environment
Windows x64, CPython 3.14.5, extension rebuilt from the commit above. Hardware-dependent entry points below were exercised with a mock DLL, not a connected device. The existing 30 tests pass despite these findings.