tests/util_allocator.c

Mon, 18 Nov 2024 22:05:42 +0100

author
Mike Becker <universe@uap-core.de>
date
Mon, 18 Nov 2024 22:05:42 +0100
changeset 988
15b3ca7ee33f
parent 985
68754c7de906
permissions
-rw-r--r--

make ucx C++ compatible again (and add tests for it) - fixes #486

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "util_allocator.h"
#include "cx/test.h"

#if !defined(__clang__) && __GNUC__ > 11
// this utility is explicitly designed to track UAF
#pragma GCC diagnostic ignored "-Wuse-after-free"
#endif

static void cx_testing_allocator_track(CxTestingAllocator *alloc, void *ptr) {
    for (size_t i = 0; i < alloc->tracked_count; i++) {
        if (alloc->tracked[i] == ptr) return; // is already tracked
    }

    if (alloc->tracked_count == alloc->tracked_capacity) {
        size_t newcapa = alloc->tracked_capacity + 64;
        void *newarr = realloc(alloc->tracked, newcapa * sizeof(void *));
        if (newarr == NULL) abort();
        alloc->tracked = newarr;
        alloc->tracked_capacity = newcapa;
    }

    alloc->tracked[alloc->tracked_count] = ptr;
    alloc->tracked_count++;
}

static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc, void *ptr) {
    for (size_t i = 0; i < alloc->tracked_count; i++) {
        if (alloc->tracked[i] == ptr) {
            size_t last = alloc->tracked_count - 1;
            if (i < last) {
                alloc->tracked[i] = alloc->tracked[last];
            }
            alloc->tracked_count--;
            return true;
        }
    }
    return false;
}

static void *cx_malloc_testing(void *d, size_t n) {
    CxTestingAllocator *data = d;
    void *ptr = malloc(n);
    data->alloc_total++;
    if (ptr == NULL) {
        data->alloc_failed++;
    } else {
        cx_testing_allocator_track(data, ptr);
    }
    return ptr;
}

static void *cx_realloc_testing(void *d, void *mem, size_t n) {
    CxTestingAllocator *data = d;
    void *ptr = realloc(mem, n);
    if (ptr == mem) {
        return ptr;
    } else {
        data->alloc_total++;
        if (ptr == NULL) {
            data->alloc_failed++;
        } else {
            data->free_total++;
            if (!cx_testing_allocator_untrack(data, mem)) {
                data->free_failed++;
            }
            cx_testing_allocator_track(data, ptr);
        }
        return ptr;
    }
}

static void *cx_calloc_testing(void *d, size_t nelem, size_t n) {
    CxTestingAllocator *data = d;
    void *ptr = calloc(nelem, n);
    data->alloc_total++;
    if (ptr == NULL) {
        data->alloc_failed++;
    } else {
        cx_testing_allocator_track(data, ptr);
    }
    return ptr;
}

static void cx_free_testing(void *d, void *mem) {
    CxTestingAllocator *data = d;
    data->free_total++;
    if (cx_testing_allocator_untrack(data, mem)) {
        free(mem);
    } else {
        data->free_failed++;
        // do not even attempt to free mem, because it is likely to segfault
    }
}

cx_allocator_class cx_testing_allocator_class = {
        cx_malloc_testing,
        cx_realloc_testing,
        cx_calloc_testing,
        cx_free_testing
};


void cx_testing_allocator_init(CxTestingAllocator *alloc) {
    alloc->base.cl = &cx_testing_allocator_class;
    alloc->base.data = alloc;
    alloc->alloc_failed = 0;
    alloc->alloc_total = 0;
    alloc->free_failed = 0;
    alloc->free_total = 0;
    size_t initial_capa = 16;
    alloc->tracked_capacity = initial_capa;
    alloc->tracked_count = 0;
    alloc->tracked = calloc(initial_capa, sizeof(void *));
}

void cx_testing_allocator_destroy(CxTestingAllocator *alloc) {
    free(alloc->tracked);
}

bool cx_testing_allocator_used(const CxTestingAllocator *alloc) {
    return alloc->alloc_total > 0;
}

bool cx_testing_allocator_verify(const CxTestingAllocator *alloc) {
    return alloc->tracked_count == 0 && alloc->alloc_failed == 0 && alloc->free_failed == 0
           && alloc->alloc_total == alloc->free_total;
}

// SELF-TEST

CX_TEST(test_util_allocator_expect_free) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
                        "Fresh testing allocator fails to verify.");
        CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc),
                        "Fresh testing allocator already used.");
        void *ptr = cxMalloc(alloc, 16);
        CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
                        "Testing allocator verifies with unfreed memory.");
        CX_TEST_ASSERT(cx_testing_allocator_used(&talloc));
        CX_TEST_ASSERT(ptr != NULL);

        cxFree(alloc, ptr);
        CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
                        "Testing allocator fails to verify after everything freed.");
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_util_allocator_detect_double_free) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
        void *ptr = cxMalloc(alloc, 16);
        CX_TEST_ASSERT(ptr != NULL);
        cxFree(alloc, ptr);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
        cxFree(alloc, ptr);
        CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
                        "Testing allocator does not detect double-free.");
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_util_allocator_free_untracked) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    void *ptr = malloc(16);
    CX_TEST_DO {
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
        cxFree(alloc, ptr);
        CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
                        "Testing allocator does not detect free of untracked memory.");
    }
    free(ptr);
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_util_allocator_full_lifecycle_with_realloc) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
        void *ptr = cxMalloc(alloc, 16);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
        CX_TEST_ASSERT(ptr != NULL);
        CX_TEST_ASSERT(talloc.tracked_count == 1);
        ptr = cxRealloc(alloc, ptr, 256);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
        CX_TEST_ASSERT(ptr != NULL);
        CX_TEST_ASSERT(talloc.tracked_count == 1);
        cxFree(alloc, ptr);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_util_allocator_calloc_initializes) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        const char zeros[16] = {0};
        void *ptr = cxCalloc(alloc, 16, 1);
        CX_TEST_ASSERT(memcmp(ptr, zeros, 16) == 0);
        cxFree(alloc, ptr);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CxTestSuite *cx_test_suite_testing_allocator(void) {
    CxTestSuite *suite = cx_test_suite_new("testing allocator self-test");

    cx_test_register(suite, test_util_allocator_expect_free);
    cx_test_register(suite, test_util_allocator_detect_double_free);
    cx_test_register(suite, test_util_allocator_free_untracked);
    cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc);
    cx_test_register(suite, test_util_allocator_calloc_initializes);

    return suite;
}

mercurial