8000 Decoder: fix leaks and rejected custom backends when an encoding form… by sjoblomj · Pull Request #1191 · mackron/miniaudio · GitHub
[go: up one dir, main page]

Skip to content

Decoder: fix leaks and rejected custom backends when an encoding form… - #1191

Open
sjoblomj wants to merge 1 commit into
mackron:devfrom
sjoblomj:fix-decoder-leaks
Open

sjoblomj wants to merge 1 commit into
mackron:devfrom
sjoblomj:fix-decoder-leaks

Conversation

@sjoblomj
@sjoblomj sjoblomj commented Nov 2, 2026 •
Copy link
Copy Markdown
Contributor

...at is requested

Base branch: dev. dev-0.12 doesn't have this code.

Summary

There are two bugs at the same check, and both need encodingFormat to be set:

if (config.encodingFormat != ma_encoding_format_unknown) {
    return MA_NO_BACKEND;
}
  • A failed open leaks the file. ma_decoder_init_vfs() and ma_decoder_init_vfs_w() have already opened the file when they get here, and they don't close it. With a limit of 32 handles, fopen() fails after 29 of these opens, and a valid file then fails to open with Too many open files.
  • A custom backend that succeeded is thrown away. The check doesn't look at result, so it returns MA_NO_BACKEND even when a custom backend has just opened the data. The caller gets an error and the backend is leaked. ma_decoder_init(), ma_decoder_init_vfs() and ma_decoder_init_vfs_w() all do this, so a custom backend can't be used through them with encodingFormat set. ma_decoder_init_file() and ma_decoder_init_memory() have the check, and work.

Both only happen when the caller sets encodingFormat and calls these functions directly. The resource manager doesn't set it, and ma_decoder_init_file() returns before it gets to the VFS.

The fix

  • Add the result != MA_SUCCESS check in all three, the same as ma_decoder_init_file() and ma_decoder_init_memory(). A decoder from a custom backend now continues to post-initialization.
  • Close the file before returning in the two VFS functions.

The comment above the check already describes this: "If we get to this point and we still haven't found a decoder".

ma_decoder_init__internal() is also called by ma_decoder_init_memory() and the VFS functions, but they only get there with no format requested, so only ma_decoder_init() changes.

Reproduction

The program opens files through a VFS that counts the files left open, with allocation callbacks that count the allocations left. It writes format_test.wav and format_test.bin to the current directory. Run it from the root of the repository.

