diff --git a/doc/xml/release.xml b/doc/xml/release.xml index 26a4e2adf..5e19fd06d 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -40,6 +40,10 @@

Page checksum module uses new C error handler.

+ + +

Add C memory contexts.

+
diff --git a/src/common/memContext.c b/src/common/memContext.c new file mode 100644 index 000000000..25d22b43a --- /dev/null +++ b/src/common/memContext.c @@ -0,0 +1,395 @@ +/*********************************************************************************************************************************** +Memory Context Manager +***********************************************************************************************************************************/ +#include +#include + +#include "common/errorType.h" +#include "common/memContext.h" + +/*********************************************************************************************************************************** +Memory context states +***********************************************************************************************************************************/ +typedef enum {memContextStateFree = 0, memContextStateFreeing, memContextStateActive} MemContextState; + +/*********************************************************************************************************************************** +Contains information about the memory context +***********************************************************************************************************************************/ +struct MemContext +{ + MemContextState state; // Current state of the context + const char name[MEM_CONTEXT_NAME_SIZE + 1]; // Indicates what the context is being used for + + MemContext *contextParent; // All contexts have a parent except top + + MemContext **contextChildList; // List of contexts created in this context + int contextChildListSize; // Size of child context list (not the actual count of contexts) + + void **allocList; // List of memory allocations created in this context + int allocListSize; // Size of alloc list (not the actual count of allocations) + + MemContextCallback callbackFunction; // Function to call before the context is freed + void *callbackArgument; // Argument to pass to callback function +}; + +/*********************************************************************************************************************************** +Top context + +The top context always exists and can never be freed. All other contexts are children of the top context. The top context is +generally used to allocate memory that exists for the life of the program. +***********************************************************************************************************************************/ +MemContext contextTop = {.state = memContextStateActive, .name = "TOP"}; + +/*********************************************************************************************************************************** +Current context + +All memory allocations will be done from the current context. Initialized to top context at execution start. +***********************************************************************************************************************************/ +MemContext *contextCurrent = &contextTop; + +/*********************************************************************************************************************************** +Wrapper around malloc() +***********************************************************************************************************************************/ +static void *memAllocInternal(size_t size, bool zero) +{ + // Allocate memory + void *buffer = malloc(size); + + // Error when malloc fails + if (!buffer) + ERROR_THROW(MemoryError, "unable to allocate %lu bytes", size); + + // Zero the memory when requested + if (zero) + memset(buffer, 0, size); + + // Return the buffer + return buffer; +} + +/*********************************************************************************************************************************** +Wrapper around realloc() +***********************************************************************************************************************************/ +static void *memReAllocInternal(void *bufferOld, size_t sizeOld, size_t sizeNew, bool zeroNew) +{ + // Allocate memory + void *bufferNew = realloc(bufferOld, sizeNew); + + // Error when realloc fails + if(!bufferNew) + ERROR_THROW(MemoryError, "unable to reallocate %lu bytes", sizeNew); + + // Zero the new memory when requested - old memory is left untouched else why bother with a realloc? + if (zeroNew) + memset(bufferNew + sizeOld, 0, sizeNew - sizeOld); + + // Return the buffer + return bufferNew; +} + +/*********************************************************************************************************************************** +Wrapper around free() +***********************************************************************************************************************************/ +static void memFreeInternal(void *buffer) +{ + // Error if pointer is null + if(!buffer) + ERROR_THROW(MemoryError, "unable to free null pointer"); + + // Free the buffer + free(buffer); +} + +/*********************************************************************************************************************************** +Create a new memory context +***********************************************************************************************************************************/ +MemContext * +memContextNew(const char *name) +{ + // Check context name length + if (strlen(name) == 0 || strlen(name) > MEM_CONTEXT_NAME_SIZE) + ERROR_THROW(AssertError, "context name length must be > 0 and <= %d", MEM_CONTEXT_NAME_SIZE); + + // Try to find space for the new context + int contextIdx; + + for (contextIdx = 0; contextIdx < memContextCurrent()->contextChildListSize; contextIdx++) + if (!memContextCurrent()->contextChildList[contextIdx] || + memContextCurrent()->contextChildList[contextIdx]->state == memContextStateFree) + { + break; + } + + // If no space was found then allocate more + if (contextIdx == memContextCurrent()->contextChildListSize) + { + // If no space has been allocated to the list + if (memContextCurrent()->contextChildListSize == 0) + { + // Allocate memory before modifying anything else in case there is an error + memContextCurrent()->contextChildList = memAllocInternal(sizeof(MemContext *) * MEM_CONTEXT_INITIAL_SIZE, true); + + // Set new list size + memContextCurrent()->contextChildListSize = MEM_CONTEXT_INITIAL_SIZE; + } + // Else grow the list + else + { + // Calculate new list size + int contextChildListSizeNew = memContextCurrent()->contextChildListSize * 2; + + // ReAllocate memory before modifying anything else in case there is an error + memContextCurrent()->contextChildList = memReAllocInternal( + memContextCurrent()->contextChildList, sizeof(MemContext *) * memContextCurrent()->contextChildListSize, + sizeof(MemContext *) * contextChildListSizeNew, true); + + // Set new list size + memContextCurrent()->contextChildListSize = contextChildListSizeNew; + } + } + + // If the context has not been allocated yet + if (!memContextCurrent()->contextChildList[contextIdx]) + memContextCurrent()->contextChildList[contextIdx] = memAllocInternal(sizeof(MemContext), true); + + // Get the context + MemContext *this = memContextCurrent()->contextChildList[contextIdx]; + + // Create initial space for allocations + this->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true); + this->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE; + + // Set the context name + strcpy((char *)this->name, name); + + // Set new context active + this->state = memContextStateActive; + + // Set current context as the parent + this->contextParent = memContextCurrent(); + + // Return context + return this; +} + +/*********************************************************************************************************************************** +Register a callback to be called just before the context is freed +***********************************************************************************************************************************/ +void +memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument) +{ + // Error if context is not active + if (this->state != memContextStateActive) + ERROR_THROW(AssertError, "cannot assign callback to inactive context"); + + // Top context cannot have a callback + if (this == memContextTop()) + ERROR_THROW(AssertError, "top context may not have a callback"); + + // Error if callback has already been set - there may be valid use cases for this but error until one is found + if (this->callbackFunction) + ERROR_THROW(AssertError, "callback is already set for context '%s'", this->name); + + // Set callback function and argument + this->callbackFunction = callbackFunction; + this->callbackArgument = callbackArgument; +} + +/*********************************************************************************************************************************** +Allocate memory in the memory context and optionally zero it. +***********************************************************************************************************************************/ +static void * +memContextAlloc(size_t size, bool zero) +{ + // Find space for the new allocation + int allocIdx; + + for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++) + if (!memContextCurrent()->allocList[allocIdx]) + break; + + // If no space was found then allocate more + if (allocIdx == memContextCurrent()->allocListSize) + { + // Only the top context will not have initial space for allocations + if (memContextCurrent()->allocListSize == 0) + { + // Allocate memory before modifying anything else in case there is an error + memContextCurrent()->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true); + + // Set new size + memContextCurrent()->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE; + } + // Else grow the list + else + { + // Calculate new list size + int allocListSizeNew = memContextCurrent()->allocListSize * 2; + + // ReAllocate memory before modifying anything else in case there is an error + memContextCurrent()->allocList = memReAllocInternal( + memContextCurrent()->allocList, sizeof(void *) * memContextCurrent()->contextChildListSize, + sizeof(MemContext *) * allocListSizeNew, true); + + // Set new size + memContextCurrent()->allocListSize = allocListSizeNew; + } + } + + // Allocate the memory + memContextCurrent()->allocList[allocIdx] = memAllocInternal(size, zero); + + // Return buffer + return memContextCurrent()->allocList[allocIdx]; +} + +/*********************************************************************************************************************************** +Allocate zeroed memory in the memory context +***********************************************************************************************************************************/ +void * +memNew(size_t size) +{ + return memContextAlloc(size, true); +} + +/*********************************************************************************************************************************** +Allocate memory in the memory context without initializing it +***********************************************************************************************************************************/ +void * +memNewRaw(size_t size) +{ + return memContextAlloc(size, false); +} + +/*********************************************************************************************************************************** +Free a memory allocation in the context +***********************************************************************************************************************************/ +void +memFree(void *buffer) +{ + // Error if buffer is null + if (!buffer) + ERROR_THROW(AssertError, "unable to free null allocation"); + + // Find memory to free + int allocIdx; + + for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++) + if (memContextCurrent()->allocList[allocIdx] == buffer) + break; + + // Error if the buffer was not found + if (allocIdx == memContextCurrent()->allocListSize) + ERROR_THROW(AssertError, "unable to find allocation"); + + // Free the buffer + memFreeInternal(memContextCurrent()->allocList[allocIdx]); + memContextCurrent()->allocList[allocIdx] = NULL; +} + +/*********************************************************************************************************************************** +Switch to the specified context and return the old context +***********************************************************************************************************************************/ +MemContext * +memContextSwitch(MemContext *this) +{ + // Error if context is not active + if (this->state != memContextStateActive) + ERROR_THROW(AssertError, "cannot switch to inactive context"); + + MemContext *memContextOld = memContextCurrent(); + contextCurrent = this; + + return memContextOld; +} + +/*********************************************************************************************************************************** +Return the top context +***********************************************************************************************************************************/ +MemContext * +memContextTop() +{ + return &contextTop; +} + +/*********************************************************************************************************************************** +Return the current context +***********************************************************************************************************************************/ +MemContext * +memContextCurrent() +{ + return contextCurrent; +} + +/*********************************************************************************************************************************** +Return the context name +***********************************************************************************************************************************/ +const char * +memContextName(MemContext *this) +{ + // Error if context is not active + if (this->state != memContextStateActive) + ERROR_THROW(AssertError, "cannot get name for inactive context"); + + return this->name; +} + +/*********************************************************************************************************************************** +memContextFree - free all memory used by the context and all child contexts +***********************************************************************************************************************************/ +void +memContextFree(MemContext *this) +{ + // If context is already freeing then return if memContextFree() is called again - this can happen in callbacks + if (this->state == memContextStateFreeing) + return; + + // Top context cannot be freed + if (this == memContextTop()) + ERROR_THROW(AssertError, "cannot free top context"); + + // Current context cannot be freed + if (this == memContextCurrent()) + ERROR_THROW(AssertError, "cannot free current context '%s'", this->name); + + // Error if context is not active + if (this->state != memContextStateActive) + ERROR_THROW(AssertError, "cannot free inactive context"); + + // Free child contexts + if (this->contextChildListSize > 0) + for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++) + if (this->contextChildList[contextIdx] && this->contextChildList[contextIdx]->state == memContextStateActive) + memContextFree(this->contextChildList[contextIdx]); + + // Set state to freeing now that there are no child contexts. Child contexts might need to interact with their parent while + // freeing so the parent needs to remain active until they are all gone. + this->state = memContextStateFreeing; + + // Execute callback if defined + if (this->callbackFunction) + this->callbackFunction(this->callbackArgument); + + // Free child context allocations + if (this->contextChildListSize > 0) + { + for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++) + if (this->contextChildList[contextIdx]) + memFreeInternal(this->contextChildList[contextIdx]); + + memFreeInternal(this->contextChildList); + } + + // Free memory allocations + if (this->allocListSize > 0) + { + for (int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++) + if (this->allocList[allocIdx]) + memFreeInternal(this->allocList[allocIdx]); + + memFreeInternal(this->allocList); + } + + // Reset the memory context so it can be used again + memset(this, 0, sizeof(MemContext)); +} diff --git a/src/common/memContext.h b/src/common/memContext.h new file mode 100644 index 000000000..985b7e767 --- /dev/null +++ b/src/common/memContext.h @@ -0,0 +1,152 @@ +/*********************************************************************************************************************************** +Memory Context Manager +***********************************************************************************************************************************/ +#ifndef MEM_CONTEXT_H +#define MEM_CONTEXT_H + +#include "common/error.h" +#include "common/type.h" + +/*********************************************************************************************************************************** +Memory context names cannot be larger than this size (excluding terminator) or an error will be thrown +***********************************************************************************************************************************/ +#define MEM_CONTEXT_NAME_SIZE 64 + +/*********************************************************************************************************************************** +Define initial number of memory contexts + +No space is reserved for child contexts when a new context is created because most contexts will be leaves. When a child context is +requested then space will be reserved for this many child contexts initially. When more space is needed the size will be doubled. +***********************************************************************************************************************************/ +#define MEM_CONTEXT_INITIAL_SIZE 4 + +/*********************************************************************************************************************************** +Define initial number of memory allocations + +Space is reserved for this many allocations when a context is created. When more space is needed the size will be doubled. +***********************************************************************************************************************************/ +#define MEM_CONTEXT_ALLOC_INITIAL_SIZE 4 + +/*********************************************************************************************************************************** +Memory context object +***********************************************************************************************************************************/ +typedef struct MemContext MemContext; + +/*********************************************************************************************************************************** +Memory context callback function type, useful for casts in memContextCallback() +***********************************************************************************************************************************/ +typedef void (*MemContextCallback)(void *callbackArgument); + +/*********************************************************************************************************************************** +Memory context management functions + +MemContext *context = memContextNew(); +MemContext *contextOld = memContextSwitch(context); + +ERROR_TRY() +{ + +} +ERROR_CATCH_ANY() +{ + + + memContextFree(context); + RETHROW(); +} +FINALLY +{ + memContextSwitch(context); +} + +Use the MEM_CONTEXT*() macros when possible rather than implement error-handling for every memory context block. +***********************************************************************************************************************************/ +MemContext *memContextNew(const char *name); +void memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument); +MemContext *memContextSwitch(MemContext *this); +void memContextFree(MemContext *this); + +/*********************************************************************************************************************************** +Memory context accessors +***********************************************************************************************************************************/ +MemContext *memContextCurrent(); +MemContext *memContextTop(); +const char *memContextName(MemContext *this); + +/*********************************************************************************************************************************** +Memory management + +These functions always new/free within the current memory context. +***********************************************************************************************************************************/ +void *memNew(size_t size); +void *memNewRaw(size_t size); +void memFree(void *buffer); + +/*********************************************************************************************************************************** +Ensure that the old memory context is restored after the block executes (even on error) + +MEM_CONTEXT_BEGIN(memContext) +{ + + +} +MEM_CONTEXT_END(); + + +***********************************************************************************************************************************/ +#define MEM_CONTEXT_BEGIN(memContext) \ +{ \ + /* Switch to the new memory context */ \ + MemContext *MEM_CONTEXT_memContextOld = memContextSwitch(memContext); \ + \ + /* Try the statement block */ \ + ERROR_TRY() + +#define MEM_CONTEXT_OLD() \ + MEM_CONTEXT_memContextOld + +#define MEM_CONTEXT_END() \ + /* Free the context on error */ \ + ERROR_FINALLY() \ + { \ + memContextSwitch(MEM_CONTEXT_OLD()); \ + } \ +} + +/*********************************************************************************************************************************** +Create a new context and make sure it is freed on error and old context is restored in all cases + +MEM_CONTEXT_NEW_BEGIN(memContextName) +{ + + + ObjectType *object = memNew(sizeof(ObjectType)); + object->memContext = MEM_CONTEXT_NEW(); + + + +} +MEM_CONTEXT_NEW_END(); + + +***********************************************************************************************************************************/ +#define MEM_CONTEXT_NEW_BEGIN(memContextName) \ +{ \ + MemContext *MEM_CONTEXT_NEW_BEGIN_memContext = memContextNew(memContextName); \ + \ + MEM_CONTEXT_BEGIN(MEM_CONTEXT_NEW_BEGIN_memContext) \ + +#define MEM_CONTEXT_NEW() \ + MEM_CONTEXT_NEW_BEGIN_memContext + +#define MEM_CONTEXT_NEW_END() \ + ERROR_CATCH_ANY() \ + { \ + memContextSwitch(MEM_CONTEXT_OLD()); \ + memContextFree(MEM_CONTEXT_NEW()); \ + ERROR_RETHROW(); \ + } \ + MEM_CONTEXT_END(); \ +} + +#endif diff --git a/test/lib/pgBackRestTest/Common/DefineTest.pm b/test/lib/pgBackRestTest/Common/DefineTest.pm index f255865e3..39ba2f292 100644 --- a/test/lib/pgBackRestTest/Common/DefineTest.pm +++ b/test/lib/pgBackRestTest/Common/DefineTest.pm @@ -118,6 +118,16 @@ my $oTestDef = 'common/errorType' => TESTDEF_COVERAGE_FULL, }, }, + { + &TESTDEF_NAME => 'mem-context', + &TESTDEF_TOTAL => 6, + &TESTDEF_C => true, + + &TESTDEF_COVERAGE => + { + 'common/memContext' => TESTDEF_COVERAGE_FULL, + }, + }, { &TESTDEF_NAME => 'http-client', &TESTDEF_TOTAL => 2, diff --git a/test/src/module/common/memContextTest.c b/test/src/module/common/memContextTest.c new file mode 100644 index 000000000..b2a07bd5a --- /dev/null +++ b/test/src/module/common/memContextTest.c @@ -0,0 +1,285 @@ +/*********************************************************************************************************************************** +Test Memory Contexts +***********************************************************************************************************************************/ + +/*********************************************************************************************************************************** +testFree - test callback function +***********************************************************************************************************************************/ +MemContext *memContextCallbackArgument = NULL; + +void testFree(MemContext *this) +{ + TEST_RESULT_INT(this->state, memContextStateFreeing, "state should be freeing before memContextFree() in callback"); + memContextFree(this); + TEST_RESULT_INT(this->state, memContextStateFreeing, "state should still be freeing after memContextFree() in callback"); + + TEST_ERROR( + memContextCallback(this, (MemContextCallback)testFree, this), + AssertError, "cannot assign callback to inactive context"); + + TEST_ERROR(memContextSwitch(this), AssertError, "cannot switch to inactive context"); + TEST_ERROR(memContextName(this), AssertError, "cannot get name for inactive context"); + + memContextCallbackArgument = this; +} + +/*********************************************************************************************************************************** +Test Run +***********************************************************************************************************************************/ +void testRun() +{ + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("memAllocInternal(), memReAllocInternal(), and memFreeInternal()")) + { + TEST_ERROR( + memAllocInternal(-1, false), MemoryError, + sizeof(size_t) == 8 ? "unable to allocate 18446744073709551615 bytes" : "unable to allocate 4294967295 bytes"); + + TEST_ERROR(memFreeInternal(NULL), MemoryError, "unable to free null pointer"); + + // Check that bad realloc is caught + void *buffer = memAllocInternal(sizeof(size_t), false); + TEST_ERROR( + memReAllocInternal(buffer, sizeof(size_t), -1, false), MemoryError, + sizeof(size_t) == 8 ? "unable to reallocate 18446744073709551615 bytes" : "unable to reallocate 4294967295 bytes"); + memFreeInternal(buffer); + + // Normal memory allocation + buffer = memAllocInternal(sizeof(size_t), false); + buffer = memReAllocInternal(buffer, sizeof(size_t), sizeof(size_t) * 2, false); + memFreeInternal(buffer); + + // Zeroed memory allocation + unsigned char *buffer2 = memAllocInternal(sizeof(size_t), true); + int expectedTotal = 0; + + for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++) + if (buffer2[charIdx] == 0) + expectedTotal++; + + TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0"); + + // Zeroed memory reallocation + memset(buffer2, 0xC7, sizeof(size_t)); + + buffer2 = memReAllocInternal(buffer2, sizeof(size_t), sizeof(size_t) * 2, true); + + expectedTotal = 0; + + for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++) + if (buffer2[charIdx] == 0xC7) + expectedTotal++; + + TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all old bytes are filled"); + + expectedTotal = 0; + + for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++) + if ((buffer2 + sizeof(size_t))[charIdx] == 0) + expectedTotal++; + + TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all new bytes are 0"); + } + + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("memContextNew() and memContextFree()")) + { + // Make sure top context was created + TEST_RESULT_STR(memContextName(memContextTop()), "TOP", "top context should exist"); + TEST_RESULT_INT(memContextTop()->contextChildListSize, 0, "top context should init with zero children"); + TEST_RESULT_PTR(memContextTop()->contextChildList, NULL, "top context child list empty"); + + TEST_ERROR(memContextFree(memContextTop()), AssertError, "cannot free top context"); + + // Current context should equal top context + TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "top context == current context"); + + // Context name length errors + TEST_ERROR(memContextNew(""), AssertError, "context name length must be > 0 and <= 64"); + TEST_ERROR( + memContextNew("12345678901234567890123456789012345678901234567890123456789012345"), + AssertError, "context name length must be > 0 and <= 64"); + + MemContext *memContext = memContextNew("test1"); + TEST_RESULT_STR(memContextName(memContext), "test1", "test1 context name"); + TEST_RESULT_PTR(memContext->contextParent, memContextTop(), "test1 context parent is top"); + TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE, "initial top context child list size"); + + TEST_RESULT_PTR(memContextSwitch(memContext), memContextTop(), "switch returns top as old context"); + TEST_RESULT_PTR(memContext, memContextCurrent(), "current context is now test1"); + + // Create enough mem contexts to use up the initially allocated block + for (int contextIdx = 1; contextIdx < MEM_CONTEXT_INITIAL_SIZE; contextIdx++) + { + memContextSwitch(memContextTop()); + memContextNew("test-filler"); + TEST_RESULT_BOOL( + memContextTop()->contextChildList[contextIdx]->state == memContextStateActive, true, "new context is active"); + TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[contextIdx]), "test-filler", "new contest name"); + } + + // This forces the child context array to grow + memContextNew("test5"); + TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE * 2, "increased child context list size"); + + // Free a context + memContextFree(memContextTop()->contextChildList[1]); + TEST_RESULT_BOOL( + memContextTop()->contextChildList[1]->state == memContextStateActive, false, "child context inactive after free"); + TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "current context is top"); + + // Create a new context and it should end up in the same spot + memContextNew("test-reuse"); + TEST_RESULT_BOOL( + memContextTop()->contextChildList[1]->state == memContextStateActive, + true, "new context in same index as freed context is active"); + TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[1]), "test-reuse", "new context name"); + + // Create a child context to test recursive free + memContextSwitch(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]); + memContextNew("test-reuse"); + TEST_RESULT_PTR_NE( + memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildList, NULL, "context child list is allocated"); + TEST_RESULT_INT( + memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE, + "context child list initial size"); + + TEST_ERROR( + memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]), + AssertError, "cannot free current context 'test5'"); + + memContextSwitch(memContextTop()); + memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]); + + TEST_ERROR( + memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]), + AssertError, "cannot free inactive context"); + } + + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("memContextAlloc(), memNew*(), and memFree()")) + { + memContextSwitch(memContextTop()); + memNew(sizeof(size_t)); + + MemContext *memContext = memContextNew("test-alloc"); + memContextSwitch(memContext); + + for (int allocIdx = 0; allocIdx <= MEM_CONTEXT_ALLOC_INITIAL_SIZE; allocIdx++) + { + unsigned char *buffer = memNew(sizeof(size_t)); + + // Check that the buffer is zeroed + int expectedTotal = 0; + + for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++) + if (buffer[charIdx] == 0) + expectedTotal++; + + TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0"); + + TEST_RESULT_INT( + memContextCurrent()->allocListSize, + allocIdx == MEM_CONTEXT_ALLOC_INITIAL_SIZE ? MEM_CONTEXT_ALLOC_INITIAL_SIZE * 2 : MEM_CONTEXT_ALLOC_INITIAL_SIZE, + "allocation list size"); + } + + + unsigned char *buffer= memNewRaw(sizeof(size_t)); + + TEST_ERROR(memFree(NULL), AssertError, "unable to free null allocation"); + TEST_ERROR(memFree((void *)0x01), AssertError, "unable to find allocation"); + memFree(buffer); + + memContextSwitch(memContextTop()); + memContextFree(memContext); + } + + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("memContextCallback()")) + { + TEST_ERROR(memContextCallback(memContextTop(), NULL, NULL), AssertError, "top context may not have a callback"); + + MemContext *memContext = memContextNew("test-callback"); + memContextCallback(memContext, (MemContextCallback)testFree, memContext); + TEST_ERROR( + memContextCallback(memContext, (MemContextCallback)testFree, memContext), + AssertError, "callback is already set for context 'test-callback'"); + + memContextFree(memContext); + TEST_RESULT_PTR(memContextCallbackArgument, memContext, "callback argument is context"); + } + + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("MEM_CONTEXT_BEGIN() and MEM_CONTEXT_END()")) + { + memContextSwitch(memContextTop()); + MemContext *memContext = memContextNew("test-block"); + + // Check normal block + MEM_CONTEXT_BEGIN(memContext) + { + TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block"); + } + MEM_CONTEXT_END(); + + TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top"); + + // Check block that errors + TEST_ERROR( + MEM_CONTEXT_BEGIN(memContext) + { + TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block"); + ERROR_THROW(AssertError, "error in test block"); + } + MEM_CONTEXT_END(), + AssertError, "error in test block"); + + TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top"); + } + + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("MEM_CONTEXT_NEW_BEGIN() and MEM_CONTEXT_NEW_END()")) + { + // ------------------------------------------------------------------------------------------------------------------------ + // Successful context new block + char *memContextTestName = "test-new-block"; + MemContext *memContext; + + MEM_CONTEXT_NEW_BEGIN(memContextTestName) + { + memContext = MEM_CONTEXT_NEW(); + TEST_RESULT_PTR(memContext, memContextCurrent(), "new mem context is current"); + TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName); + } + MEM_CONTEXT_NEW_END(); + + TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'"); + TEST_RESULT_BOOL(memContext->state == memContextStateActive, true, "new mem context is still active"); + memContextFree(memContext); + + // ------------------------------------------------------------------------------------------------------------------------ + // Failed context new block + memContextTestName = "test-new-failed-block"; + bool bCatch = false; + + ERROR_TRY() + { + MEM_CONTEXT_NEW_BEGIN(memContextTestName) + { + memContext = MEM_CONTEXT_NEW(); + TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName); + ERROR_THROW(AssertError, "create failed"); + } + MEM_CONTEXT_NEW_END(); + } + ERROR_CATCH(AssertError) + { + bCatch = true; + } + + TEST_RESULT_BOOL(bCatch, true, "new context error was caught"); + TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'"); + TEST_RESULT_BOOL(memContext->state == memContextStateFree, true, "new mem context is not active"); + } +}