Skip to content

fix: make reverb allocation failure safe (mirrors existing echo guard)#744

Merged
dpwe merged 1 commit into
shorepine:mainfrom
rt-rtos:fix-reverb-oom-safety
Jun 22, 2026
Merged

fix: make reverb allocation failure safe (mirrors existing echo guard)#744
dpwe merged 1 commit into
shorepine:mainfrom
rt-rtos:fix-reverb-oom-safety

Conversation

@rt-rtos

@rt-rtos rt-rtos commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Bug:

init_stereo_reverb() allocates 10 delay lines but returns void, so a partial
allocation failure is silently ignored. The idempotency guard at the top of the
function checks only rev->delay_1: if delay_1 succeeds but any later line fails,
rev->delay_1 is non-NULL on all subsequent calls, so the half-initialised struct
is never repaired. config_reverb() then sets reverb.level unconditionally, and
the render guard in amy_fill_buffer checks only reverb.level > 0 — so
stereo_reverb() runs and dereferences a NULL delay-line pointer, guaranteed crash.

Echo already has the correct guard (echo_delay_lines[0] != NULL). This PR brings
reverb in line, adapted to the struct-based reverb_params_t design:

Allocator — returns bool, rolls back on failure:

amy.c - line 204

bool alloc_echo_delay_lines(uint8_t bus, uint32_t max_delay_samples) {
    bool success = true;
    for (int c = 0; c < AMY_NCHANS; ++c) {
        delay_line_t *delay_line = new_delay_line(max_delay_samples, 0, amy_global.config.ram_caps_delay);
        if (delay_line) {
            amy_global.bus[bus]->echo.echo_delay_lines[c] = delay_line;
        } else {
            success = false;
            break;
        }
    }
    if (!success) {
        fprintf(stderr, "unable to alloc echo of %d samples\n", (int)max_delay_samples);
        dealloc_echo_delay_lines(bus);
        return false;
    }
    return true;
}

config_echo — bails on allocation failure before committing level:

amy.c - line 243-245

    if (level > 0) {
        if (amy_global.bus[bus]->echo.echo_delay_lines[0] == NULL) {
            if (!alloc_echo_delay_lines(bus, max_delay_samples)) return;
        }
        ...
    }
    amy_global.bus[bus]->echo.level = F2S(level);

Render guard:

amy.c - line 1975
if (amy_global.bus[bus]->echo.level > 0 && amy_global.bus[bus]->echo.echo_delay_lines[0] != NULL) {

Fix:

  • init_stereo_reverb(reverb_params_t *rev) returns bool. On any allocation
    failure it calls the existing deinit_stereo_reverb() to roll back all partial
    allocations (leaving every pointer NULL) and returns false.
  • alloc_reverb_delay_lines() returns bool, propagating the failure to its caller.
  • config_reverb() forces level=0 and returns early on allocation failure instead
    of committing a non-zero level against an uninitialised rev.
  • The render guard adds rev != NULL && rev->delay_1 != NULL as a final defence
    against NULL deref (mirrors the echo pattern; delay_1 is the first line
    allocated and the last freed, making it a reliable sentinel).

Behaviour is unchanged when allocation succeeds. The added render-path check is
two pointer comparisons per block.

Pardon me if I am missing something obvious and wasted anyones time.

…@ alloc_echo_delay_lines (line 204), config_echo (line 243-245), Render guard (line 1975)
@bwhitman

Copy link
Copy Markdown
Collaborator

🎛️ AMYboard HW CI

Dispatched this PR's amy SHA to the tulipcc bench — it builds the AMYboard firmware with your change and runs it on the physical board (audio spectral-compared to a committed reference). Results post here in a few minutes.

@bwhitman

Copy link
Copy Markdown
Collaborator

🎛️ HW CI (physical bench)

Built from this AMY PR, pinned into the tulipcc amy submodule.

AMYboard (USB-MIDI + AMY zP → audio): ✅ PASS — flashed this PR’s firmware; all checks matched the references.

Tulip (TULIP4_R11; serial-REPL audio + WiFi screenshot): ✅ PASS — flashed this PR’s firmware; all checks matched the references.

⬇️ Artifacts: recordings · screenshot · serial logs · run logs

Self-hosted bench. Audio spectral-compared to ref/hwci_basic.wav + ref/tulip_basic.wav; Tulip screenshot pixel-compared to ref/tulip_screenshot.png. Both analog outs share one capture card, so the tests run sequentially.

@dpwe dpwe merged commit 0e38665 into shorepine:main Jun 22, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants