diff --git a/src/umm_malloc.c b/src/umm_malloc.c index 940f8ca..2b04f34 100644 --- a/src/umm_malloc.c +++ b/src/umm_malloc.c @@ -135,6 +135,24 @@ static uint16_t umm_blocks(size_t size) { * When a block removed from the free list, the space used by the free * pointers is available for data. That's what the first calculation * of size is doing. + * + * We don't check for the special case of (size == 0) here as this needs + * special handling in the caller depending on context. For example when we + * realloc() a block to size 0 it should simply be freed. + * + * We do NOT need to check for allocating more blocks than the heap can + * possibly hold - the allocator figures this out for us. + * + * There are only two cases left to consider: + * + * 1. (size <= body) Obviously this is just one block + * 2. (blocks > (2^15)) This should return ((2^15)) to force a + * failure when the allocator runs + * + * If the requested size is greater that 32677-2 blocks (max block index + * minus the overhead of the top and bottom bookkeeping blocks) then we + * will return an incorrectly truncated value when the result is cast to + * a uint16_t. */ if (size <= (sizeof(((umm_block *)0)->body))) { @@ -143,12 +161,33 @@ static uint16_t umm_blocks(size_t size) { /* * If it's for more than that, then we need to figure out the number of - * additional whole blocks the size of an umm_block are required. + * additional whole blocks the size of an umm_block are required, so + * reduce the size request by the number of bytes in the body of the + * first block. + */ + + size -= (sizeof(((umm_block *)0)->body)); + + /* NOTE WELL that we take advantage of the fact that INT16_MAX is the + * number of blocks that we can index in 15 bits :-) + * + * The below expression looks wierd, but it's right. Assuming body + * size of 4 bytes and a block size of 8 bytes: + * + * BYTES (BYTES-BODY) (BYTES-BODY-1)/BLOCKSIZE BLOCKS + * 1 n/a n/a 1 + * 5 1 0 2 + * 12 8 0 2 + * 13 9 1 3 */ - size -= (1 + (sizeof(((umm_block *)0)->body))); + size_t blocks = (2 + ((size-1) / (UMM_BLOCKSIZE))); + + if (blocks > (INT16_MAX)) { + blocks = INT16_MAX; + } - return 2 + size / (UMM_BLOCKSIZE); + return (uint16_t)blocks; } /* ------------------------------------------------------------------------ */ diff --git a/test/test_TooBigMalloc.c b/test/test_TooBigMalloc.c new file mode 100644 index 0000000..c29951b --- /dev/null +++ b/test/test_TooBigMalloc.c @@ -0,0 +1,197 @@ +#include "unity.h" + +#include +#include + +#include + +/* Use the default DBGLOG_LEVEL and DBGLOG_FUNCTION */ + +#define DBGLOG_LEVEL 0 + +#ifdef DBGLOG_ENABLE + #include "dbglog/dbglog.h" +#endif + +void setUp(void) { + umm_init(); + umm_critical_depth = 0; + umm_max_critical_depth = 0; +} + +void tearDown(void) { + TEST_ASSERT_LESS_OR_EQUAL(1, umm_max_critical_depth); +} + +struct block_test_values Initialization_test_values[] = +{ {0, false, 1, 0, 1, 1} + , {1, true, UMM_LASTBLOCK, 0, 0, 0} + , {UMM_LASTBLOCK, false, 0, 1, 0, 0}}; + +#if 0 +void testHeapInitialization(void) { + DBGLOG_FORCE(true, "support heapsize %08x\n", SUPPORT_UMM_MALLOC_HEAP_SIZE); + + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} +#endif + +void testHeapFirstMallocMaxHeapBlocks(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +void testHeapFirstMallocMaxHeapBlocksMinus1(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-1) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +void testHeapFirstMallocMaxHeapBlocksMinus2(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-2) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +struct block_test_values MallocMaxHeapBlocksMinus3_test_values[] = +{ {0, false, 1, 0, 0, 0} + , {1, false, UMM_LASTBLOCK, 0, 0, 0} + , {UMM_LASTBLOCK, false, 0, 1, 0, 0}}; + +void testHeapFirstMallocMaxHeapBlocksMinus3(void) { + // This is a fairly complex test, so we will break it down + // + // First allocate the largest block possible ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE], (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-3) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksMinus3_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksMinus3_test_values))); + + // Then free it ... + umm_free((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +struct block_test_values MallocMaxHeapBlocksBig_test_values[] = +{ {0, false, 1, 0, UMM_LASTBLOCK-1, UMM_LASTBLOCK-1} + , {1, false, UMM_LASTBLOCK-1, 0, 0, 0} + , {UMM_LASTBLOCK-1, true, UMM_LASTBLOCK, 1, 0, 0} + , {UMM_LASTBLOCK, false, 0, UMM_LASTBLOCK-1, 0, 0}}; + +struct block_test_values MallocMaxHeapBlocksBigThenSmall_test_values[] = +{ {0, false, 1, 0, 0, 0} + , {1, false, UMM_LASTBLOCK-1, 0, 0, 0} + , {UMM_LASTBLOCK-1, 0, UMM_LASTBLOCK, 1, 0, 0} + , {UMM_LASTBLOCK, false, 0, UMM_LASTBLOCK-1, 0, 0}}; + +void testHeapTooBigMalloc_BigThenSmallMax(void) { + // This is a fairly complex test, so we will break it down + // + // First allocate the largest block possible that leaves exactly one block free ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE], (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-4) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksBig_test_values))); + + // Then allocate exactly one more block ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[UMM_LASTBLOCK-1][UMM_BLOCK_HEADER_SIZE], (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksBigThenSmall_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksBigThenSmall_test_values))); + + // Then allocate exactly one more block ... which should fail + // + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksBigThenSmall_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksBigThenSmall_test_values))); + + // Then free the last block ... + umm_free((void *)&test_umm_heap[UMM_LASTBLOCK-1][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksBig_test_values))); + + // Then free the first block ... which should get us back to the initialized state + umm_free((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +struct block_test_values MallocMaxHeapBlocksSmall_test_values[] = +{ {0, false, 1, 0, 2, 2} + , {1, false, 2, 0, 0, 0} + , {2, true, UMM_LASTBLOCK, 1, 0, 0} + , {UMM_LASTBLOCK, false, 0, 2, 0, 0}}; + +struct block_test_values MallocMaxHeapBlocksSmallThenBig_test_values[] = +{ {0, false, 1, 0, 0, 0} + , {1, false, 2, 0, 0, 0} + , {2, 0, UMM_LASTBLOCK, 1, 0, 0} + , {UMM_LASTBLOCK, false, 0, 2, 0, 0}}; + +void testHeapTooBigMalloc_SmallThenBigMax(void) { + // This is a fairly complex test, so we will break it down + // + // First allocate the smallest block possible that leaves a large block free ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE], (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmall_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmall_test_values))); + + // Then allocate the largest possible leftover block ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[2][UMM_BLOCK_HEADER_SIZE], (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-4) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmallThenBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmallThenBig_test_values))); + + // Then allocate exactly one more block ... which should fail + // + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmallThenBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmallThenBig_test_values))); + + // Then free the large block ... + umm_free((void *)&test_umm_heap[2][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmall_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmall_test_values))); + + // Then free the small block ... which should get us back to the initialized state + umm_free((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +struct block_test_values MallocMaxHeapBlocksSmallThenBig_ReverseFreetest_values[] = +{ {0, false, 1, 0, 1, 1} + , {1, true, 2, 0, 0, 0} + , {2, 0, UMM_LASTBLOCK, 1, 0, 0} + , {UMM_LASTBLOCK, false, 0, 2, 0, 0}}; + +void testHeapTooBigMalloc_SmallThenBigMax_ReverseFree(void) { + // This is a fairly complex test, so we will break it down + // + // First allocate the smallest block possible that leaves a large block free ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE], (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmall_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmall_test_values))); + + // Then allocate the largest possible leftover block ... + // + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[2][UMM_BLOCK_HEADER_SIZE], (umm_malloc((SUPPORT_UMM_MALLOC_BLOCKS-4) * UMM_BLOCK_BODY_SIZE))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmallThenBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmallThenBig_test_values))); + + // Then allocate exactly one more block ... which should fail + // + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(1))); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmallThenBig_test_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmallThenBig_test_values))); + + // Then free the small block ... + umm_free((void *)&test_umm_heap[1][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(MallocMaxHeapBlocksSmallThenBig_ReverseFreetest_values, ARRAYELEMENTCOUNT(MallocMaxHeapBlocksSmallThenBig_ReverseFreetest_values))); + + // Then free the large block ... + umm_free((void *)&test_umm_heap[2][UMM_BLOCK_HEADER_SIZE]); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +void testHeapFirstMallocMaxNumBlocks_Minus1(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(UMM_FIRST_BLOCK_BODY_SIZE + ((INT16_MAX-2) * 500)))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +void testHeapFirstMallocMaxNumBlocks(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(UMM_FIRST_BLOCK_BODY_SIZE + ((INT16_MAX-1) * 500)))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} + +void testHeapFirstMallocMaxNumBlocks_Plus1(void) { + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_malloc(UMM_FIRST_BLOCK_BODY_SIZE + ((INT16_MAX-0) * 500)))); + TEST_ASSERT_TRUE(check_blocks(Initialization_test_values, ARRAYELEMENTCOUNT(Initialization_test_values))); +} +