+
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");
+ }
+}