1 /***********************************************************************************************************************************
2 Memory Context Manager
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <stdlib.h>
7 #include <string.h>
8 
9 #include "common/debug.h"
10 #include "common/error.h"
11 #include "common/memContext.h"
12 
13 /***********************************************************************************************************************************
14 Memory context states
15 ***********************************************************************************************************************************/
16 typedef enum
17 {
18     memContextStateFree = 0,
19     memContextStateFreeing,
20     memContextStateActive
21 } MemContextState;
22 
23 /***********************************************************************************************************************************
24 Contains information about a memory allocation. This header is placed at the beginning of every memory allocation returned to the
25 user by memNew(), etc. The advantage is that when an allocation is passed back by the user we know the location of the allocation
26 header by doing some pointer arithmetic. This is much faster than searching through a list.
27 ***********************************************************************************************************************************/
28 typedef struct MemContextAlloc
29 {
30     unsigned int allocIdx:32;                                       // Index in the allocation list
31     unsigned int size:32;                                           // Allocation size (4GB max)
32 } MemContextAlloc;
33 
34 // Get the allocation buffer pointer given the allocation header pointer
35 #define MEM_CONTEXT_ALLOC_BUFFER(header)                            ((MemContextAlloc *)header + 1)
36 
37 // Get the allocation header pointer given the allocation buffer pointer
38 #define MEM_CONTEXT_ALLOC_HEADER(buffer)                            ((MemContextAlloc *)buffer - 1)
39 
40 // Make sure the allocation is valid for the current memory context.  This check only works correctly if the allocation is valid but
41 // belongs to another context.  Otherwise, there is likely to be a segfault.
42 #define ASSERT_ALLOC_VALID(alloc)                                                                                                  \
43     ASSERT(                                                                                                                        \
44         alloc != NULL && (uintptr_t)alloc != (uintptr_t)-sizeof(MemContextAlloc) &&                                                \
45         alloc->allocIdx < memContextStack[memContextCurrentStackIdx].memContext->allocListSize &&                                  \
46         memContextStack[memContextCurrentStackIdx].memContext->allocList[alloc->allocIdx]);
47 
48 /***********************************************************************************************************************************
49 Contains information about the memory context
50 ***********************************************************************************************************************************/
51 struct MemContext
52 {
53     const char *name;                                               // Indicates what the context is being used for
54     MemContextState state;                                          // Current state of the context
55 
56     unsigned int contextParentIdx;                                  // Index in the parent context list
57     MemContext *contextParent;                                      // All contexts have a parent except top
58 
59     MemContext **contextChildList;                                  // List of contexts created in this context
60     unsigned int contextChildListSize;                              // Size of child context list (not the actual count of contexts)
61     unsigned int contextChildFreeIdx;                               // Index of first free space in the context list
62 
63     MemContextAlloc **allocList;                                    // List of memory allocations created in this context
64     unsigned int allocListSize;                                     // Size of alloc list (not the actual count of allocations)
65     unsigned int allocFreeIdx;                                      // Index of first free space in the alloc list
66 
67     void (*callbackFunction)(void *);                               // Function to call before the context is freed
68     void *callbackArgument;                                         // Argument to pass to callback function
69 };
70 
71 /***********************************************************************************************************************************
72 Top context
73 
74 The top context always exists and can never be freed.  All other contexts are children of the top context. The top context is
75 generally used to allocate memory that exists for the life of the program.
76 ***********************************************************************************************************************************/
77 static MemContext contextTop = {.state = memContextStateActive, .name = "TOP"};
78 
79 /***********************************************************************************************************************************
80 Memory context stack types
81 ***********************************************************************************************************************************/
82 typedef enum
83 {
84     memContextStackTypeSwitch = 0,                                  // Context can be switched to allocate mem for new variables
85     memContextStackTypeNew,                                         // Context to be tracked for error handling - cannot switch to
86 } MemContextStackType;
87 
88 /***********************************************************************************************************************************
89 Mem context stack used to pop mem contexts and cleanup after an error
90 ***********************************************************************************************************************************/
91 #define MEM_CONTEXT_STACK_MAX                                       128
92 
93 static struct MemContextStack
94 {
95     MemContext *memContext;
96     MemContextStackType type;
97     unsigned int tryDepth;
98 } memContextStack[MEM_CONTEXT_STACK_MAX] = {{.memContext = &contextTop}};
99 
100 static unsigned int memContextCurrentStackIdx = 0;
101 static unsigned int memContextMaxStackIdx = 0;
102 
103 /***********************************************************************************************************************************
104 Wrapper around malloc() with error handling
105 ***********************************************************************************************************************************/
106 static void *
memAllocInternal(size_t size)107 memAllocInternal(size_t size)
108 {
109     FUNCTION_TEST_BEGIN();
110         FUNCTION_TEST_PARAM(SIZE, size);
111     FUNCTION_TEST_END();
112 
113     // Allocate memory
114     void *buffer = malloc(size);
115 
116     // Error when malloc fails
117     if (buffer == NULL)
118         THROW_FMT(MemoryError, "unable to allocate %zu bytes", size);
119 
120     // Return the buffer
121     FUNCTION_TEST_RETURN(buffer);
122 }
123 
124 /***********************************************************************************************************************************
125 Allocate an array of pointers and set all entries to NULL
126 ***********************************************************************************************************************************/
127 static void *
memAllocPtrArrayInternal(size_t size)128 memAllocPtrArrayInternal(size_t size)
129 {
130     FUNCTION_TEST_BEGIN();
131         FUNCTION_TEST_PARAM(SIZE, size);
132     FUNCTION_TEST_END();
133 
134     // Allocate memory
135     void **buffer = memAllocInternal(size * sizeof(void *));
136 
137     // Set all pointers to NULL
138     for (size_t ptrIdx = 0; ptrIdx < size; ptrIdx++)
139         buffer[ptrIdx] = NULL;
140 
141     // Return the buffer
142     FUNCTION_TEST_RETURN(buffer);
143 }
144 
145 /***********************************************************************************************************************************
146 Wrapper around realloc() with error handling
147 ***********************************************************************************************************************************/
148 static void *
memReAllocInternal(void * bufferOld,size_t sizeNew)149 memReAllocInternal(void *bufferOld, size_t sizeNew)
150 {
151     FUNCTION_TEST_BEGIN();
152         FUNCTION_TEST_PARAM_P(VOID, bufferOld);
153         FUNCTION_TEST_PARAM(SIZE, sizeNew);
154     FUNCTION_TEST_END();
155 
156     ASSERT(bufferOld != NULL);
157 
158     // Allocate memory
159     void *bufferNew = realloc(bufferOld, sizeNew);
160 
161     // Error when realloc fails
162     if (bufferNew == NULL)
163         THROW_FMT(MemoryError, "unable to reallocate %zu bytes", sizeNew);
164 
165     // Return the buffer
166     FUNCTION_TEST_RETURN(bufferNew);
167 }
168 
169 /***********************************************************************************************************************************
170 Wrapper around realloc() with error handling
171 ***********************************************************************************************************************************/
172 static void *
memReAllocPtrArrayInternal(void * bufferOld,size_t sizeOld,size_t sizeNew)173 memReAllocPtrArrayInternal(void *bufferOld, size_t sizeOld, size_t sizeNew)
174 {
175     FUNCTION_TEST_BEGIN();
176         FUNCTION_TEST_PARAM_P(VOID, bufferOld);
177         FUNCTION_TEST_PARAM(SIZE, sizeOld);
178         FUNCTION_TEST_PARAM(SIZE, sizeNew);
179     FUNCTION_TEST_END();
180 
181     // Allocate memory
182     void **bufferNew = memReAllocInternal(bufferOld, sizeNew * sizeof(void *));
183 
184     // Set all new pointers to NULL
185     for (size_t ptrIdx = sizeOld; ptrIdx < sizeNew; ptrIdx++)
186         bufferNew[ptrIdx] = NULL;
187 
188     // Return the buffer
189     FUNCTION_TEST_RETURN(bufferNew);
190 }
191 
192 /***********************************************************************************************************************************
193 Wrapper around free()
194 ***********************************************************************************************************************************/
195 static void
memFreeInternal(void * buffer)196 memFreeInternal(void *buffer)
197 {
198     FUNCTION_TEST_BEGIN();
199         FUNCTION_TEST_PARAM_P(VOID, buffer);
200     FUNCTION_TEST_END();
201 
202     ASSERT(buffer != NULL);
203 
204     free(buffer);
205 
206     FUNCTION_TEST_RETURN_VOID();
207 }
208 
209 /***********************************************************************************************************************************
210 Find space for a new mem context
211 ***********************************************************************************************************************************/
212 static unsigned int
memContextNewIndex(MemContext * memContext,bool allowFree)213 memContextNewIndex(MemContext *memContext, bool allowFree)
214 {
215     FUNCTION_TEST_BEGIN();
216         FUNCTION_TEST_PARAM(MEM_CONTEXT, memContext);
217         FUNCTION_TEST_PARAM(BOOL, allowFree);
218     FUNCTION_TEST_END();
219 
220     ASSERT(memContext != NULL);
221 
222     // Try to find space for the new context
223     for (; memContext->contextChildFreeIdx < memContext->contextChildListSize; memContext->contextChildFreeIdx++)
224     {
225         if (memContext->contextChildList[memContext->contextChildFreeIdx] == NULL ||
226             (allowFree && memContext->contextChildList[memContext->contextChildFreeIdx]->state == memContextStateFree))
227         {
228             break;
229         }
230     }
231 
232     // If no space was found then allocate more
233     if (memContext->contextChildFreeIdx == memContext->contextChildListSize)
234     {
235         // If no space has been allocated to the list
236         if (memContext->contextChildListSize == 0)
237         {
238             // Allocate memory before modifying anything else in case there is an error
239             memContext->contextChildList = memAllocPtrArrayInternal(MEM_CONTEXT_INITIAL_SIZE);
240 
241             // Set new list size
242             memContext->contextChildListSize = MEM_CONTEXT_INITIAL_SIZE;
243         }
244         // Else grow the list
245         else
246         {
247             // Calculate new list size
248             unsigned int contextChildListSizeNew = memContext->contextChildListSize * 2;
249 
250             // ReAllocate memory before modifying anything else in case there is an error
251             memContext->contextChildList = memReAllocPtrArrayInternal(
252                 memContext->contextChildList, memContext->contextChildListSize, contextChildListSizeNew);
253 
254             // Set new list size
255             memContext->contextChildListSize = contextChildListSizeNew;
256         }
257     }
258 
259     FUNCTION_TEST_RETURN(memContext->contextChildFreeIdx);
260 }
261 
262 /**********************************************************************************************************************************/
263 MemContext *
memContextNew(const char * name)264 memContextNew(const char *name)
265 {
266     FUNCTION_TEST_BEGIN();
267         FUNCTION_TEST_PARAM(STRINGZ, name);
268     FUNCTION_TEST_END();
269 
270     ASSERT(name != NULL);
271 
272     // Check context name length
273     ASSERT(name[0] != '\0');
274 
275     // Find space for the new context
276     MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
277     unsigned int contextIdx = memContextNewIndex(contextCurrent, true);
278 
279     // If the context has not been allocated yet
280     if (contextCurrent->contextChildList[contextIdx] == NULL)
281         contextCurrent->contextChildList[contextIdx] = memAllocInternal(sizeof(MemContext));
282 
283     // Get the context
284     MemContext *this = contextCurrent->contextChildList[contextIdx];
285 
286     *this = (MemContext)
287     {
288         // Create initial space for allocations
289         .allocList = memAllocPtrArrayInternal(MEM_CONTEXT_ALLOC_INITIAL_SIZE),
290         .allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE,
291 
292         // Set the context name
293         .name = name,
294 
295         // Set new context active
296         .state = memContextStateActive,
297 
298         // Set current context as the parent
299         .contextParent = contextCurrent,
300         .contextParentIdx = contextIdx,
301     };
302 
303     // Possible free context must be in the next position
304     contextCurrent->contextChildFreeIdx++;
305 
306     // Add to the mem context stack so it will be automatically freed on error if memContextKeep() has not been called
307     memContextMaxStackIdx++;
308 
309     memContextStack[memContextMaxStackIdx] = (struct MemContextStack)
310     {
311         .memContext = this,
312         .type = memContextStackTypeNew,
313         .tryDepth = errorTryDepth(),
314     };
315 
316     // Return context
317     FUNCTION_TEST_RETURN(this);
318 }
319 
320 /**********************************************************************************************************************************/
321 void
memContextCallbackSet(MemContext * this,void (* callbackFunction)(void *),void * callbackArgument)322 memContextCallbackSet(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument)
323 {
324     FUNCTION_TEST_BEGIN();
325         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
326         FUNCTION_TEST_PARAM(FUNCTIONP, callbackFunction);
327         FUNCTION_TEST_PARAM_P(VOID, callbackArgument);
328     FUNCTION_TEST_END();
329 
330     ASSERT(this != NULL);
331     ASSERT(callbackFunction != NULL);
332 
333     // Error if context is not active
334     if (this->state != memContextStateActive)
335         THROW(AssertError, "cannot assign callback to inactive context");
336 
337     // Top context cannot have a callback
338     if (this == &contextTop)
339         THROW(AssertError, "top context may not have a callback");
340 
341     // Error if callback has already been set - there may be valid use cases for this but error until one is found
342     if (this->callbackFunction)
343         THROW_FMT(AssertError, "callback is already set for context '%s'", this->name);
344 
345     // Set callback function and argument
346     this->callbackFunction = callbackFunction;
347     this->callbackArgument = callbackArgument;
348 
349     FUNCTION_TEST_RETURN_VOID();
350 }
351 
352 /**********************************************************************************************************************************/
353 void
memContextCallbackClear(MemContext * this)354 memContextCallbackClear(MemContext *this)
355 {
356     FUNCTION_TEST_BEGIN();
357         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
358     FUNCTION_TEST_END();
359 
360     ASSERT(this != NULL);
361 
362     // Error if context is not active or freeing
363     ASSERT(this->state == memContextStateActive || this->state == memContextStateFreeing);
364 
365     // Top context cannot have a callback
366     ASSERT(this != &contextTop);
367 
368     // Clear callback function and argument
369     this->callbackFunction = NULL;
370     this->callbackArgument = NULL;
371 
372     FUNCTION_TEST_RETURN_VOID();
373 }
374 
375 /***********************************************************************************************************************************
376 Find an available slot in the memory context's allocation list and allocate memory
377 ***********************************************************************************************************************************/
378 static MemContextAlloc *
memContextAllocNew(size_t size)379 memContextAllocNew(size_t size)
380 {
381     FUNCTION_TEST_BEGIN();
382         FUNCTION_TEST_PARAM(SIZE, size);
383     FUNCTION_TEST_END();
384 
385     // Find space for the new allocation
386     MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
387 
388     for (; contextCurrent->allocFreeIdx < contextCurrent->allocListSize; contextCurrent->allocFreeIdx++)
389         if (contextCurrent->allocList[contextCurrent->allocFreeIdx] == NULL)
390             break;
391 
392     // If no space was found then allocate more
393     if (contextCurrent->allocFreeIdx == contextCurrent->allocListSize)
394     {
395         // Only the top context will not have initial space for allocations
396         if (contextCurrent->allocListSize == 0)
397         {
398             // Allocate memory before modifying anything else in case there is an error
399             contextCurrent->allocList = memAllocPtrArrayInternal(MEM_CONTEXT_ALLOC_INITIAL_SIZE);
400 
401             // Set new size
402             contextCurrent->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
403         }
404         // Else grow the list
405         else
406         {
407             // Calculate new list size
408             unsigned int allocListSizeNew = contextCurrent->allocListSize * 2;
409 
410             // Reallocate memory before modifying anything else in case there is an error
411             contextCurrent->allocList = memReAllocPtrArrayInternal(
412                 contextCurrent->allocList, contextCurrent->allocListSize, allocListSizeNew);
413 
414             // Set new size
415             contextCurrent->allocListSize = allocListSizeNew;
416         }
417     }
418 
419     // Create new allocation
420     MemContextAlloc *result = memAllocInternal(sizeof(MemContextAlloc) + size);
421 
422     *result = (MemContextAlloc)
423     {
424         .allocIdx = contextCurrent->allocFreeIdx,
425         .size = (unsigned int)(sizeof(MemContextAlloc) + size),
426     };
427 
428     // Set pointer in allocation list
429     contextCurrent->allocList[contextCurrent->allocFreeIdx] = result;
430 
431     // Update free index to next location. This location may not actually be free but it is where the search should start next time.
432     contextCurrent->allocFreeIdx++;
433 
434     FUNCTION_TEST_RETURN(result);
435 }
436 
437 /***********************************************************************************************************************************
438 Resize memory that has already been allocated
439 ***********************************************************************************************************************************/
440 static MemContextAlloc *
memContextAllocResize(MemContextAlloc * alloc,size_t size)441 memContextAllocResize(MemContextAlloc *alloc, size_t size)
442 {
443     FUNCTION_TEST_BEGIN();
444         FUNCTION_TEST_PARAM_P(VOID, alloc);
445         FUNCTION_TEST_PARAM(SIZE, size);
446     FUNCTION_TEST_END();
447 
448     ASSERT_ALLOC_VALID(alloc);
449 
450     // Resize the allocation
451     alloc = memReAllocInternal(alloc, sizeof(MemContextAlloc) + size);
452     alloc->size = (unsigned int)(sizeof(MemContextAlloc) + size);
453 
454     // Update pointer in allocation list in case the realloc moved the allocation
455     memContextStack[memContextCurrentStackIdx].memContext->allocList[alloc->allocIdx] = alloc;
456 
457     FUNCTION_TEST_RETURN(alloc);
458 }
459 
460 /**********************************************************************************************************************************/
461 void *
memNew(size_t size)462 memNew(size_t size)
463 {
464     FUNCTION_TEST_BEGIN();
465         FUNCTION_TEST_PARAM(SIZE, size);
466     FUNCTION_TEST_END();
467 
468     FUNCTION_TEST_RETURN(MEM_CONTEXT_ALLOC_BUFFER(memContextAllocNew(size)));
469 }
470 
471 /**********************************************************************************************************************************/
472 void *
memNewPtrArray(size_t size)473 memNewPtrArray(size_t size)
474 {
475     FUNCTION_TEST_BEGIN();
476         FUNCTION_TEST_PARAM(SIZE, size);
477     FUNCTION_TEST_END();
478 
479     // Allocate pointer array
480     void **buffer = (void **)MEM_CONTEXT_ALLOC_BUFFER(memContextAllocNew(size * sizeof(void *)));
481 
482     // Set pointers to NULL
483     for (size_t ptrIdx = 0; ptrIdx < size; ptrIdx++)
484         buffer[ptrIdx] = NULL;
485 
486     FUNCTION_TEST_RETURN(buffer);
487 }
488 
489 /**********************************************************************************************************************************/
490 void *
memResize(const void * buffer,size_t size)491 memResize(const void *buffer, size_t size)
492 {
493     FUNCTION_TEST_BEGIN();
494         FUNCTION_TEST_PARAM_P(VOID, buffer);
495         FUNCTION_TEST_PARAM(SIZE, size);
496     FUNCTION_TEST_END();
497 
498     FUNCTION_TEST_RETURN(MEM_CONTEXT_ALLOC_BUFFER(memContextAllocResize(MEM_CONTEXT_ALLOC_HEADER(buffer), size)));
499 }
500 
501 /**********************************************************************************************************************************/
502 void
memFree(void * buffer)503 memFree(void *buffer)
504 {
505     FUNCTION_TEST_BEGIN();
506         FUNCTION_TEST_PARAM_P(VOID, buffer);
507     FUNCTION_TEST_END();
508 
509     ASSERT_ALLOC_VALID(MEM_CONTEXT_ALLOC_HEADER(buffer));
510 
511     // Get the allocation
512     MemContext *contextCurrent = memContextStack[memContextCurrentStackIdx].memContext;
513     MemContextAlloc *alloc = MEM_CONTEXT_ALLOC_HEADER(buffer);
514 
515     // If this allocation is before the current free allocation then make it the current free allocation
516     if (alloc->allocIdx < contextCurrent->allocFreeIdx)
517         contextCurrent->allocFreeIdx = alloc->allocIdx;
518 
519     // Free the allocation
520     contextCurrent->allocList[alloc->allocIdx] = NULL;
521     memFreeInternal(alloc);
522 
523     FUNCTION_TEST_RETURN_VOID();
524 }
525 
526 /**********************************************************************************************************************************/
527 void
memContextMove(MemContext * this,MemContext * parentNew)528 memContextMove(MemContext *this, MemContext *parentNew)
529 {
530     FUNCTION_TEST_BEGIN();
531         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
532         FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
533     FUNCTION_TEST_END();
534 
535     ASSERT(parentNew != NULL);
536 
537     // Only move if a valid mem context is provided and the old and new parents are not the same
538     if (this != NULL && this->contextParent != parentNew)
539     {
540         // Find context in the old parent and NULL it out
541         MemContext *parentOld = this->contextParent;
542         unsigned int contextIdx;
543 
544         for (contextIdx = 0; contextIdx < parentOld->contextChildListSize; contextIdx++)
545         {
546             if (parentOld->contextChildList[contextIdx] == this)
547             {
548                 parentOld->contextChildList[contextIdx] = NULL;
549                 break;
550             }
551         }
552 
553         // The memory must be found
554         if (contextIdx == parentOld->contextChildListSize)
555             THROW(AssertError, "unable to find mem context in old parent");
556 
557         // Find a place in the new parent context and assign it. The child list may be moved while finding a new index so store the
558         // index and use it with (what might be) the new pointer.
559         contextIdx = memContextNewIndex(parentNew, false);
560         ASSERT(parentNew->contextChildList[contextIdx] == NULL);
561         parentNew->contextChildList[contextIdx] = this;
562 
563         // Assign new parent
564         this->contextParent = parentNew;
565     }
566 
567     FUNCTION_TEST_RETURN_VOID();
568 }
569 
570 /**********************************************************************************************************************************/
571 void
memContextSwitch(MemContext * this)572 memContextSwitch(MemContext *this)
573 {
574     FUNCTION_TEST_BEGIN();
575         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
576     FUNCTION_TEST_END();
577 
578     ASSERT(this != NULL);
579     ASSERT(memContextCurrentStackIdx < MEM_CONTEXT_STACK_MAX - 1);
580 
581     // Error if context is not active
582     if (this->state != memContextStateActive)
583         THROW(AssertError, "cannot switch to inactive context");
584 
585     memContextMaxStackIdx++;
586     memContextCurrentStackIdx = memContextMaxStackIdx;
587 
588     // Add memContext to the stack as a context that can be used for memory allocation
589     memContextStack[memContextCurrentStackIdx] = (struct MemContextStack)
590     {
591         .memContext = this,
592         .type = memContextStackTypeSwitch,
593         .tryDepth = errorTryDepth(),
594     };
595 
596     FUNCTION_TEST_RETURN_VOID();
597 }
598 
599 /**********************************************************************************************************************************/
600 void
memContextSwitchBack(void)601 memContextSwitchBack(void)
602 {
603     FUNCTION_TEST_VOID();
604 
605     ASSERT(memContextCurrentStackIdx > 0);
606 
607     // Generate a detailed error to help with debugging
608 #ifdef DEBUG
609     if (memContextStack[memContextMaxStackIdx].type == memContextStackTypeNew)
610     {
611         THROW_FMT(
612             AssertError, "current context expected but new context '%s' found",
613             memContextName(memContextStack[memContextMaxStackIdx].memContext));
614     }
615 #endif
616 
617     ASSERT(memContextCurrentStackIdx == memContextMaxStackIdx);
618 
619     memContextMaxStackIdx--;
620     memContextCurrentStackIdx--;
621 
622     // memContext of type New cannot be the current context so keep going until we find a memContext we can switch to as the current
623     // context
624     while (memContextStack[memContextCurrentStackIdx].type == memContextStackTypeNew)
625         memContextCurrentStackIdx--;
626 
627     FUNCTION_TEST_RETURN_VOID();
628 }
629 
630 /**********************************************************************************************************************************/
631 void
memContextKeep(void)632 memContextKeep(void)
633 {
634     FUNCTION_TEST_VOID();
635 
636     // Generate a detailed error to help with debugging
637 #ifdef DEBUG
638     if (memContextStack[memContextMaxStackIdx].type != memContextStackTypeNew)
639     {
640         THROW_FMT(
641             AssertError, "new context expected but current context '%s' found",
642             memContextName(memContextStack[memContextMaxStackIdx].memContext));
643     }
644 #endif
645 
646     memContextMaxStackIdx--;
647 
648     FUNCTION_TEST_RETURN_VOID();
649 }
650 
651 /**********************************************************************************************************************************/
652 void
memContextDiscard(void)653 memContextDiscard(void)
654 {
655     FUNCTION_TEST_VOID();
656 
657     // Generate a detailed error to help with debugging
658 #ifdef DEBUG
659     if (memContextStack[memContextMaxStackIdx].type != memContextStackTypeNew)
660     {
661         THROW_FMT(
662             AssertError, "new context expected but current context '%s' found",
663             memContextName(memContextStack[memContextMaxStackIdx].memContext));
664     }
665 #endif
666 
667     memContextFree(memContextStack[memContextMaxStackIdx].memContext);
668     memContextMaxStackIdx--;
669 
670     FUNCTION_TEST_RETURN_VOID();
671 }
672 
673 /**********************************************************************************************************************************/
674 MemContext *
memContextTop(void)675 memContextTop(void)
676 {
677     FUNCTION_TEST_VOID();
678     FUNCTION_TEST_RETURN(&contextTop);
679 }
680 
681 /**********************************************************************************************************************************/
682 MemContext *
memContextCurrent(void)683 memContextCurrent(void)
684 {
685     FUNCTION_TEST_VOID();
686     FUNCTION_TEST_RETURN(memContextStack[memContextCurrentStackIdx].memContext);
687 }
688 
689 /**********************************************************************************************************************************/
690 bool
memContextFreeing(MemContext * this)691 memContextFreeing(MemContext *this)
692 {
693     FUNCTION_TEST_BEGIN();
694         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
695     FUNCTION_TEST_END();
696 
697     ASSERT(this != NULL);
698 
699     FUNCTION_TEST_RETURN(this->state == memContextStateFreeing);
700 }
701 
702 /**********************************************************************************************************************************/
703 const char *
memContextName(MemContext * this)704 memContextName(MemContext *this)
705 {
706     FUNCTION_TEST_BEGIN();
707         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
708     FUNCTION_TEST_END();
709 
710     ASSERT(this != NULL);
711 
712     // Error if context is not active
713     if (this->state != memContextStateActive)
714         THROW(AssertError, "cannot get name for inactive context");
715 
716     FUNCTION_TEST_RETURN(this->name);
717 }
718 
719 /**********************************************************************************************************************************/
720 MemContext *
memContextPrior(void)721 memContextPrior(void)
722 {
723     FUNCTION_TEST_VOID();
724 
725     ASSERT(memContextCurrentStackIdx > 0);
726 
727     unsigned int priorIdx = 1;
728 
729     while (memContextStack[memContextCurrentStackIdx - priorIdx].type == memContextStackTypeNew)
730         priorIdx++;
731 
732     FUNCTION_TEST_RETURN(memContextStack[memContextCurrentStackIdx - priorIdx].memContext);
733 }
734 
735 /**********************************************************************************************************************************/
736 size_t
memContextSize(const MemContext * this)737 memContextSize(const MemContext *this)
738 {
739     FUNCTION_TEST_BEGIN();
740         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
741     FUNCTION_TEST_END();
742 
743     // Size of struct and child context/alloc arrays
744     size_t result =
745         sizeof(MemContext) + (this->contextChildListSize * sizeof(MemContext *)) +
746         (this->allocListSize * sizeof(MemContextAlloc *));
747 
748     // Add child contexts
749     for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
750     {
751         if (this->contextChildList[contextIdx])
752             result += memContextSize(this->contextChildList[contextIdx]);
753     }
754 
755     // Add allocations
756     for (unsigned int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
757     {
758         if (this->allocList[allocIdx] != NULL)
759             result += this->allocList[allocIdx]->size;
760     }
761 
762     FUNCTION_TEST_RETURN(result);
763 }
764 
765 /**********************************************************************************************************************************/
766 void
memContextClean(unsigned int tryDepth)767 memContextClean(unsigned int tryDepth)
768 {
769     FUNCTION_TEST_BEGIN();
770         FUNCTION_TEST_PARAM(UINT, tryDepth);
771     FUNCTION_TEST_END();
772 
773     ASSERT(tryDepth > 0);
774 
775     // Iterate through everything pushed to the stack since the last try
776     while (memContextStack[memContextMaxStackIdx].tryDepth >= tryDepth)
777     {
778         // Free memory contexts that were not kept
779         if (memContextStack[memContextMaxStackIdx].type == memContextStackTypeNew)
780         {
781             memContextFree(memContextStack[memContextMaxStackIdx].memContext);
782         }
783         // Else find the prior context and make it the current context
784         else
785         {
786             memContextCurrentStackIdx--;
787 
788             while (memContextStack[memContextCurrentStackIdx].type == memContextStackTypeNew)
789                 memContextCurrentStackIdx--;
790         }
791 
792         memContextMaxStackIdx--;
793     }
794 
795     FUNCTION_TEST_RETURN_VOID();
796 }
797 
798 /**********************************************************************************************************************************/
799 void
memContextFree(MemContext * this)800 memContextFree(MemContext *this)
801 {
802     FUNCTION_TEST_BEGIN();
803         FUNCTION_TEST_PARAM(MEM_CONTEXT, this);
804     FUNCTION_TEST_END();
805 
806     ASSERT(this != NULL);
807 
808     // If context is already freeing then return if memContextFree() is called again - this can happen in callbacks
809     if (this->state != memContextStateFreeing)
810     {
811         // Current context cannot be freed unless it is top (top is never really freed, just the stuff under it)
812         if (this == memContextStack[memContextCurrentStackIdx].memContext && this != &contextTop)
813             THROW_FMT(AssertError, "cannot free current context '%s'", this->name);
814 
815         // Error if context is not active
816         if (this->state != memContextStateActive)
817             THROW(AssertError, "cannot free inactive context");
818 
819         // Free child contexts
820         if (this->contextChildListSize > 0)
821             for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
822                 if (this->contextChildList[contextIdx] && this->contextChildList[contextIdx]->state == memContextStateActive)
823                     memContextFree(this->contextChildList[contextIdx]);
824 
825         // Set state to freeing now that there are no child contexts.  Child contexts might need to interact with their parent while
826         // freeing so the parent needs to remain active until they are all gone.
827         this->state = memContextStateFreeing;
828 
829         // Execute callback if defined
830         TRY_BEGIN()
831         {
832             if (this->callbackFunction)
833                 this->callbackFunction(this->callbackArgument);
834         }
835         // Finish cleanup even if the callback fails
836         FINALLY()
837         {
838             // Free child context allocations
839             if (this->contextChildListSize > 0)
840             {
841                 for (unsigned int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
842                     if (this->contextChildList[contextIdx])
843                         memFreeInternal(this->contextChildList[contextIdx]);
844 
845                 memFreeInternal(this->contextChildList);
846                 this->contextChildListSize = 0;
847             }
848 
849             // Free memory allocations
850             if (this->allocListSize > 0)
851             {
852                 for (unsigned int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
853                     if (this->allocList[allocIdx] != NULL)
854                         memFreeInternal(this->allocList[allocIdx]);
855 
856                 memFreeInternal(this->allocList);
857                 this->allocListSize = 0;
858             }
859 
860             // If the context index is lower than the current free index in the parent then replace it
861             if (this->contextParent != NULL && this->contextParentIdx < this->contextParent->contextChildFreeIdx)
862                 this->contextParent->contextChildFreeIdx = this->contextParentIdx;
863 
864             // Make top context active again
865             if (this == &contextTop)
866                 this->state = memContextStateActive;
867             // Else reset the memory context so it can be reused
868             else
869                 *this = (MemContext){.state = memContextStateFree};
870         }
871         TRY_END();
872     }
873 
874     FUNCTION_TEST_RETURN_VOID();
875 }
876