diff --git a/src/umm_malloc.c b/src/umm_malloc.c index b4275e0..9faec2a 100644 --- a/src/umm_malloc.c +++ b/src/umm_malloc.c @@ -8,7 +8,7 @@ * R.Hempel 2007-09-22 - Original * R.Hempel 2008-12-11 - Added MIT License biolerplate * - realloc() now looks to see if previous block is free - * - made common operations functions + * - made common operations functions * R.Hempel 2009-03-02 - Added macros to disable tasking * - Added function to dump heap and check for valid free * pointer @@ -22,9 +22,11 @@ * R.Hempel 2016-12-04 - Add support for Unity test framework * - Reorganize source files to avoid redundant content * - Move integrity and poison checking to separate file + * R.Hempel 2017-12-29 - Fix bug in realloc when requesting a new block that + * results in OOM error - see Issue 11 * ---------------------------------------------------------------------------- */ - + #include #include @@ -118,8 +120,6 @@ static unsigned short int umm_blocks( size_t size ) { /* * Split the block `c` into two blocks: `c` and `c + blocks`. * - * - `cur_freemask` should be `0` if `c` used, or `UMM_FREELIST_MASK` - * otherwise. * - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK` * otherwise. * @@ -149,7 +149,10 @@ static void umm_disconnect_from_free_list( unsigned short int c ) { UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK); } -/* ------------------------------------------------------------------------ */ +/* ------------------------------------------------------------------------ + * The umm_assimilate_up() function assumes that UMM_NBLOCK(c) does NOT + * have the UMM_FREELIST_MASK bit set! + */ static void umm_assimilate_up( unsigned short int c ) { @@ -169,10 +172,13 @@ static void umm_assimilate_up( unsigned short int c ) { UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c; UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK; - } + } } -/* ------------------------------------------------------------------------ */ +/* ------------------------------------------------------------------------ + * The umm_assimilate_down() function assumes that UMM_NBLOCK(c) does NOT + * have the UMM_FREELIST_MASK bit set! + */ static unsigned short int umm_assimilate_down( unsigned short int c, unsigned short int freemask ) { @@ -399,8 +405,7 @@ void *umm_malloc( size_t size ) { * split current free block `cf` into two blocks. The first one will be * returned to user, so it's not free, and the second one will be free. */ - umm_split_block( cf, blocks, - UMM_FREELIST_MASK/*new block is free*/); + umm_split_block( cf, blocks, UMM_FREELIST_MASK /*new block is free*/ ); /* * `umm_split_block()` does not update the free pointers (it affects @@ -440,6 +445,8 @@ void *umm_realloc( void *ptr, size_t size ) { unsigned short int blocks; unsigned short int blockSize; + unsigned short int prevBlockSize = 0; + unsigned short int nextBlockSize = 0; unsigned short int c; @@ -477,9 +484,6 @@ void *umm_realloc( void *ptr, size_t size ) { return( (void *)NULL ); } - /* Protect the critical section... */ - UMM_CRITICAL_ENTRY(); - /* * Otherwise we need to actually do a reallocation. A naiive approach * would be to malloc() a new block of the correct size, copy the old data @@ -495,7 +499,7 @@ void *umm_realloc( void *ptr, size_t size ) { c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); - /* Figure out how big this block is... */ + /* Figure out how big this block is ... the free bit is not set :-) */ blockSize = (UMM_NBLOCK(c) - c); @@ -503,112 +507,101 @@ void *umm_realloc( void *ptr, size_t size ) { curSize = (blockSize*sizeof(umm_block))-(sizeof(((umm_block *)0)->header)); - /* - * Ok, now that we're here, we know the block number of the original chunk - * of memory, and we know how much new memory we want, and we know the original - * block size... - */ - - if( blockSize == blocks ) { - /* This space intentionally left blank - return the original pointer! */ - - DBGLOG_DEBUG( "realloc the same size block - %i, do nothing\n", blocks ); - - /* Release the critical section... */ - UMM_CRITICAL_EXIT(); - - return( ptr ); - } + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); - /* - * Now we have a block size that could be bigger or smaller. Either - * way, try to assimilate up to the next block before doing anything... + /* Now figure out if the previous and/or next blocks are free as well as + * their sizes - this will help us to minimize special code later when we + * decide if it's possible to use the adjacent blocks. * - * If it's still too small, we have to free it anyways and it will save the - * assimilation step later in free :-) + * We set prevBlockSize and nextBlockSize to non-zero values ONLY if they + * are free! */ - umm_assimilate_up( c ); - - /* - * Now check if it might help to assimilate down, but don't actually - * do the downward assimilation unless the resulting block will hold the - * new request! If this block of code runs, then the new block will - * either fit the request exactly, or be larger than the request. - */ - - if( (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) && - (blocks <= (UMM_NBLOCK(c)-UMM_PBLOCK(c))) ) { - - /* Check if the resulting block would be big enough... */ - - DBGLOG_DEBUG( "realloc() could assimilate down %i blocks - fits!\n\r", c-UMM_PBLOCK(c) ); - - /* Disconnect the previous block from the FREE list */ - - umm_disconnect_from_free_list( UMM_PBLOCK(c) ); - - /* - * Connect the previous block to the next block ... and then - * realign the current block pointer - */ - - c = umm_assimilate_down(c, 0); - - /* - * Move the bytes down to the new block we just created, but be sure to move - * only the original bytes. - */ - - memmove( (void *)&UMM_DATA(c), ptr, curSize ); - - /* And don't forget to adjust the pointer to the new block location! */ - - ptr = (void *)&UMM_DATA(c); + if ((UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK)) { + nextBlockSize = (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) - UMM_NBLOCK(c); } - /* Now calculate the block size again...and we'll have three cases */ - - blockSize = (UMM_NBLOCK(c) - c); - - if( blockSize == blocks ) { - /* This space intentionally left blank - return the original pointer! */ - - DBGLOG_DEBUG( "realloc the same size block - %i, do nothing\n", blocks ); - - } else if (blockSize > blocks ) { - /* - * New block is smaller than the old block, so just make a new block - * at the end of this one and put it up on the free list... - */ + if ((UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK)) { + prevBlockSize = (c - UMM_PBLOCK(c)); + } - DBGLOG_DEBUG( "realloc %i to a smaller block %i, shrink and free the leftover bits\n", blockSize, blocks ); + DBGLOG_DEBUG( "realloc blocks %i blockSize %i nextBlockSize %i prevBlockSize %i\n", blocks, blockSize, nextBlockSize, prevBlockSize ); - umm_split_block( c, blocks, 0 ); - umm_free( (void *)&UMM_DATA(c+blocks) ); - } else { - /* New block is bigger than the old block... */ - - void *oldptr = ptr; + /* + * Ok, now that we're here we know how many blocks we want and the current + * blockSize. The prevBlockSize and nextBlockSize are set and we can figure + * out the best strategy for the new allocation as follows: + * + * 1. If the new block is the same size or smaller than the current block do + * nothing. + * 2. If the next block is free and adding it to the current block gives us + * enough memory, assimilate the next block. + * 3. If the prev block is free and adding it to the current block gives us + * enough memory, remove the previous block from the free list, assimilate + * it, copy to the new block. + * 4. If the prev and next blocks are free and adding them to the current + * block gives us enough memory, assimilate the next block, remove the + * previous block from the free list, assimilate it, copy to the new block. + * 5. Otherwise try to allocate an entirely new block of memory. If the + * allocation works free the old block and return the new pointer. If + * the allocation fails, return NULL and leave the old block intact. + * + * All that's left to do is decide if the fit was exact or not. If the fit + * was not exact, then split the memory block so that we use only the requested + * number of blocks and add what's left to the free list. + */ - DBGLOG_DEBUG( "realloc %i to a bigger block %i, make new, copy, and free the old\n", blockSize, blocks ); + if (blockSize >= blocks) { + DBGLOG_DEBUG( "realloc the same or smaller size block - %i, do nothing\n", blocks ); + /* This space intentionally left blank */ + } else if ((blockSize + nextBlockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using next block - %i\n", blocks ); + umm_assimilate_up( c ); + blockSize += nextBlockSize; + } else if ((prevBlockSize + blockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using prev block - %i\n", blocks ); + umm_disconnect_from_free_list( UMM_PBLOCK(c) ); + c = umm_assimilate_down(c, 0); + memmove( (void *)&UMM_DATA(c), ptr, curSize ); + ptr = (void *)&UMM_DATA(c); + blockSize += prevBlockSize; + } else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using prev and next block - %i\n", blocks ); + umm_assimilate_up( c ); + umm_disconnect_from_free_list( UMM_PBLOCK(c) ); + c = umm_assimilate_down(c, 0); + memmove( (void *)&UMM_DATA(c), ptr, curSize ); + ptr = (void *)&UMM_DATA(c); + blockSize += (prevBlockSize + nextBlockSize); + } else { + DBGLOG_DEBUG( "realloc a completely new block %i\n", blocks ); + void *oldptr = ptr; + if( (ptr = umm_malloc( size )) ) { + DBGLOG_DEBUG( "realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks ); + memcpy( ptr, oldptr, curSize ); + umm_free( oldptr ); + } else { + DBGLOG_DEBUG( "realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks ); + /* This space intentionally left blnk */ + } + blockSize = blocks; + } - /* - * Now umm_malloc() a new one, copy the old data to the new block, and - * free up the old block, but only if the malloc was sucessful! + /* Now all we need to do is figure out if the block fit exactly or if we + * need to split and free ... */ - if( (ptr = umm_malloc( size )) ) { - memcpy( ptr, oldptr, curSize ); + if (blockSize > blocks ) { + DBGLOG_DEBUG( "split and free %i blocks from %i\n", blocks, blockSize ); + umm_split_block( c, blocks, 0 ); + umm_free( (void *)&UMM_DATA(c+blocks) ); } - umm_free( oldptr ); - } - - /* Release the critical section... */ - UMM_CRITICAL_EXIT(); + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); - return( ptr ); + return( ptr ); } /* ------------------------------------------------------------------------ */ @@ -617,6 +610,7 @@ void *umm_calloc( size_t num, size_t item_size ) { void *ret; ret = umm_malloc((size_t)(item_size * num)); + if (ret) memset(ret, 0x00, (size_t)(item_size * num)); diff --git a/src/umm_malloc_cfg.h b/src/umm_malloc_cfg.h index fcc4fb1..6a5a7fc 100644 --- a/src/umm_malloc_cfg.h +++ b/src/umm_malloc_cfg.h @@ -13,19 +13,7 @@ * * -D UMM_TEST_MAIN * - * Set this if you want to compile in the test suite at the end of this file. - * - * If you leave this define unset, then you might want to set another one: - * - * -D UMM_REDEFINE_MEM_FUNCTIONS - * - * If you leave this define unset, then the function names are left alone as - * umm_malloc() umm_free() and umm_realloc() so that they cannot be confused - * with the C runtime functions malloc() free() and realloc() - * - * If you do set this define, then the function names become malloc() - * free() and realloc() so that they can be used as the C runtime functions - * in an embedded environment. + * Set this if you want to compile in the test suite * * -D UMM_BEST_FIT (defualt) * diff --git a/src/umm_malloc_cfg_example.h b/src/umm_malloc_cfg_example.h index f28b657..ae3ecfd 100644 --- a/src/umm_malloc_cfg_example.h +++ b/src/umm_malloc_cfg_example.h @@ -13,19 +13,7 @@ * * -D UMM_TEST_MAIN * - * Set this if you want to compile in the test suite at the end of this file. - * - * If you leave this define unset, then you might want to set another one: - * - * -D UMM_REDEFINE_MEM_FUNCTIONS - * - * If you leave this define unset, then the function names are left alone as - * umm_malloc() umm_free() and umm_realloc() so that they cannot be confused - * with the C runtime functions malloc() free() and realloc() - * - * If you do set this define, then the function names become malloc() - * free() and realloc() so that they can be used as the C runtime functions - * in an embedded environment. + * Set this if you want to compile in the test suite * * -D UMM_BEST_FIT (defualt) * diff --git a/src/umm_malloc_cfg_test.h b/src/umm_malloc_cfg_test.h deleted file mode 100644 index fcc4fb1..0000000 --- a/src/umm_malloc_cfg_test.h +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Configuration for umm_malloc - */ - -#ifndef _UMM_MALLOC_CFG_H -#define _UMM_MALLOC_CFG_H - -/* - * There are a number of defines you can set at compile time that affect how - * the memory allocator will operate. - * You can set them in your config file umm_malloc_cfg.h. - * In GNU C, you also can set these compile time defines like this: - * - * -D UMM_TEST_MAIN - * - * Set this if you want to compile in the test suite at the end of this file. - * - * If you leave this define unset, then you might want to set another one: - * - * -D UMM_REDEFINE_MEM_FUNCTIONS - * - * If you leave this define unset, then the function names are left alone as - * umm_malloc() umm_free() and umm_realloc() so that they cannot be confused - * with the C runtime functions malloc() free() and realloc() - * - * If you do set this define, then the function names become malloc() - * free() and realloc() so that they can be used as the C runtime functions - * in an embedded environment. - * - * -D UMM_BEST_FIT (defualt) - * - * Set this if you want to use a best-fit algorithm for allocating new - * blocks - * - * -D UMM_FIRST_FIT - * - * Set this if you want to use a first-fit algorithm for allocating new - * blocks - * - * -D UMM_DBG_LOG_LEVEL=n - * - * Set n to a value from 0 to 6 depending on how verbose you want the debug - * log to be - * - * ---------------------------------------------------------------------------- - * - * Support for this library in a multitasking environment is provided when - * you add bodies to the UMM_CRITICAL_ENTRY and UMM_CRITICAL_EXIT macros - * (see below) - * - * ---------------------------------------------------------------------------- - */ - -extern char test_umm_heap[]; - -/* Start addresses and the size of the heap */ -#define UMM_MALLOC_CFG_HEAP_ADDR (test_umm_heap) -#define UMM_MALLOC_CFG_HEAP_SIZE 0x10000 - -/* A couple of macros to make packing structures less compiler dependent */ - -#define UMM_H_ATTPACKPRE -#define UMM_H_ATTPACKSUF __attribute__((__packed__)) - -#define UMM_BEST_FIT -#undef UMM_FIRST_FIT - -/* - * -D UMM_INFO : - * - * Enables a dup of the heap contents and a function to return the total - * heap size that is unallocated - note this is not the same as the largest - * unallocated block on the heap! - */ - -#define UMM_INFO - -#ifdef UMM_INFO - typedef struct UMM_HEAP_INFO_t { - unsigned short int totalEntries; - unsigned short int usedEntries; - unsigned short int freeEntries; - - unsigned short int totalBlocks; - unsigned short int usedBlocks; - unsigned short int freeBlocks; - - unsigned short int maxFreeContiguousBlocks; - } - UMM_HEAP_INFO; - - extern UMM_HEAP_INFO ummHeapInfo; - - void *umm_info( void *ptr, int force ); - size_t umm_free_heap_size( void ); - -#else -#endif - -/* - * A couple of macros to make it easier to protect the memory allocator - * in a multitasking system. You should set these macros up to use whatever - * your system uses for this purpose. You can disable interrupts entirely, or - * just disable task switching - it's up to you - * - * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is - * called from within umm_malloc() - */ - -#define UMM_CRITICAL_ENTRY() -#define UMM_CRITICAL_EXIT() - -/* - * -D UMM_INTEGRITY_CHECK : - * - * Enables heap integrity check before any heap operation. It affects - * performance, but does NOT consume extra memory. - * - * If integrity violation is detected, the message is printed and user-provided - * callback is called: `UMM_HEAP_CORRUPTION_CB()` - * - * Note that not all buffer overruns are detected: each buffer is aligned by - * 4 bytes, so there might be some trailing "extra" bytes which are not checked - * for corruption. - */ - -#define UMM_INTEGRITY_CHECK - -#ifdef UMM_INTEGRITY_CHECK - int umm_integrity_check( void ); -# define INTEGRITY_CHECK() umm_integrity_check() - extern void umm_corruption(void); -# define UMM_HEAP_CORRUPTION_CB() printf( "Heap Corruption!" ) -#else -# define INTEGRITY_CHECK() 0 -#endif - -/* - * -D UMM_POISON : - * - * Enables heap poisoning: add predefined value (poison) before and after each - * allocation, and check before each heap operation that no poison is - * corrupted. - * - * Other than the poison itself, we need to store exact user-requested length - * for each buffer, so that overrun by just 1 byte will be always noticed. - * - * Customizations: - * - * UMM_POISON_SIZE_BEFORE: - * Number of poison bytes before each block, e.g. 2 - * UMM_POISON_SIZE_AFTER: - * Number of poison bytes after each block e.g. 2 - * UMM_POISONED_BLOCK_LEN_TYPE - * Type of the exact buffer length, e.g. `short` - * - * NOTE: each allocated buffer is aligned by 4 bytes. But when poisoning is - * enabled, actual pointer returned to user is shifted by - * `(sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE)`. - * It's your responsibility to make resulting pointers aligned appropriately. - * - * If poison corruption is detected, the message is printed and user-provided - * callback is called: `UMM_HEAP_CORRUPTION_CB()` - */ - -#define UMM_POISON_CHECK - -#define UMM_POISON_SIZE_BEFORE 4 -#define UMM_POISON_SIZE_AFTER 4 -#define UMM_POISONED_BLOCK_LEN_TYPE short - -#ifdef UMM_POISON_CHECK - void *umm_poison_malloc( size_t size ); - void *umm_poison_calloc( size_t num, size_t size ); - void *umm_poison_realloc( void *ptr, size_t size ); - void umm_poison_free( void *ptr ); - int umm_poison_check( void ); -# define POISON_CHECK() umm_poison_check() -#else -# define POISON_CHECK() 0 -#endif - -#endif /* _UMM_MALLOC_CFG_H */ diff --git a/test/test_umm_malloc.c b/test/test_umm_malloc.c index cc18003..c44c969 100644 --- a/test/test_umm_malloc.c +++ b/test/test_umm_malloc.c @@ -562,6 +562,230 @@ TEST_GROUP_RUNNER(Free) RUN_TEST_CASE(Free, LoHi); } +TEST_GROUP(Realloc); + +TEST_SETUP(Realloc) +{ + umm_init (); +} + +TEST_TEAR_DOWN(Realloc) +{ +} + +struct block_test_values ReallocTooBig_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} + }; + +TEST(Realloc, TooBig) +{ + void *foo = umm_malloc (4); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], foo ); + TEST_ASSERT_TRUE (check_blocks (ReallocTooBig_test_values, ARRAYELEMENTCOUNT(ReallocTooBig_test_values))); + + // Realloc with a request that is too big should return NULL and leave the original memory untouched. + + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_realloc (foo,UMM_MALLOC_CFG_HEAP_SIZE*2))); + TEST_ASSERT_TRUE (check_blocks (ReallocTooBig_test_values, ARRAYELEMENTCOUNT(ReallocTooBig_test_values))); +} + +struct block_test_values ReallocSameSize_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} + }; + +TEST(Realloc, SameSize) +{ + void *foo = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], foo ); + TEST_ASSERT_TRUE (check_blocks (ReallocSameSize_test_values, ARRAYELEMENTCOUNT(ReallocSameSize_test_values))); + + // Realloc with a request that is same size or block size should leave the original memory untouched. + + TEST_ASSERT_EQUAL_PTR((void *)foo, (umm_realloc (foo, 2))); + TEST_ASSERT_TRUE (check_blocks (ReallocSameSize_test_values, ARRAYELEMENTCOUNT(ReallocSameSize_test_values))); + + // Realloc with a request that is same size or block size should leave the original memory untouched. + + TEST_ASSERT_EQUAL_PTR((void *)foo, (umm_realloc (foo, 1))); + TEST_ASSERT_TRUE (check_blocks (ReallocSameSize_test_values, ARRAYELEMENTCOUNT(ReallocSameSize_test_values))); + + // Realloc with a request that is same size or block size should leave the original memory untouched. + + TEST_ASSERT_EQUAL_PTR((void *)foo, (umm_realloc (foo, 4))); + TEST_ASSERT_TRUE (check_blocks (ReallocSameSize_test_values, ARRAYELEMENTCOUNT(ReallocSameSize_test_values))); +} + +struct block_test_values ReallocFree_test_values[] = + { {0 , false, 1 , 0, 1, 1} + , {1 , true, UMM_LASTBLOCK, 0, 0, 0} + , {UMM_LASTBLOCK, false, 0 , 1, 0, 0} + }; + +TEST(Realloc, Free) +{ + void *foo = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], foo ); + + // Realloc with a request that is 0 size should free the block + + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_realloc (foo, 0))); + TEST_ASSERT_TRUE (check_blocks (ReallocFree_test_values, ARRAYELEMENTCOUNT(ReallocFree_test_values))); +} + +struct block_test_values ReallocFreeRealloc_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} + }; + +TEST(Realloc, FreeRealloc) +{ + void *foo = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], foo ); + TEST_ASSERT_TRUE (check_blocks (ReallocFreeRealloc_test_values, ARRAYELEMENTCOUNT(ReallocFreeRealloc_test_values))); + + // Realloc with a request that is 0 size should free the block + + TEST_ASSERT_EQUAL_PTR((void *)NULL, (umm_realloc (foo, 0))); + + // Realloc with a request that is same size or block size should leave the original memory untouched. + + TEST_ASSERT_EQUAL_PTR((void *)foo, (umm_realloc (NULL, 4))); + TEST_ASSERT_TRUE (check_blocks (ReallocSameSize_test_values, ARRAYELEMENTCOUNT(ReallocSameSize_test_values))); +} + +struct block_test_values ReallocAssimilateUp[] = + { {0 , false, 1 , 0, 4, 4} + , {1 , false, 2 , 0, 0, 0} + , {2 , false, 4 , 1, 0, 0} + , {4 , true, UMM_LASTBLOCK, 2, 0, 0} + , {UMM_LASTBLOCK, false, 0 , 4, 0, 0} + }; + +TEST(Realloc, AssimilateUp) +{ + void *mem0 = umm_malloc (2); + void *mem1 = umm_malloc (2); + void *mem2 = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], mem0 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[20], mem1 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[28], mem2 ); + + // Free the last block and then realloc the middle block to use it + + umm_free( mem2 ); + + TEST_ASSERT_EQUAL_PTR((void *)mem1, (umm_realloc (mem1, 5))); + TEST_ASSERT_TRUE (check_blocks (ReallocAssimilateUp, ARRAYELEMENTCOUNT(ReallocAssimilateUp))); +} + +struct block_test_values ReallocAssimilateDown[] = + { {0 , false, 1 , 0, 4, 4} + , {1 , false, 3 , 0, 0, 0} + , {3 , false, 4 , 1, 0, 0} + , {4 , true, UMM_LASTBLOCK, 3, 0, 0} + , {UMM_LASTBLOCK, false, 0 , 4, 0, 0} + }; + +TEST(Realloc, AssimilateDown) +{ + void *mem0 = umm_malloc (2); + void *mem1 = umm_malloc (2); + void *mem2 = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], mem0 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[20], mem1 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[28], mem2 ); + + // Free the last block and then realloc the middle block to use it + + umm_free( mem0 ); + + TEST_ASSERT_EQUAL_PTR((void *)mem0, (umm_realloc (mem1, 5))); + TEST_ASSERT_TRUE (check_blocks (ReallocAssimilateDown, ARRAYELEMENTCOUNT(ReallocAssimilateDown))); +} + +struct block_test_values ReallocAssimilateUpDown[] = + { {0 , false, 1 , 0, 5, 5} + , {1 , false, 4 , 0, 0, 0} + , {4 , false, 5 , 1, 0, 0} + , {5 , true, UMM_LASTBLOCK, 4, 0, 0} + , {UMM_LASTBLOCK, false, 0 , 5, 0, 0} + }; + +TEST(Realloc, AssimilateUpDown) +{ + void *mem0 = umm_malloc (2); + void *mem1 = umm_malloc (2); + void *mem2 = umm_malloc (2); + void *mem3 = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], mem0 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[20], mem1 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[28], mem2 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[36], mem3 ); + + // Free the last block and then realloc the middle block to use it + + umm_free( mem0 ); + umm_free( mem2 ); + + TEST_ASSERT_EQUAL_PTR((void *)mem0, (umm_realloc (mem1, 20))); + TEST_ASSERT_TRUE (check_blocks (ReallocAssimilateUpDown, ARRAYELEMENTCOUNT(ReallocAssimilateUpDown))); +} + +struct block_test_values ReallocNewBlock[] = + { {0 , false, 1 , 0, 2, 6} + , {1 , false, 2 , 0, 0, 0} + , {2 , true , 3 , 1, 6, 0} + , {3 , false, 4 , 2, 0, 0} + , {4 , false, 6 , 3, 0, 0} + , {6 , true, UMM_LASTBLOCK, 4, 0, 2} + , {UMM_LASTBLOCK, false, 0 , 6, 0, 0} + }; + +TEST(Realloc, NewBlock) +{ + void *mem0 = umm_malloc (2); + void *mem1 = umm_malloc (2); + void *mem2 = umm_malloc (2); + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[12], mem0 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[20], mem1 ); + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[28], mem2 ); + + /* Realloc the middle block - should need a totally new block */ + + TEST_ASSERT_EQUAL_PTR((void *)&test_umm_heap[36], (umm_realloc(mem1, 5))); + TEST_ASSERT_TRUE (check_blocks (ReallocNewBlock, ARRAYELEMENTCOUNT(ReallocNewBlock))); +} + +TEST_GROUP_RUNNER(Realloc) +{ + RUN_TEST_CASE(Realloc, TooBig); + RUN_TEST_CASE(Realloc, SameSize); + RUN_TEST_CASE(Realloc, Free); + RUN_TEST_CASE(Realloc, FreeRealloc); + RUN_TEST_CASE(Realloc, AssimilateUp); + RUN_TEST_CASE(Realloc, AssimilateDown); + RUN_TEST_CASE(Realloc, AssimilateUpDown); + RUN_TEST_CASE(Realloc, NewBlock); +} + + + TEST_GROUP(Poison); TEST_SETUP(Poison) @@ -746,6 +970,7 @@ static void runAllTests (void) RUN_TEST_GROUP(MultiMalloc); RUN_TEST_GROUP(Free); RUN_TEST_GROUP(Poison); + RUN_TEST_GROUP(Realloc); } int main (int argc, const char* argv[])