/*
Opens files through ma_decoder_init_vfs(), ma_decoder_init_vfs_w() and ma_decoder_init() with a specific
encoding format. A VFS counts the files left open, and the allocation callbacks count the allocations
left. ma_decoder_init() reads through callbacks, and the program closes that file itself, so it never
has any files left.

The custom backend is the WAV decoder from miniaudio. Requesting MP3 from it stands in for an
application that provides its own decoder for a format.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define WAV_DATA_SIZE 4000

static ma_default_vfs g_defaultVFS;
static int g_filesLeft;
static int g_allocationsLeft;

static ma_result vfs_open(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile)
{
    ma_result result = ma_vfs_open(&g_defaultVFS, pFilePath, openMode, pFile);
    (void)pVFS;
    if (result == MA_SUCCESS) {
        g_filesLeft += 1;
    }
    return result;
}

static ma_result vfs_open_w(ma_vfs* pVFS, const wchar_t* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile)
{
    ma_result result = ma_vfs_open_w(&g_defaultVFS, pFilePath, openMode, pFile);
    (void)pVFS;
    if (result == MA_SUCCESS) {
        
8000
g_filesLeft += 1;
    }
    return result;
}

static ma_result vfs_close(ma_vfs* pVFS, ma_vfs_file file)
{
    (void)pVFS;
    g_filesLeft -= 1;
    return ma_vfs_close(&g_defaultVFS, file);
}

static ma_result vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead)
{
    (void)pVFS;
    return ma_vfs_read(&g_defaultVFS, file, pDst, sizeInBytes, pBytesRead);
}

static ma_result vfs_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin)
{
    (void)pVFS;
    return ma_vfs_seek(&g_defaultVFS, file, offset, origin);
}

static ma_result vfs_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor)
{
    (void)pVFS;
    return ma_vfs_tell(&g_defaultVFS, file, pCursor);
}

static ma_result vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo)
{
    (void)pVFS;
    return ma_vfs_info(&g_defaultVFS, file, pInfo);
}

static ma_vfs_callbacks g_vfs = { vfs_open, vfs_open_w, vfs_close, vfs_read, NULL, vfs_seek, vfs_tell, vfs_info };

static void* test_malloc(size_t size, void* pUserData)
{
    void* p = malloc(size);
    (void)pUserData;
    if (p != NULL) {
        g_allocationsLeft += 1;
    }
    return p;
}

static void* test_realloc(void* p, size_t size, void* pUserData)
{
    (void)pUserData;
    if (p == NULL) {
        return test_malloc(size, pUserData);
    }
    return realloc(p, size);
}

static void test_free(void* p, void* pUserData)
{
    (void)pUserData;
    if (p != NULL) {
        g_allocationsLeft -= 1;
    }
    free(p);
}

static void put_u16(unsigned char* p, unsigned int v)
{
    p[0] = (unsigned char)(v & 0xFF);
    p[1] = (unsigned char)(v >> 8);
}

static void put_u32(unsigned char* p, unsigned int v)
{
    put_u16(p + 0, v & 0xFFFF);
    put_u16(p + 2, v >> 16);
}

/* 16-bit stereo at 44100 Hz. */
static void make_files(void)
{
    static unsigned char wav[44 + WAV_DATA_SIZE];
    FILE* pFile;

    memcpy(wav +  0, "RIFF", 4);
    put_u32(wav +  4, 36 + WAV_DATA_SIZE);
    memcpy(wav +  8, "WAVEfmt ", 8);
    put_u32(wav + 16, 16);
    put_u16(wav + 20, 1);
    put_u16(wav + 22, 2);
    put_u32(wav + 24, 44100);
    put_u32(wav + 28, 44100 * 4);
    put_u16(wav + 32, 4);
    put_u16(wav + 34, 16);
    memcpy(wav + 36, "data", 4);
    put_u32(wav + 40, WAV_DATA_SIZE);

    pFile = fopen("format_test.wav", "wb");
    fwrite(wav, 1, sizeof(wav), pFile);
    fclose(pFile);

    pFile = fopen("format_test.bin", "wb");
    fputs("This isn't audio data, in any format at all, not even a little.", pFile);
    fclose(pFile);
}

static ma_result on_read(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead)
{
    *pBytesRead = fread(pBufferOut, 1, bytesToRead, (FILE*)pDecoder->pUserData);
    return (*pBytesRead == 0) ? MA_AT_END : MA_SUCCESS;
}

static ma_result on_seek(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin)
{
    int whence = (origin == ma_seek_origin_start) ? SEEK_SET : ((origin == ma_seek_origin_current) ? SEEK_CUR : SEEK_END);
    return (fseek((FILE*)pDecoder->pUserData, (long)offset, whence) == 0) ? MA_SUCCESS : MA_ERROR;
}

static const char* const g_entryPointNames[3] = {
    "ma_decoder_init_vfs",
    "ma_decoder_init_vfs_w",
    "ma_decoder_init"
};

