From b1827403129601685b7a633e3f4f547d517a4202 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Wed, 5 Aug 2026 07:58:48 -0700 Subject: [PATCH 1/3] Fix perform_fft ignoring the get_window failure and transforming uninitialized memory perform_fft allocates windowed_data with new double[data_len] and then calls get_window to fill it. It discards the return code. get_window returns INVALID_ARGUMENTS_ERROR without writing anything when window_function is not a valid WindowOperations value, so the very next loop multiplies the caller data into a buffer that was never initialized, and kiss_fftr transforms whatever bytes the allocator handed back. perform_fft then returns STATUS_OK, so the caller has no way to tell that the result is heap noise. This checks the code from get_window, frees windowed_data and returns the error. get_psd already checks the perform_fft return value, so the error now propagates the whole way up through get_psd_welch to get_band_power and get_custom_band_powers, and the language bindings raise instead of handing back a garbage spectrum. Valid window functions are unaffected. --- src/data_handler/data_handler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/data_handler/data_handler.cpp b/src/data_handler/data_handler.cpp index 106c614dd..7eeed1bfc 100644 --- a/src/data_handler/data_handler.cpp +++ b/src/data_handler/data_handler.cpp @@ -788,7 +788,12 @@ int perform_fft ( } double *windowed_data = new double[data_len]; - get_window (window_function, data_len, windowed_data); + int window_res = get_window (window_function, data_len, windowed_data); + if (window_res != (int)BrainFlowExitCodes::STATUS_OK) + { + delete[] windowed_data; + return window_res; + } for (int i = 0; i < data_len; i++) { windowed_data[i] *= data[i]; From 994fe687505ec9ae3320a06ae917567e358f56dd Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Thu, 6 Aug 2026 20:27:33 -0700 Subject: [PATCH 2/3] Add fft window error path regression test Asserts an invalid window enum returns INVALID_ARGUMENTS_ERROR and leaves both output buffers and the input untouched, and that a valid window still round-trips through fft and ifft. --- .../examples/tests/fft_window_error.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 python_package/examples/tests/fft_window_error.py diff --git a/python_package/examples/tests/fft_window_error.py b/python_package/examples/tests/fft_window_error.py new file mode 100644 index 000000000..aa9eedeac --- /dev/null +++ b/python_package/examples/tests/fft_window_error.py @@ -0,0 +1,46 @@ +import numpy as np + +from brainflow.data_filter import DataFilter, DataHandlerDLL, WindowOperations +from brainflow.exit_codes import BrainFlowError, BrainFlowExitCodes + + +def main(): + data_len = 64 + data = np.arange(data_len, dtype=np.float64) + + # Call the native entry point directly rather than DataFilter.perform_fft, because the + # wrapper allocates the output buffers itself and raises before returning them. Filling + # them with sentinels here is what makes "left untouched" observable. + original = np.copy(data) + real = np.full(data_len // 2 + 1, -12345.0) + imag = np.full(data_len // 2 + 1, -54321.0) + + invalid_window = 999 + res = DataHandlerDLL.get_instance().perform_fft(data, data_len, invalid_window, real, imag) + + assert res == BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value, res + # perform_fft used to ignore the get_window failure and transform the uninitialized + # window buffer, so both outputs were written with values derived from garbage. + assert np.all(real == -12345.0), real + assert np.all(imag == -54321.0), imag + assert np.array_equal(data, original), data + + # The same failure through the public wrapper, which turns the code into an exception. + try: + DataFilter.perform_fft(np.arange(data_len, dtype=np.float64), invalid_window) + except BrainFlowError as err: + assert err.exit_code == BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value, err.exit_code + else: + raise AssertionError('an invalid window function must not be accepted') + + # A valid window still round-trips, so the early return did not shadow the normal path. + restored = DataFilter.perform_ifft( + DataFilter.perform_fft(np.copy(original), WindowOperations.NO_WINDOW.value) + ) + assert np.allclose(restored, original), restored + + print('fft window error regression passed') + + +if __name__ == '__main__': + main() From 81821f98a53edf51ee5448db3667c8dc82cae1da Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Thu, 6 Aug 2026 20:37:22 -0700 Subject: [PATCH 3/3] Run the new regression test in unix CI --- .github/workflows/run_unix.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run_unix.yml b/.github/workflows/run_unix.yml index 557001dae..4a7a1d1b4 100644 --- a/.github/workflows/run_unix.yml +++ b/.github/workflows/run_unix.yml @@ -354,6 +354,8 @@ jobs: run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/transforms.py - name: Downsampling Python run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/downsampling.py + - name: FFT Window Error Python + run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/fft_window_error.py - name: ICA Python run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/ica.py - name: CSP Python