diff --git a/src/whisper.cpp b/src/whisper.cpp index 89146e2e4..a2dcfc9aa 100644 --- a/src/whisper.cpp +++ b/src/whisper.cpp @@ -412,9 +412,12 @@ static const std::map g_aheads { static std::vector get_alignment_heads_by_layer(const whisper_context_params & cparams, int il, int32_t n_text_layer, int32_t n_head); struct whisper_mel { - int n_len; - int n_len_org; - int n_mel; + // Default-initialized so a freshly allocated whisper_state whose mel was never + // computed (e.g. whisper_full called with n_samples == 0) reads as "0 frames" + // instead of indeterminate garbage that can drive a NULL read in the encoder. + int n_len = 0; + int n_len_org = 0; + int n_mel = 0; std::vector data; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aecc6f3b2..169692eed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,6 +96,16 @@ target_link_libraries(${UTF8_TEST} PRIVATE common) add_test(NAME ${UTF8_TEST} COMMAND ${UTF8_TEST}) set_tests_properties(${UTF8_TEST} PROPERTIES LABELS "unit") +# whisper_full() with n_samples == 0 must not read an uninitialized mel (#3978) +set(ZERO_SAMPLES_TEST test-whisper-zero-samples) +add_executable(${ZERO_SAMPLES_TEST} ${ZERO_SAMPLES_TEST}.cpp) +target_include_directories(${ZERO_SAMPLES_TEST} PRIVATE ../include ../ggml/include ../examples) +target_link_libraries(${ZERO_SAMPLES_TEST} PRIVATE common) +target_compile_definitions(${ZERO_SAMPLES_TEST} PRIVATE + WHISPER_MODEL_PATH="${PROJECT_SOURCE_DIR}/models/for-tests-ggml-tiny.bin") +add_test(NAME ${ZERO_SAMPLES_TEST} COMMAND ${ZERO_SAMPLES_TEST}) +set_tests_properties(${ZERO_SAMPLES_TEST} PROPERTIES LABELS "tiny;gh") + # VAD test tests VAD in isolation set(VAD_TEST test-vad) add_executable(${VAD_TEST} ${VAD_TEST}.cpp) diff --git a/tests/test-whisper-zero-samples.cpp b/tests/test-whisper-zero-samples.cpp new file mode 100644 index 000000000..bfea05e0c --- /dev/null +++ b/tests/test-whisper-zero-samples.cpp @@ -0,0 +1,38 @@ +// Regression test for issue #3978: +// whisper_full() called with n_samples == 0 on a fresh state must not read the +// never-computed (previously uninitialized) whisper_mel fields. With the mel +// default-initialized to "0 frames", the call takes the too-short path and +// returns cleanly with zero segments instead of running the encoder on garbage +// dimensions (which could dereference a NULL mel buffer). + +#include "whisper.h" + +#include + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +int main() { + struct whisper_context_params cparams = whisper_context_default_params(); + cparams.use_gpu = false; + + struct whisper_context * ctx = whisper_init_from_file_with_params(WHISPER_MODEL_PATH, cparams); + assert(ctx != nullptr); + + struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY); + params.no_timestamps = true; + params.print_progress = false; + params.print_realtime = false; + + // n_samples == 0 with a fresh state: the mel is never computed. + const int rc = whisper_full(ctx, params, nullptr, 0); + assert(rc == 0); + assert(whisper_full_n_segments(ctx) == 0); + + whisper_free(ctx); + + printf("test-whisper-zero-samples: OK\n"); + return 0; +}