static void run_case(const char* pName, int entryPoint, int isWav, ma_encoding_format format, int useCustomBackend)
{
    static ma_int16 pcm[4096 * 2];
    ma_decoding_backend_vtable* pCustomBackends[1];
    ma_decoder_config config = ma_decoder_config_init(ma_format_s16, 2, 44100);
    ma_decoder decoder;
    ma_uint64 frames = 0;
    ma_result result;
    FILE* pFile = NULL;

    config.encodingFormat = format;
    config.allocationCallbacks.onMalloc  = test_malloc;
    config.allocationCallbacks.onRealloc = test_realloc;
    config.allocationCallbacks.onFree    = test_free;

    if (useCustomBackend) {
        pCustomBackends[0] = &g_ma_decoding_backend_vtable_wav;
        config.ppCustomBackendVTables = pCustomBackends;
        config.customBackendCount = 1;
    }

    g_filesLeft = 0;
    g_allocationsLeft = 0;

    if (entryPoint == 0) {
        result = ma_decoder_init_vfs(&g_vfs, isWav ? "format_test.wav" : "format_test.bin", &config, &decoder);
    } else if (entryPoint == 1) {
        result = ma_decoder_init_vfs_w(&g_vfs, isWav ? L"format_test.wav" : L"format_test.bin", &config, &decoder);
    } else {
        pFile = fopen(isWav ? "format_test.wav" : "format_test.bin", "rb");
        result = ma_decoder_init(on_read, on_seek, pFile, &config, &decoder);
    }

    if (result == MA_SUCCESS) {
        ma_decoder_read_pcm_frames(&decoder, pcm, 4096, &frames);
        ma_decoder_uninit(&decoder);
    }

    if (pFile != NULL) {
        fclose(pFile);
    }

    printf("%-21s  %-34s  %-10s  %4d frames  %d files left, %d allocations left\n",
        g_entryPointNames[entryPoint], pName, ma_result_description(result), (int)frames, g_filesLeft, g_allocationsLeft);
}

int main(void)
{
    int entryPoint;

    ma_default_vfs_init(&g_defaultVFS, NULL);
    make_files();

    for (entryPoint = 0; entryPoint < 3; entryPoint += 1) {
        run_case("Not audio, WAV requested",           entryPoint, 0, ma_encoding_format_wav,     0);
        run_case("WAV, MP3 requested",                 entryPoint, 1, ma_encoding_format_mp3,     0);
        run_case("WAV, MP3 requested, custom backend", entryPoint, 1, ma_encoding_format_mp3,     1);
        run_case("WAV, WAV requested",                 entryPoint, 1, ma_encoding_format_wav,     0);
        run_case("WAV, nothing requested",             entryPoint, 1, ma_encoding_format_unknown, 0);
    }

    return 0;
}
gcc decoder_vfs_format.c -o decoder_vfs_format -std=c89 -Wall -Wextra -lm -lpthread -ldl
./decoder_vfs_format

Before, on 877ea4a:

ma_decoder_init_vfs    Not audio, WAV requested            No backend     0 frames  1 files left, 0 allocations left
ma_decoder_init_vfs    WAV, MP3 requested                  No backend     0 frames  1 files left, 0 allocations left
ma_decoder_init_vfs    WAV, MP3 requested, custom backend  No backend     0 frames  1 files left, 1 allocations left
ma_decoder_init_vfs    WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs    WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  Not audio, WAV requested            No backend     0 frames  1 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, MP3 requested                  No backend     0 frames  1 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, MP3 requested, custom backend  No backend     0 frames  1 files left, 1 allocations left
ma_decoder_init_vfs_w  WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init        Not audio, WAV requested            No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, MP3 requested                  No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, MP3 requested, custom backend  No backend     0 frames  0 files left, 1 allocations left
ma_decoder_init        WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left

After:

ma_decoder_init_vfs    Not audio, WAV requested            No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init_vfs    WAV, MP3 requested                  No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init_vfs    WAV, MP3 requested, custom backend  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs    WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs    WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  Not audio, WAV requested            No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, MP3 requested                  No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, MP3 requested, custom backend  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init_vfs_w  WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init        Not audio, WAV requested            No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, MP3 requested                  No backend     0 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, MP3 requested, custom backend  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, WAV requested                  No error    1000 frames  0 files left, 0 allocations left
ma_decoder_init        WAV, nothing requested              No error    1000 frames  0 files left, 0 allocations left

Built with -fsanitize=address -g, the program reports 1560 byte(s) leaked in 3 allocation(s) before, and nothing after. Those are the three custom backends.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0