Browse Source

major refactor of allocator

Caleb James DeLisle 10 years ago
parent
commit
2ffd748237

+ 2 - 4
admin/Admin.c

@@ -134,15 +134,14 @@ static uint8_t sendMessage(struct Message* message, struct Sockaddr* dest, struc
 
 static int sendBenc(Dict* message, struct Sockaddr* dest, struct Admin* admin)
 {
-    struct Allocator* allocator;
-    BufferAllocator_STACK(allocator, 256);
+    struct Allocator* alloc = Allocator_child(admin->allocator);
 
     #define SEND_MESSAGE_PADDING 32
     uint8_t buff[Admin_MAX_RESPONSE_SIZE + SEND_MESSAGE_PADDING];
 
     struct Writer* w = ArrayWriter_new(buff + SEND_MESSAGE_PADDING,
                                        Admin_MAX_RESPONSE_SIZE,
-                                       allocator);
+                                       alloc);
     StandardBencSerializer_get()->serializeDictionary(w, message);
 
     struct Message m = {
@@ -150,7 +149,6 @@ static int sendBenc(Dict* message, struct Sockaddr* dest, struct Admin* admin)
         .length = w->bytesWritten,
         .padding = SEND_MESSAGE_PADDING
     };
-    struct Allocator* alloc = Allocator_child(admin->allocator);
     struct Message* msg = Message_clone(&m, alloc);
     int out = sendMessage(msg, dest, admin);
     Allocator_free(alloc);

+ 1 - 1
admin/angel/AngelInit.c

@@ -171,7 +171,7 @@ int AngelInit_main(int argc, char** argv)
     struct Writer* logWriter = FileWriter_new(stdout, alloc);
     struct Log* logger = WriterLog_new(logWriter, alloc);
     struct Random* rand = Random_new(alloc, logger, eh);
-    MallocAllocator_setCanary(alloc, (long)Random_int64(rand));
+    Allocator_setCanary(alloc, (long)Random_int64(rand));
     struct Allocator* tempAlloc = Allocator_child(alloc);
     struct EventBase* eventBase = EventBase_new(alloc);
 

+ 2 - 2
admin/angel/Core.c

@@ -152,7 +152,7 @@ static void adminMemory(Dict* input, void* vcontext, String* txid)
 {
     struct Context* context = vcontext;
     Dict d = Dict_CONST(
-        String_CONST("bytes"), Int_OBJ(MallocAllocator_bytesAllocated(context->allocator)), NULL
+        String_CONST("bytes"), Int_OBJ(Allocator_bytesAllocated(context->allocator)), NULL
     );
     Admin_sendMessage(&d, txid, context->admin);
 }
@@ -268,7 +268,7 @@ int Core_main(int argc, char** argv)
     struct Random* rand = LibuvEntropyProvider_newDefaultRandom(eventBase, logger, eh, alloc);
 
     // -------------------- Change Canary Value ---------------------- //
-    MallocAllocator_setCanary(alloc, (long)Random_int64(rand));
+    Allocator_setCanary(alloc, (long)Random_int64(rand));
     struct Allocator* tempAlloc = Allocator_child(alloc);
 
 

+ 3 - 1
crypto/test/CryptoAuth_unit_test.c

@@ -302,6 +302,8 @@ void testGetUsers()
 
 int main()
 {
+    testGetUsers();
+
     struct Allocator* allocator;
     BufferAllocator_STACK(allocator, 512);
     eventBase = EventBase_new(allocator);
@@ -311,6 +313,6 @@ int main()
     encryptRndNonceTest();
     createNew();
     repeatHello();
-    testGetUsers();
+    Allocator_free(allocator);
     return 0;
 }

+ 0 - 7
dht/dhtcore/Janitor.c

@@ -187,13 +187,6 @@ static void maintanenceCycle(void* vcontext)
                   nonZeroNodes,
                   janitor->nodeStore->size - nonZeroNodes,
                   janitor->nodeStore->size);
-
-        /* Accessible via admin interface.
-        size_t bytes = MallocAllocator_bytesAllocated(janitor->allocator);
-        Log_debug(janitor->logger,
-                   "Using %u bytes of memory.\n",
-                   (unsigned int) bytes);
-        */
     #endif
 
     if (now > janitor->timeOfNextGlobalMaintainence) {

+ 1 - 3
interface/addressable/PacketHeaderToUDPAddrInterface.c

@@ -15,7 +15,6 @@
 #include "interface/Interface.h"
 #include "interface/addressable/PacketHeaderToUDPAddrInterface.h"
 #include "memory/Allocator.h"
-#include "memory/BufferAllocator.h"
 #include "util/platform/Sockaddr.h"
 #include "util/Assert.h"
 #include "util/Identity.h"
@@ -84,8 +83,7 @@ static uint8_t receiveMessage(struct Message* message, struct Interface* iface)
         return Error_NONE;
     }
 
-    struct Allocator* alloc;
-    BufferAllocator_STACK(alloc, 512);
+    struct Allocator* alloc = Allocator_child(message->alloc);
     struct Sockaddr* addr = Sockaddr_clone(context->pub.addr, alloc);
     uint8_t* addrPtr = NULL;
     Assert_true(Sockaddr_getAddress(addr, &addrPtr) == 16);

+ 770 - 0
memory/Allocator.c

@@ -0,0 +1,770 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#define string_strrchr
+#include "util/platform/libc/string.h"
+
+#include "memory/Allocator.h"
+#include "memory/Allocator_pvt.h"
+#include "util/Bits.h"
+#include "util/log/Log.h"
+
+#include <stdio.h>
+
+/** This provides the padding for each line based on the depth in the stack. */
+struct Unroller;
+struct Unroller
+{
+    const char* const content;
+    const struct Unroller* const last;
+};
+static void writeUnroller(const struct Unroller* unroller)
+{
+    if (unroller) {
+        writeUnroller(unroller->last);
+        fprintf(stderr, "%s", unroller->content);
+    }
+}
+static void unroll(struct Allocator_pvt* context, struct Unroller* unroller)
+{
+    writeUnroller(unroller);
+    const char* ident = (context->pub.fileName) ? strrchr(context->pub.fileName, '/') : "UNKNOWN";
+    ident = ident ? ident : context->pub.fileName;
+
+    fprintf(stderr, "%s:%d [%lu] bytes%s\n",
+            ident,
+            context->pub.lineNum,
+            context->allocatedHere,
+            (context->pub.isFreeing) ? " (freeing)" : "");
+
+    if (context->firstChild) {
+        unroll(context->firstChild, &(struct Unroller) {
+            .content = ((context->nextSibling) ? "| " : "  "),
+            .last = unroller
+        });
+    }
+    if (context->nextSibling) {
+        unroll(context->nextSibling, unroller);
+    }
+}
+
+Gcc_NORETURN
+static void failure(struct Allocator_pvt* context,
+                    const char* message,
+                    const char* fileName,
+                    int lineNum)
+{
+    // get the root allocator.
+    struct Allocator_pvt* rootAlloc = context;
+    while (rootAlloc->lastSibling && rootAlloc->lastSibling != rootAlloc) {
+        rootAlloc = rootAlloc->lastSibling;
+    }
+    // can't use this allocator because it failed.
+    unroll(rootAlloc, NULL);
+
+    Assert_failure("%s:%d Fatal error: [%s] totalBytes [%ld] remaining [%ld]",
+                   fileName, lineNum, message,
+                   (long)context->rootAlloc->maxSpace,
+                   (long)context->rootAlloc->spaceAvailable);
+}
+
+static inline unsigned long getRealSize(unsigned long requestedSize)
+{
+    return ((requestedSize + (sizeof(char*) - 1)) & ~(sizeof(char*) - 1)) // align
+        + sizeof(struct Allocator_Allocation_pvt)
+    #ifdef Allocator_USE_CANARIES
+        + sizeof(long)
+    #endif
+    ;
+}
+
+#define END_CANARY(alloc) ((long*) alloc)[ (alloc->size / sizeof(long)) - 1 ]
+
+static inline void setCanaries(struct Allocator_Allocation_pvt* alloc,
+                               struct Allocator_pvt* context)
+{
+    #ifdef Allocator_USE_CANARIES
+        END_CANARY(alloc) = alloc->beginCanary = context->canary;
+    #endif
+}
+
+static inline void checkCanaries(struct Allocator_Allocation_pvt* alloc,
+                                 struct Allocator_pvt* context)
+{
+    #ifdef Allocator_USE_CANARIES
+        char* canary;
+        if (alloc->beginCanary != context->canary) {
+            canary = "begin";
+        } else if (END_CANARY(alloc) != alloc->beginCanary) {
+            canary = "end";
+        } else {
+            return;
+        }
+        Assert_failure("%s:%d Fatal error: invalid [%s] canary",
+                       context->fileName, context->pub.lineNum, canary);
+    #endif
+}
+
+static inline void* newAllocation(struct Allocator_pvt* context,
+                                  unsigned long size,
+                                  const char* fileName,
+                                  int lineNum)
+{
+    int64_t realSize = getRealSize(size);
+    if (context->rootAlloc->spaceAvailable <= realSize) {
+        failure(context, "Out of memory, limit exceeded", fileName, lineNum);
+    }
+
+    context->rootAlloc->spaceAvailable -= realSize;
+    context->allocatedHere += realSize;
+
+    struct Allocator_Allocation_pvt* alloc =
+        context->rootAlloc->provider(context->rootAlloc->providerContext,
+                                     NULL,
+                                     realSize,
+                                     &context->pub);
+    if (alloc == NULL) {
+        failure(context, "Out of memory, malloc() returned NULL", fileName, lineNum);
+    }
+    alloc->next = context->allocations;
+    alloc->pub.size = realSize;
+    alloc->pub.fileName = fileName;
+    alloc->pub.lineNum = lineNum;
+    context->allocations = alloc;
+    setCanaries(alloc, context);
+
+    return (void*) (alloc + 1);
+}
+
+struct Allocator_Allocation* Allocator_getAllocation(struct Allocator* alloc, int allocNum)
+{
+    struct Allocator_pvt* ctx = Identity_cast((struct Allocator_pvt*)alloc);
+    if (allocNum < 0) {
+        return NULL;
+    }
+    struct Allocator_Allocation_pvt* allocation = ctx->allocations;
+    for (;allocation && allocNum > 0; allocNum--) {
+        allocation = allocation->next;
+    }
+    return (allocation) ? &allocation->pub : NULL;
+}
+
+struct Allocator* Allocator_getChild(struct Allocator* alloc, int childNumber)
+{
+    struct Allocator_pvt* ctx = Identity_cast((struct Allocator_pvt*)alloc);
+    if (childNumber < 0) {
+        return NULL;
+    }
+    struct Allocator_pvt* child = ctx->firstChild;
+    for (;child && childNumber > 0; childNumber--) {
+        child = child->nextSibling;
+    }
+    return (child) ? &child->pub : NULL;
+}
+
+static int removeJob(struct Allocator_OnFreeJob_pvt* job)
+{
+    struct Allocator_pvt* context = Identity_cast(job->alloc);
+    struct Allocator_OnFreeJob_pvt* j = context->onFree;
+    struct Allocator_OnFreeJob_pvt** jP = &context->onFree;
+    while (j && j != job) {
+        jP = &j->next;
+        j = j->next;
+    }
+    if (j == job) {
+        *jP = j->next;
+        return 0;
+    } else {
+        return -1;
+        failure(context, "Allocator_onFreeComplete() called multiple times", job->file, job->line);
+    }
+}
+
+static void releaseAllocation(struct Allocator_pvt* context,
+                              struct Allocator_Allocation_pvt* allocation,
+                              Allocator_Provider provider,
+                              Allocator_Provider_CONTEXT_TYPE* providerCtx)
+{
+    checkCanaries(allocation, context);
+
+    // TODO: make this optional.
+    Bits_memset(&(&allocation->pub)[1],
+                0xee,
+                allocation->pub.size - sizeof(struct Allocator_Allocation));
+
+    provider(providerCtx,
+             &allocation->pub,
+             0,
+             ((char*)context != (char*)allocation) ? &context->pub : NULL);
+}
+
+static void releaseMemory(struct Allocator_pvt* context,
+                          Allocator_Provider provider,
+                          Allocator_Provider_CONTEXT_TYPE* providerCtx)
+{
+    // Free all of the allocations including the one which holds the allocator.
+    #ifdef PARANOIA
+        unsigned long allocatedHere = context->allocatedHere;
+    #endif
+
+    context->rootAlloc->spaceAvailable += context->allocatedHere;
+
+    struct Allocator_Allocation_pvt* loc = context->allocations;
+    while (loc != NULL) {
+        #ifdef PARANOIA
+            allocatedHere -= loc->pub.size;
+        #endif
+
+        struct Allocator_Allocation_pvt* nextLoc = loc->next;
+        releaseAllocation(context, loc, provider, providerCtx);
+        loc = nextLoc;
+    }
+    #ifdef PARANOIA
+        Assert_always(allocatedHere == 0);
+    #endif
+}
+
+/**
+ * Change the root allocator for a given subtree.
+ * @param alloc an allocator.
+ *
+static void changeRoot(struct Allocator_pvt* alloc,
+                       struct Allocator_FirstCtx* root,
+                       struct Allocator_pvt* first,
+                       const char* file,
+                       int line)
+{
+    Assert_true(first != alloc);
+    if (!first) {
+        first = alloc;
+    }
+    if (alloc->rootAlloc != NULL) {
+        alloc->rootAlloc->spaceAvailable += alloc->allocatedHere;
+    }
+    if (root != NULL) {
+        if (root->spaceAvailable < (int64_t)alloc->allocatedHere) {
+            failure(alloc, "Out of memory, limit exceeded", file, line);
+        }
+        root->spaceAvailable -= alloc->allocatedHere;
+    }
+    alloc->rootAlloc = root;
+
+    struct Allocator_pvt* child = alloc->firstChild;
+    while (child) {
+        struct Allocator_pvt* nextChild = child->nextSibling;
+        changeRoot(child, root, first, file, line);
+        child = nextChild;
+    }
+}
+*/
+// disconnect an allocator from it's parent.
+static void disconnect(struct Allocator_pvt* context)
+{
+    // Remove this allocator from the sibling list.
+    Assert_true(context->lastSibling);
+
+    if (context->lastSibling->nextSibling == context) {
+        context->lastSibling->nextSibling = context->nextSibling;
+
+    } else if (context->lastSibling->firstChild == context) {
+        context->lastSibling->firstChild = context->nextSibling;
+
+    } else if (context->lastSibling == context) {
+        // root alloc
+        Assert_true(!context->nextSibling);
+    }
+
+    if (context->nextSibling) {
+        context->nextSibling->lastSibling = context->lastSibling;
+        context->nextSibling = NULL;
+    }
+
+    context->lastSibling = context;
+}
+
+// connect an allocator to a new parent.
+static void connect(struct Allocator_pvt* parent,
+                    struct Allocator_pvt* child,
+                    const char* file,
+                    int line)
+{
+    Assert_true(child->lastSibling == child);
+    Assert_true(child->nextSibling == NULL);
+    child->nextSibling = parent->firstChild;
+    parent->firstChild = child;
+    child->lastSibling = parent;
+    //changeRoot(child, parent->rootAlloc, NULL, file, line);
+}
+
+static struct Allocator_pvt* getParent(struct Allocator_pvt* child)
+{
+    struct Allocator_pvt* ls = child->lastSibling;
+    while (ls) {
+        if (ls->firstChild == child) {
+           return ls;
+        }
+        if (ls == ls->lastSibling) {
+            // root alloc
+            return NULL;
+        }
+        child = ls;
+        ls = ls->lastSibling;
+    }
+    Assert_true(0);
+}
+
+static void freeAllocator(struct Allocator_pvt* context, const char* file, int line);
+
+static void childFreed(struct Allocator_pvt* child)
+{
+    struct Allocator_pvt* parent = getParent(child);
+    // disconnect the child and if there are no children left then call freeAllocator()
+    // on the parent a second time.
+    disconnect(child);
+    if (parent && !parent->firstChild && parent->pub.isFreeing) {
+        freeAllocator(parent, child->pub.fileName, child->pub.lineNum);
+    }
+}
+
+void Allocator_onFreeComplete(struct Allocator_OnFreeJob* onFreeJob)
+{
+    struct Allocator_OnFreeJob_pvt* job = (struct Allocator_OnFreeJob_pvt*) onFreeJob;
+    struct Allocator_pvt* context = Identity_cast(job->alloc);
+
+    if (removeJob(job)) {
+        failure(context, "OnFreeJob->complete() called multiple times", job->file, job->line);
+    }
+
+    if (!context->onFree) {
+        //childFreed(context);
+        // There are no more jobs, release the memory.
+        freeAllocator(context, context->pub.fileName, context->pub.lineNum);
+    }
+}
+
+static void disconnectAdopted(struct Allocator_pvt* parent, struct Allocator_pvt* child)
+{
+    Assert_true(parent->adoptions);
+    Assert_true(parent->adoptions->children);
+    struct Allocator_List** cpp = &parent->adoptions->children;
+    struct Allocator_List* cp;
+    int found = 0;
+    while ((cp = *cpp)) {
+        if (cp->alloc == child) {
+            *cpp = cp->next;
+            found = 1;
+            break;
+        }
+        cpp = &cp->next;
+    }
+    Assert_true(found);
+
+    Assert_true(child->adoptions);
+    Assert_true(child->adoptions->parents);
+    cpp = &child->adoptions->parents;
+    found = 0;
+    while ((cp = *cpp)) {
+        if (cp->alloc == parent) {
+            *cpp = cp->next;
+            found = 1;
+            break;
+        }
+        cpp = &cp->next;
+    }
+    Assert_true(found);
+}
+
+/**
+ * Triggered when freeAllocator() is called and the allocator nolonger
+ * has any remaining links to the allocator tree.
+ */
+static void freeAllocator(struct Allocator_pvt* context, const char* file, int line)
+{
+    if (context->adoptions && context->adoptions->parents) {
+        disconnect(context);
+        connect(context->adoptions->parents->alloc, context, file, line);
+        disconnectAdopted(context->adoptions->parents->alloc, context);
+        return;
+    }
+
+    // When the last child calls us back via childFreed() we will be called the last time and
+    // if this is not set, the child will be disconnected from us and we will be left.
+    context->pub.isFreeing = 1;
+
+    // from now on, fileName/line will point to the place of freeing.
+    // this allows childFreed() to tell the truth when calling us back.
+    context->pub.fileName = file;
+    context->pub.lineNum = line;
+
+    // Disconnect adopted children.
+    struct Allocator_List* childL = context->adoptions ? context->adoptions->children : NULL;
+    while (childL) {
+        disconnectAdopted(context, childL->alloc);
+        childL = childL->next;
+    }
+
+    // Do the onFree jobs.
+    struct Allocator_OnFreeJob_pvt** jobP = &context->onFree;
+    while (*jobP != NULL) {
+        struct Allocator_OnFreeJob_pvt* job = *jobP;
+        if (!job->pub.callback) {
+            // no callback, remove the job
+            Assert_true(!removeJob(job));
+            continue;
+        } else if (!job->done) {
+            if  (job->pub.callback(&job->pub) != Allocator_ONFREE_ASYNC) {
+                Assert_true(!removeJob(job));
+                continue;
+            }
+            // asynchronously completing, don't bother it again.
+            job->done = 1;
+        }
+        jobP = &job->next;
+    }
+
+    if (context->onFree) {
+        // onFreeComplete() will call us back.
+        return;
+    }
+
+    // Free children
+    struct Allocator_pvt* child = context->firstChild;
+    if (child) {
+        while (child) {
+            struct Allocator_pvt* nextChild = child->nextSibling;
+            freeAllocator(child, file, line);
+            child = nextChild;
+        }
+        // childFreed() will call us back.
+        return;
+    }
+
+    // Grab out the provider and provider context in case the root allocator is freed.
+    Allocator_Provider provider = context->rootAlloc->provider;
+    Allocator_Provider_CONTEXT_TYPE* providerCtx = context->rootAlloc->providerContext;
+
+    childFreed(context);
+    releaseMemory(context, provider, providerCtx);
+}
+
+void Allocator__free(struct Allocator* alloc, const char* file, int line)
+{
+    struct Allocator_pvt* context = Identity_cast((struct Allocator_pvt*) alloc);
+    freeAllocator(context, file, line);
+}
+
+void* Allocator__malloc(struct Allocator* allocator,
+                        unsigned long length,
+                        const char* fileName,
+                        int lineNum)
+{
+    struct Allocator_pvt* ctx = Identity_cast((struct Allocator_pvt*) allocator);
+    return newAllocation(ctx, length, fileName, lineNum);
+}
+
+void* Allocator__calloc(struct Allocator* alloc,
+                        unsigned long length,
+                        unsigned long count,
+                        const char* fileName,
+                        int lineNum)
+{
+    void* pointer = Allocator__malloc(alloc, length * count, fileName, lineNum);
+    Bits_memset(pointer, 0, length * count);
+    return pointer;
+}
+
+void* Allocator__realloc(struct Allocator* allocator,
+                         const void* original,
+                         unsigned long size,
+                         const char* fileName,
+                         int lineNum)
+{
+    if (original == NULL) {
+        return Allocator__malloc(allocator, size, fileName, lineNum);
+    }
+
+    struct Allocator_pvt* context = Identity_cast((struct Allocator_pvt*) allocator);
+    struct Allocator_Allocation_pvt** locPtr = &context->allocations;
+    struct Allocator_Allocation_pvt* origLoc =
+        ((struct Allocator_Allocation_pvt*) original) - 1;
+    for (;;) {
+        struct Allocator_Allocation_pvt* loc = *locPtr;
+        if (loc == NULL) {
+            failure(context,
+                    "Reallocation of memory which was not allocated using this allocator.",
+                    fileName,
+                    lineNum);
+        }
+        checkCanaries(loc, context);
+        if (loc == origLoc) {
+            break;
+        }
+        locPtr = &loc->next;
+    }
+
+    struct Allocator_Allocation_pvt* nextLoc = origLoc->next;
+
+    if (size == 0) {
+        // realloc(0) means free()
+        *locPtr = nextLoc;
+        Assert_true(origLoc->pub.size <= context->allocatedHere);
+        context->rootAlloc->spaceAvailable += origLoc->pub.size;
+        context->allocatedHere -= origLoc->pub.size;
+        releaseAllocation(context,
+                          origLoc,
+                          context->rootAlloc->provider,
+                          context->rootAlloc->providerContext);
+        return NULL;
+    }
+
+    size_t realSize = getRealSize(size);
+    if (context->rootAlloc->spaceAvailable + origLoc->pub.size < realSize) {
+        failure(context, "Out of memory, limit exceeded.", fileName, lineNum);
+    }
+    context->rootAlloc->spaceAvailable += origLoc->pub.size;
+    context->rootAlloc->spaceAvailable -= realSize;
+    context->allocatedHere -= origLoc->pub.size;
+    context->allocatedHere += realSize;
+
+    struct Allocator_Allocation_pvt* alloc =
+        context->rootAlloc->provider(context->rootAlloc->providerContext,
+                                     &origLoc->pub,
+                                     realSize,
+                                     allocator);
+
+    if (alloc == NULL) {
+        failure(context, "Out of memory, realloc() returned NULL.", fileName, lineNum);
+    }
+    alloc->next = nextLoc;
+    alloc->pub.size = realSize;
+    *locPtr = alloc;
+
+    setCanaries(alloc, context);
+
+    return (void*) (alloc + 1);
+}
+
+void* Allocator__clone(struct Allocator* allocator,
+                       const void* toClone,
+                       unsigned long length,
+                       const char* fileName,
+                       int lineNum)
+{
+    void* pointer = Allocator__malloc(allocator, length, fileName, lineNum);
+    Bits_memcpy(pointer, toClone, length);
+    return pointer;
+}
+
+struct Allocator* Allocator__child(struct Allocator* allocator, const char* file, int line)
+{
+    struct Allocator_pvt* parent = Identity_cast((struct Allocator_pvt*) allocator);
+
+    struct Allocator_pvt stackChild = {
+        .pub = {
+            .fileName = file,
+            .lineNum = line,
+        },
+        .rootAlloc = parent->rootAlloc
+    };
+    Identity_set(&stackChild);
+    #ifdef Allocator_USE_CANARIES
+        child->nextCanary = child->canary = parent->nextCanary;
+    #endif
+    struct Allocator_pvt* child =
+        newAllocation(&stackChild, sizeof(struct Allocator_pvt), file, line);
+    Bits_memcpyConst(child, &stackChild, sizeof(struct Allocator_pvt));
+
+    // Link the child into the parent's allocator list
+    child->lastSibling = parent;
+    child->nextSibling = parent->firstChild;
+    if (parent->firstChild != NULL) {
+        parent->firstChild->lastSibling = child;
+    }
+    parent->firstChild = child;
+
+    return &child->pub;
+}
+
+int Allocator_cancelOnFree(struct Allocator_OnFreeJob* toRemove)
+{
+    struct Allocator_OnFreeJob_pvt* job = (struct Allocator_OnFreeJob_pvt*) toRemove;
+    struct Allocator_pvt* context = Identity_cast(job->alloc);
+    struct Allocator_OnFreeJob_pvt** jobPtr = &(context->onFree);
+    while (*jobPtr != NULL) {
+        if (*jobPtr == job) {
+            *jobPtr = (*jobPtr)->next;
+            return 0;
+        }
+        jobPtr = &(*jobPtr)->next;
+    }
+    return -1;
+}
+
+/** return 1 if true, otherwise zero. */
+static int isAncestorOf(struct Allocator_pvt* maybeParent,
+                        struct Allocator_pvt* maybeChild)
+{
+    if (maybeParent == maybeChild) {
+        return 1;
+    }
+    if (maybeParent == NULL || maybeChild == NULL) {
+        return 0;
+    }
+    if (isAncestorOf(maybeParent, getParent(maybeChild))) {
+        return 1;
+    }
+    if (maybeChild->adoptions) {
+        struct Allocator_List* al = maybeChild->adoptions->parents;
+        while (al) {
+            if (isAncestorOf(maybeParent, al->alloc)) {
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+void Allocator__adopt(struct Allocator* adoptedParent,
+                      struct Allocator* childToAdopt,
+                      const char* file,
+                      int line)
+{
+    struct Allocator_pvt* parent = Identity_cast((struct Allocator_pvt*) adoptedParent);
+    struct Allocator_pvt* child = Identity_cast((struct Allocator_pvt*) childToAdopt);
+
+    if (isAncestorOf(child, parent)) {
+        // The child is a parent of the parent, this means an adoption would be meaningless
+        // because if the child is otherwise freed, it will take the parent along with it.
+        return;
+    }
+
+    if (!parent->adoptions) {
+        parent->adoptions =
+            Allocator__calloc(adoptedParent, sizeof(struct Allocator_Adoptions), 1, file, line);
+    }
+    if (!child->adoptions) {
+        child->adoptions =
+            Allocator__calloc(childToAdopt, sizeof(struct Allocator_Adoptions), 1, file, line);
+    }
+
+    struct Allocator_List* pl =
+        Allocator__calloc(adoptedParent, sizeof(struct Allocator_List), 1, file, line);
+    pl->alloc = child;
+    pl->next = parent->adoptions->children;
+    parent->adoptions->children = pl;
+
+    struct Allocator_List* cl =
+        Allocator__calloc(childToAdopt, sizeof(struct Allocator_List), 1, file, line);
+    cl->alloc = parent;
+    cl->next = child->adoptions->parents;
+    child->adoptions->parents = cl;
+}
+
+struct Allocator_OnFreeJob* Allocator__onFree(struct Allocator* alloc,
+                                              Allocator_OnFreeCallback callback,
+                                              void* callbackContext,
+                                              const char* file,
+                                              int line)
+{
+    struct Allocator_pvt* context = Identity_cast((struct Allocator_pvt*) alloc);
+
+    struct Allocator_OnFreeJob_pvt* newJob =
+        Allocator_clone(alloc, (&(struct Allocator_OnFreeJob_pvt) {
+            .pub = {
+                .callback = callback,
+                .userData = callbackContext
+            },
+            .alloc = context,
+            .file = file,
+            .line = line
+        }));
+    Identity_set(newJob);
+
+    struct Allocator_OnFreeJob_pvt* job = context->onFree;
+    if (job == NULL) {
+        context->onFree = newJob;
+    } else {
+        while (job->next != NULL) {
+            job = job->next;
+        }
+        job->next = newJob;
+    }
+    return &newJob->pub;
+}
+
+struct Allocator* Allocator_new(unsigned long sizeLimit,
+                                Allocator_Provider provider,
+                                void* providerContext,
+                                const char* fileName,
+                                int lineNum)
+{
+    // Add in the size of the allocator so that a very small sizeLimit is sane.
+    sizeLimit += getRealSize(sizeof(struct Allocator_FirstCtx));
+
+    struct Allocator_FirstCtx stackContext = {
+        .spaceAvailable = (sizeLimit == 0) ? SIZE_MAX : sizeLimit,
+        .provider = provider,
+        .providerContext = providerContext,
+        .context = {
+            .pub = {
+                .fileName = fileName,
+                .lineNum = lineNum,
+            },
+            #ifdef Allocator_USE_CANARIES
+            .canary = (long) 0x09F911029D74E35Bll,
+            .nextCanary = (long) 0xD84156C5635688C0ll,
+            #endif
+        }
+    };
+    stackContext.maxSpace = stackContext.spaceAvailable;
+    stackContext.context.rootAlloc = &stackContext;
+    Identity_set(&stackContext.context);
+
+    struct Allocator_FirstCtx* firstContext =
+        Allocator__clone(&stackContext.context.pub,
+                         &stackContext,
+                         sizeof(struct Allocator_FirstCtx),
+                         fileName,
+                         lineNum);
+
+    struct Allocator_pvt* context = &firstContext->context;
+    context->rootAlloc = firstContext;
+    context->lastSibling = context;
+
+    Identity_set(context);
+    return &context->pub;
+}
+
+static inline uint64_t bytesAllocated(struct Allocator_pvt* ctx)
+{
+    uint64_t bytes = ctx->allocatedHere;
+    for (struct Allocator_pvt* child = ctx->firstChild; child; child = child->nextSibling) {
+        bytes += bytesAllocated(child);
+    }
+    return bytes;
+}
+
+unsigned long Allocator_bytesAllocated(struct Allocator* allocator)
+{
+    struct Allocator_pvt* context = Identity_cast((struct Allocator_pvt*) allocator);
+    return bytesAllocated(context);
+}
+
+void Allocator_setCanary(struct Allocator* alloc, long value)
+{
+    #ifdef Allocator_USE_CANARIES
+        struct Allocator_pvt* context = Identity_cast((struct Allocator_pvt*) alloc);
+        context->nextCanary ^= value;
+    #endif
+}

+ 136 - 79
memory/Allocator.h

@@ -17,25 +17,21 @@
 
 #include "util/Identity.h"
 
+#if !defined(Allocator_USE_CANARIES) && defined(PARANOIA)
+    //#define Allocator_USE_CANARIES
+#endif
+
 /**
  * A handle which is provided in response to calls to Allocator_onFree().
  * This handle is sutable for use with Allocator_notOnFree() to cancel a job.
  */
 struct Allocator_OnFreeJob;
 typedef int (* Allocator_OnFreeCallback)(struct Allocator_OnFreeJob* job);
-struct Allocator_OnFreeJob {
-    /** Set by allocator. */
-    const Allocator_OnFreeCallback cancel;
-
-    /** Complete the job, used only if the callback returns Allocator_ONFREE_ASYNC. */
-    const Allocator_OnFreeCallback complete;
-
+struct Allocator_OnFreeJob
+{
     /** Set by caller. */
     Allocator_OnFreeCallback callback;
     void* userData;
-
-    /** Set by allocator. */
-    Identity
 };
 
 /**
@@ -101,50 +97,44 @@ struct Allocator_OnFreeJob {
  *
  * The function pointers in the allocator structure are best called through the associated macros.
  */
-struct Allocator;
 struct Allocator
 {
-    void* (* const malloc)(unsigned long numberOfBytes,
-                           struct Allocator* thisAlloc,
-                           const char* identFile,
-                           int identLine);
+    /** The name of the file where this allocator was created. */
+    const char* fileName;
 
-    void* (* const calloc)(unsigned long numberOfBytes,
-                           unsigned long multiplier,
-                           struct Allocator* thisAlloc,
-                           const char* identFile,
-                           int identLine);
-    void* (* const realloc)(const void* originalAllocation,
-                            unsigned long numberOfBytes,
-                            struct Allocator* thisAllocator,
-                            const char* identFile,
-                            int identLine);
+    /** The number of the line where this allocator was created. */
+    int lineNum;
 
-    void* (* const clone)(unsigned long numberOfBytes,
-                          struct Allocator* thisAllocator,
-                          const void* toClone,
-                          const char* identFile,
-                          int identLine);
-
-
-    void (*  free)(struct Allocator* alloc, const char* identFile, int identLine);
-
-    struct Allocator_OnFreeJob* (* const onFree)(struct Allocator* alloc,
-                                                 const char* file,
-                                                 int line);
+    /** Non-zero if allocator is currently freeing. */
+    int isFreeing;
+};
 
+struct Allocator_Allocation
+{
+    const char* fileName;
 
-    struct Allocator* (* const child)(struct Allocator* thisAlloc,
-                                      const char* identFile,
-                                      int identLine);
+    int lineNum;
 
-    void (* const adopt)(struct Allocator* parentAlloc,
-                         struct Allocator* alloc,
-                         const char* identFile,
-                         int identLine);
+    unsigned long size;
 };
 
+/**
+ * Get a child of a given allocator.
+ *
+ * @param alloc the parent
+ * @param childNumber
+ * @return a child allocator or NULL if childNumber is out of range.
+ */
+struct Allocator* Allocator_getChild(struct Allocator* alloc, int childNumber);
 
+/**
+ * Get one of the allocations held by this allocator.
+ *
+ * @param alloc the allocator.
+ * @param allocNum the number of the allocation.
+ * @return an allocation or NULL if allocNum is out of range.
+ */
+struct Allocator_Allocation* Allocator_getAllocation(struct Allocator* alloc, int allocNum);
 
 /**
  * Allocate some memory from this memory allocator.
@@ -156,8 +146,11 @@ struct Allocator
  * @return a pointer to the newly allocated memory.
  * @see malloc()
  */
-#define Allocator_malloc(alloc, size) \
-    (alloc)->malloc((size), (alloc), __FILE__, __LINE__)
+void* Allocator__malloc(struct Allocator* allocator,
+                        unsigned long length,
+                        const char* fileName,
+                        int lineNum);
+#define Allocator_malloc(a, b) Allocator__malloc((a),(b),__FILE__,__LINE__)
 
 /**
  * Allocate some memory from this memory allocator.
@@ -171,8 +164,12 @@ struct Allocator
  * @return a pointer to the newly allocated memory.
  * @see calloc()
  */
-#define Allocator_calloc(alloc, size, count) \
-    (alloc)->calloc((size), (count), (alloc), __FILE__, __LINE__)
+void* Allocator__calloc(struct Allocator* alloc,
+                        unsigned long length,
+                        unsigned long count,
+                        const char* fileName,
+                        int lineNum);
+#define Allocator_calloc(a, b, c) Allocator__calloc((a),(b),(c),__FILE__,__LINE__)
 
 /**
  * Re-allocate memory so that an allocation can be expanded.
@@ -188,8 +185,12 @@ struct Allocator
  *             without freeing the entire allocator.
  * @return a pointer to the newly allocated memory.
  */
-#define Allocator_realloc(alloc, orig, size) \
-    (alloc)->realloc((orig), (size), (alloc), __FILE__, __LINE__)
+void* Allocator__realloc(struct Allocator* allocator,
+                         const void* original,
+                         unsigned long size,
+                         const char* fileName,
+                         int lineNum);
+#define Allocator_realloc(a, b, c) Allocator__realloc((a),(b),(c),__FILE__,__LINE__)
 
 /**
  * Allocate some memory and copy something into that memory space.
@@ -202,8 +203,23 @@ struct Allocator
  *                the size of the new allocation will be sizeof(*content).
  * @return a pointer to the newly allocated memory.
  */
-#define Allocator_clone(alloc, content) \
-    (alloc)->clone(sizeof(*(content)), (alloc), (content), __FILE__, __LINE__)
+void* Allocator__clone(struct Allocator* allocator,
+                       const void* toClone,
+                       unsigned long length,
+                       const char* fileName,
+                       int lineNum);
+#define Allocator_clone(a, b) Allocator__clone((a),(b),sizeof(*(b)),__FILE__,__LINE__)
+
+/**
+ * Spawn a new child of this allocator.
+ * When this allocator is freed all of its children which have no surviving parent will also be
+ * freed.
+ *
+ * @param alloc the memory allocator.
+ * @return a child allocator.
+ */
+struct Allocator* Allocator__child(struct Allocator* alloc, const char* fileName, int lineNum);
+#define Allocator_child(a) Allocator__child((a),__FILE__,__LINE__)
 
 /**
  * Sever the link between an allocator and it's original parent.
@@ -213,8 +229,8 @@ struct Allocator
  *
  * @param alloc the allocator to disconnect from it's parent.
  */
-#define Allocator_free(alloc) \
-    (alloc)->free((alloc), __FILE__, __LINE__)
+void Allocator__free(struct Allocator* alloc, const char* file, int line);
+#define Allocator_free(a) Allocator__free((a),__FILE__,__LINE__)
 
 /**
  * Add a function to be called when the allocator is freed.
@@ -223,20 +239,12 @@ struct Allocator
  * @param callback the function to call.
  * @return an Allocator_OnFreeJob which can be cancelled with Allocator_cancelOnFree().
  */
-#define Allocator_onFree(alloc, callback, context) \
-    Allocator__onFree((alloc), (callback), (context), __FILE__, __LINE__)
-
-static inline struct Allocator_OnFreeJob* Allocator__onFree(struct Allocator* alloc,
-                                                            Allocator_OnFreeCallback callback,
-                                                            void* context,
-                                                            const char* file,
-                                                            int line)
-{
-    struct Allocator_OnFreeJob* j = alloc->onFree(alloc, file, line);
-    j->callback = callback;
-    j->userData = context;
-    return j;
-}
+struct Allocator_OnFreeJob* Allocator__onFree(struct Allocator* alloc,
+                                              Allocator_OnFreeCallback callback,
+                                              void* context,
+                                              const char* file,
+                                              int line);
+#define Allocator_onFree(a, b, c) Allocator__onFree((a), (b), (c), __FILE__, __LINE__)
 
 /**
  * Remove a function which was registered with Allocator_onFree().
@@ -244,19 +252,14 @@ static inline struct Allocator_OnFreeJob* Allocator__onFree(struct Allocator* al
  * @param job the return value from calling Allocator_onFree().
  * @return 0 if the job was found and removed, -1 otherwise.
  */
-#define Allocator_cancelOnFree(job) \
-    (job)->cancel(job)
+int Allocator_cancelOnFree(struct Allocator_OnFreeJob* toRemove);
 
 /**
- * Spawn a new child of this allocator.
- * When this allocator is freed all of its children which have no surviving parent will also be
- * freed.
+ * Tell the allocator that an asynchronous onFree() job has completed.
  *
- * @param alloc the memory allocator.
- * @return a child allocator.
+ * @param job the return value from calling Allocator_onFree().
  */
-#define Allocator_child(alloc) \
-    (alloc)->child((alloc), __FILE__, __LINE__)
+void Allocator_onFreeComplete(struct Allocator_OnFreeJob* onFreeJob);
 
 /**
  * Adopt an allocator.
@@ -284,7 +287,61 @@ static inline struct Allocator_OnFreeJob* Allocator__onFree(struct Allocator* al
  * @param toAdopt the allocator which should be adopted by the returned child allocator.
  * @return a new allocator which is an adopted parent of toAdopt.
  */
-#define Allocator_adopt(parentAlloc, toAdopt) \
-    (parentAlloc)->adopt((parentAlloc), (toAdopt), __FILE__, __LINE__)
+void Allocator__adopt(struct Allocator* parentAlloc,
+                      struct Allocator* alloc,
+                      const char* fileName,
+                      int lineNum);
+#define Allocator_adopt(a, b) Allocator__adopt((a),(b),__FILE__,__LINE__)
+
+/**
+ * Set the heap protection canary for the next child allocator.
+ * If heap protection canaries are enabled, they will be added at the beginning and end
+ * of each memory allocation and checked during free and other operations. If one is corrupted
+ * the program will be aborted to protect against security attacks and other faults.
+ * By default the canaries are statically set but this allows the value to be changed so that
+ * the value of the canaries is unpredictable in order to foil targetted attacks.
+ */
+void Allocator_setCanary(struct Allocator* alloc, long value);
+
+/**
+ * Get the number of bytes allocated by this allocator and all of it's children.
+ */
+unsigned long Allocator_bytesAllocated(struct Allocator* allocator);
+
+
+/**
+ * The underlying memory provider function which backs the allocator.
+ * This function is roughly equivilant to realloc() API in that it is used for allocation,
+ * reallocation and freeing but it also contains a context field which allows the provider
+ * to store it's state in a non-global way and a group pointer.
+ *
+ * The group pointer is used to add memory to an allocation group. If the group pointer is set to
+ * NULL, the provider is requested to begin a new group, if the group pointer is not null, it will
+ * be set to an allocation which had previously been returned by the provider, in this case the
+ * provider should internally group this allocation with the other as they will likely be freed
+ * at the same time.
+ *
+ * @param ctx the context which was passed to Allocator_new() along with the provider.
+ * @param original if this is NULL then the allocator is to provide a new allocation, otherwise it
+ *                 should resize or free an existing allocation.
+ * @param size if this is 0 then the allocator should free original and return NULL, if it is not
+ *             zero then original should be resized or created.
+ * @param group if this is not NULL then the provider is being informed that the current allocation
+ *              and the allocation in group are likely to have the same life span and should be
+ *              colocated if it is logical to do so.
+ */
+#ifndef Allocator_Provider_CONTEXT_TYPE
+    #define Allocator_Provider_CONTEXT_TYPE void
+#endif
+typedef void* (* Allocator_Provider)(Allocator_Provider_CONTEXT_TYPE* ctx,
+                                     struct Allocator_Allocation* original,
+                                     unsigned long size,
+                                     struct Allocator* group);
+
+struct Allocator* Allocator_new(unsigned long sizeLimit,
+                                Allocator_Provider provider,
+                                Allocator_Provider_CONTEXT_TYPE* providerContext,
+                                const char* fileName,
+                                int lineNum);
 
 #endif

+ 39 - 39
memory/MallocAllocator_pvt.h → memory/Allocator_pvt.h

@@ -12,52 +12,55 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef MallocAllocator_pvt_H
-#define MallocAllocator_pvt_H
+#ifndef Allocator_pvt_H
+#define Allocator_pvt_H
 
-#include "memory/MallocAllocator.h"
+#include "memory/Allocator.h"
 #include "util/Identity.h"
 
 #include <stdint.h>
 
-struct MallocAllocator_pvt;
+struct Allocator_pvt;
 
-struct MallocAllocator_OnFreeJob;
-struct MallocAllocator_OnFreeJob {
-    struct Allocator_OnFreeJob generic;
-    struct MallocAllocator_pvt* alloc;
-    struct MallocAllocator_OnFreeJob* next;
+struct Allocator_OnFreeJob_pvt;
+struct Allocator_OnFreeJob_pvt {
+    struct Allocator_OnFreeJob pub;
+    struct Allocator_pvt* alloc;
+    struct Allocator_OnFreeJob_pvt* next;
 
     /* prevent async jobs from being called multiple times, nonzero = done */
     int done;
 
     const char* file;
     int line;
+
+    /** Set by allocator. */
+    Identity
 };
 
-struct MallocAllocator_Allocation;
-struct MallocAllocator_Allocation {
-    struct MallocAllocator_Allocation* next;
-    size_t size;
-#ifdef MallocAllocator_USE_CANARIES
+struct Allocator_Allocation_pvt;
+struct Allocator_Allocation_pvt {
+    struct Allocator_Allocation pub;
+    struct Allocator_Allocation_pvt* next;
+#ifdef Allocator_USE_CANARIES
     long beginCanary;
 #endif
 };
 
 /** Singly linked list of allocators. */
-struct MallocAllocator_List;
-struct MallocAllocator_List {
-    struct MallocAllocator_pvt* alloc;
-    struct MallocAllocator_List* next;
+struct Allocator_List;
+struct Allocator_List {
+    struct Allocator_pvt* alloc;
+    struct Allocator_List* next;
 };
 
-struct MallocAllocator_Adoptions {
-    struct MallocAllocator_List* parents;
-    struct MallocAllocator_List* children;
+struct Allocator_Adoptions {
+    struct Allocator_List* parents;
+    struct Allocator_List* children;
 };
 
 /** Internal state for Allocator. */
-struct MallocAllocator_pvt
+struct Allocator_pvt
 {
     /** This allocator. */
     struct Allocator pub;
@@ -66,10 +69,10 @@ struct MallocAllocator_pvt
      * A linked list of the allocations made with this allocator.
      * These are all freed when the allocator is freed.
      */
-    struct MallocAllocator_Allocation* allocations;
+    struct Allocator_Allocation_pvt* allocations;
 
     /** A linked list of jobs which must be done when this allocator is freed. */
-    struct MallocAllocator_OnFreeJob* onFree;
+    struct Allocator_OnFreeJob_pvt* onFree;
 
     /**
      * When this allocator is freed, lastSibling->nextSibling will be set to nextSibling
@@ -77,16 +80,16 @@ struct MallocAllocator_pvt
      * GOTCHYA: if this is the first sibling, lastSibling will point to the parent and
      *          in that case, lastSibling->firstChild becomes nextSibling.
      */
-    struct MallocAllocator_pvt* lastSibling;
+    struct Allocator_pvt* lastSibling;
 
     /** A pointer to the next allocator which is a child of the same parent. */
-    struct MallocAllocator_pvt* nextSibling;
+    struct Allocator_pvt* nextSibling;
 
     /** The first child allocator, this will be freed when this allocator is freed. */
-    struct MallocAllocator_pvt* firstChild;
+    struct Allocator_pvt* firstChild;
 
     /** The root allocator with additional tree-global data. */
-    struct MallocAllocator_FirstCtx* rootAlloc;
+    struct Allocator_FirstCtx* rootAlloc;
 
     /** The number of bytes allocated by *this* allocator (but not it's children). */
     unsigned long allocatedHere;
@@ -96,16 +99,9 @@ struct MallocAllocator_pvt
      * Otherwise it is a linked list of adopted parents and children of this allocator.
      * The structure here is allocated by THIS ALLOCATOR, not by it's parent or child.
      */
-    struct MallocAllocator_Adoptions* adoptions;
-
-    /** This is the location where the allocator was created. */
-    const char* identFile;
-    int identLine;
+    struct Allocator_Adoptions* adoptions;
 
-    /** If true then this allocator is in the process of being freed. */
-    int freeing;
-
-    #ifdef MallocAllocator_USE_CANARIES
+    #ifdef Allocator_USE_CANARIES
         /** The canary for allocations made with this allocator constant to allow varification. */
         long canary;
 
@@ -118,10 +114,14 @@ struct MallocAllocator_pvt
 };
 
 /** The first ("genesis") allocator, not a child of any other allocator. */
-struct MallocAllocator_FirstCtx
+struct Allocator_FirstCtx
 {
     /** The context for the first allocator. */
-    struct MallocAllocator_pvt context;
+    struct Allocator_pvt context;
+
+    Allocator_Provider provider;
+
+    Allocator_Provider_CONTEXT_TYPE* providerContext;
 
     /** The number of bytes which can be allocated by this allocator and all of its family. */
     int64_t spaceAvailable;

+ 48 - 220
memory/BufferAllocator.c

@@ -12,13 +12,15 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#include <stdint.h>
-
+struct BufferAllocator_pvt;
+#define Allocator_Provider_CONTEXT_TYPE struct BufferAllocator_pvt
 #include "exception/Except.h"
 #include "memory/BufferAllocator.h"
 #include "util/Bits.h"
 #include "util/Identity.h"
 
+#include <stdint.h>
+
 /**
  * TODO: addOnFreeJob adds a job which is only run when the root allocator is freed
  *       and it needs to be run when the allocator which called it, or any of that allocator's
@@ -28,35 +30,25 @@
 /* Define alignment as the size of a pointer which is usually 4 or 8 bytes. */
 #define ALIGNMENT sizeof(char*)
 
-struct Job {
-    struct Allocator_OnFreeJob generic;
-    struct Allocator* alloc;
-    struct Job* next;
-    Identity
-};
-
 /** Internal state for Allocator. */
-struct BufferAllocator {
-    struct Allocator generic;
-
+struct BufferAllocator_pvt
+{
     /** Pointer to the beginning of the buffer. */
-    char* basePointer;
+    char* const basePointer;
 
-    /** Pointer to a pointer to the place in the buffer to allocate the next block of memory. */
-    char** pPointer;
+    /** Pointer to the place in the buffer to allocate the next block of memory. */
+    char* pointer;
 
     /** Pointer to the end of the buffer. */
     char* const endPointer;
 
-    struct Job* onFree;
-    /** Number of onfree jobs which are not yet complete. */
-    int outstandingJobs;
     struct Except* onOOM;
-    const char* file;
-    int line;
+
     Identity
 };
 
+
+
 /**
  * Get a pointer which is aligned on memory boundries.
  *
@@ -67,229 +59,65 @@ struct BufferAllocator {
     ((char*) ((uintptr_t)( ((char*)(pointer)) + (alignedOn) - 1) & ~ ((alignedOn) - 1)))
 
 /** @see Allocator_malloc() */
-static void* allocatorMalloc(unsigned long length,
-                             struct Allocator* allocator,
-                             const char* identFile,
-                             int identLine)
+static void* allocatorMalloc(struct BufferAllocator_pvt* context, unsigned long length)
 {
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
-
-    char* pointer = getAligned((*context->pPointer), ALIGNMENT);
+    Identity_check(context);
+    char* pointer = getAligned(context->pointer, ALIGNMENT);
     char* endOfAlloc = pointer + length;
 
     if (endOfAlloc >= context->endPointer) {
-        Except_throw(context->onOOM, "BufferAllocator ran out of memory [%s:%d]",
-                     identFile, identLine);
+        Except_throw(context->onOOM, "BufferAllocator ran out of memory.");
     }
 
-    if (endOfAlloc < *(context->pPointer)) {
-        Except_throw(context->onOOM, "BufferAllocator integer overflow [%s:%d]",
-                     identFile, identLine);
+    if (endOfAlloc < context->pointer) {
+        Except_throw(context->onOOM, "BufferAllocator integer overflow.");
     }
 
-    (*context->pPointer) = endOfAlloc;
+    context->pointer = endOfAlloc;
     return (void*) pointer;
 }
 
-/** @see Allocator->calloc() */
-static void* allocatorCalloc(unsigned long length,
-                             unsigned long count,
-                             struct Allocator* allocator,
-                             const char* identFile,
-                             int identLine)
-{
-    void* pointer = allocatorMalloc(length * count, allocator, identFile, identLine);
-    Bits_memset(pointer, 0, length * count);
-    return pointer;
-}
-
-/** @see Allocator->clone() */
-static void* allocatorClone(unsigned long length,
-                            struct Allocator* allocator,
-                            const void* toClone,
-                            const char* identFile,
-                            int identLine)
-{
-    void* pointer = allocatorMalloc(length, allocator, identFile, identLine);
-    Bits_memcpy(pointer, toClone, length);
-    return pointer;
-}
-
-/** @see Allocator->realloc() */
-static void* allocatorRealloc(const void* original,
-                              unsigned long length,
-                              struct Allocator* allocator,
-                              const char* identFile,
-                              int identLine)
+static void* provideMemory(struct BufferAllocator_pvt* context,
+                           struct Allocator_Allocation* original,
+                           unsigned long size,
+                           struct Allocator* group)
 {
     if (original == NULL) {
-        return allocatorMalloc(length, allocator, identFile, identLine);
-    }
-
-    // Need to pointer to make sure we dont copy too much.
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
-    char* pointer = *context->pPointer;
-    uint32_t amountToClone = (length < (uint32_t)(pointer - (char*)original))
-        ? length
-        : (uint32_t)(pointer - (char*)original);
-
-    // The likelyhood of nothing having been allocated since is
-    // almost 0 so we will always create a new
-    // allocation and copy into it.
-    void* newAlloc = allocatorMalloc(length, allocator, identFile, identLine);
-    Bits_memcpy(newAlloc, original, amountToClone);
-    return newAlloc;
-}
-
-/** @see Allocator->free() */
-static void freeAllocator(struct Allocator* allocator, const char* identFile, int identLine)
-{
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
-    struct Job* job = context->onFree;
-    while (job != NULL) {
-        if (job->generic.callback) {
-            job->generic.callback(&job->generic);
-            context->outstandingJobs++;
-        }
-        job = job->next;
+        return allocatorMalloc(context, size);
     }
-}
 
-static int removeOnFreeJob(struct Allocator_OnFreeJob* toRemove)
-{
-    struct Job* j = Identity_cast((struct Job*) toRemove);
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) j->alloc);
-    struct Job** jobPtr = &(context->onFree);
-    while (*jobPtr != NULL) {
-        if (*jobPtr == j) {
-            *jobPtr = (*jobPtr)->next;
-            return 0;
-        }
-        jobPtr = &(*jobPtr)->next;
+    if (((char*)original) + original->size == context->pointer) {
+        // This is reallocating the last allocation.
+        // clear the allocation then let allocatorMalloc() recreate it.
+        context->pointer = (char*)original;
     }
-    return -1;
-}
 
-static int onFreeComplete(struct Allocator_OnFreeJob* job)
-{
-    struct BufferAllocator* context =
-        Identity_cast((struct BufferAllocator*) ((struct Job*)job)->alloc);
-
-    if (!--context->outstandingJobs) {
-        if ((uintptr_t) context > (uintptr_t) context->pPointer) {
-            // pPointer points to a destination which is > context unless this is a child alloc.
-            return 0;
-        }
-        // complete
-        (*context->pPointer) = context->basePointer;
+    if (size == 0) {
+        return NULL;
     }
-    return 0;
-}
-
-/** @see Allocator->onFree() */
-static struct Allocator_OnFreeJob* onFree(struct Allocator* alloc,
-                                          const char* file,
-                                          int line)
-{
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
-
-    struct Job* newJob = Allocator_clone(alloc, (&(struct Job) {
-        .generic = {
-            .cancel = removeOnFreeJob,
-            .complete = onFreeComplete
-        },
-        .alloc = alloc,
-    }));
-    Identity_set(&newJob->generic);
-
-    struct Job* job = context->onFree;
-    if (job == NULL) {
-        context->onFree = newJob;
 
-    } else {
-        while (job->next != NULL) {
-            job = job->next;
-        }
-        job->next = newJob;
+    void* newAlloc = allocatorMalloc(context, size);
+    if (newAlloc != original) {
+        Assert_true((char*)newAlloc > (char*)original + original->size);
+        Bits_memcpy(newAlloc, original, original->size);
     }
-    return &newJob->generic;
-}
-
-/** @see Allocator_child() */
-static struct Allocator* childAllocator(struct Allocator* alloc,
-                                        const char* file,
-                                        int line)
-{
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
-    struct BufferAllocator* child =
-        allocatorClone(sizeof(struct BufferAllocator), alloc, context, file, line);
-    child->file = file;
-    child->line = line;
-    return &child->generic;
-}
-
-static void adopt(struct Allocator* alloc, struct Allocator* allocB, const char* file, int line)
-{
-    Assert_always(!"Unimplemented");
+    return newAlloc;
 }
 
 /** @see BufferAllocator.h */
-struct Allocator* BufferAllocator_newWithIdentity(void* buffer,
-                                                  unsigned long length,
-                                                  char* file,
-                                                  int line)
+struct Allocator* BufferAllocator__new(void* buffer,
+                                       unsigned long length,
+                                       char* file,
+                                       int line)
 {
-    struct FirstAlloc {
-        struct BufferAllocator alloc;
-        char* pointer;
+    struct BufferAllocator_pvt stackAlloc = {
+        .basePointer = buffer,
+        .pointer = buffer,
+        .endPointer = ((char*)buffer) + length
     };
-
-    struct FirstAlloc tempAlloc = {
-        .alloc = {
-            .generic = {
-                .free = freeAllocator,
-                .malloc = allocatorMalloc,
-                .calloc = allocatorCalloc,
-                .clone = allocatorClone,
-                .realloc = allocatorRealloc,
-                .child = childAllocator,
-                .onFree = onFree,
-                .adopt = adopt
-            },
-
-            // Align the pointer to do the first write manually.
-            .pPointer = NULL,
-            .basePointer = getAligned(buffer, sizeof(char*)),
-            .endPointer = ((char*)buffer) + length,
-            .file = file,
-            .line = line
-        },
-        .pointer = getAligned(buffer, sizeof(char*))
-    };
-    tempAlloc.alloc.pPointer = &tempAlloc.pointer;
-    Identity_set(&tempAlloc.alloc);
-
-    if (tempAlloc.alloc.endPointer < (*tempAlloc.alloc.pPointer)) {
-        // int64_t overflow.
-        return NULL;
-    }
-
-    if (length + (char*) buffer < (*tempAlloc.alloc.pPointer) + sizeof(struct BufferAllocator)) {
-        // Not enough space to allocate the context.
-        return NULL;
-    }
-
-    struct FirstAlloc* alloc = (struct FirstAlloc*) (*tempAlloc.alloc.pPointer);
-    Bits_memcpyConst(alloc, &tempAlloc, sizeof(struct FirstAlloc));
-    alloc->pointer += sizeof(struct FirstAlloc);
-    alloc->alloc.pPointer = &alloc->pointer;
-
-    return &alloc->alloc.generic;
-}
-
-void BufferAllocator_onOOM(struct Allocator* alloc,
-                           struct Except* exceptionHandler)
-{
-    struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
-    context->onOOM = exceptionHandler;
+    Identity_set(&stackAlloc);
+    struct BufferAllocator_pvt* alloc =
+        allocatorMalloc(&stackAlloc, sizeof(struct BufferAllocator_pvt));
+    Bits_memcpyConst(alloc, &stackAlloc, sizeof(struct BufferAllocator_pvt));
+    return Allocator_new(0xffffffffu, provideMemory, alloc, file, line);
 }

+ 5 - 14
memory/BufferAllocator.h

@@ -17,7 +17,6 @@
 
 #include "memory/Allocator.h"
 #include "util/UniqueName.h"
-#include "exception/Except.h"
 
 /**
  * Create a new Allocator which allocates from to a user supplied buffer.
@@ -29,24 +28,16 @@
  * @param length the size of the array. If more is written than this length,
  *               further allocations will fail and return NULL.
  */
-struct Allocator* BufferAllocator_newWithIdentity(void* buffer,
-                                                  unsigned long length,
-                                                  char* file,
-                                                  int line);
+struct Allocator* BufferAllocator__new(void* buffer,
+                                       unsigned long length,
+                                       char* file,
+                                       int line);
 
-#define BufferAllocator_new(buffer, length) \
-    BufferAllocator_newWithIdentity(buffer, length, __FILE__, __LINE__)
+#define BufferAllocator_new(a,b) BufferAllocator__new((a),(b),__FILE__,__LINE__)
 
 // This relies on the fact that UniqueName is only unique on a per-line basis.
 #define BufferAllocator_STACK(name, length) \
     uint8_t UniqueName_get()[length]; \
     name = BufferAllocator_new(UniqueName_get(), length);
 
-/**
- * @param bufferAllocator the buffer allocator to set this on.
- * @param eh an exception handler.
- */
-void BufferAllocator_onOOM(struct Allocator* bufferAllocator,
-                           struct Except* eh);
-
 #endif

+ 1 - 0
memory/CMakeLists.txt

@@ -10,6 +10,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 add_library(cjdmemory
+    Allocator.c
     BufferAllocator.c
     MallocAllocator.c
 )

+ 11 - 707
memory/MallocAllocator.c

@@ -12,727 +12,31 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#define string_strrchr
-#include "util/platform/libc/string.h"
-
-#include "memory/Allocator.h"
 #include "memory/MallocAllocator.h"
-#include "memory/MallocAllocator_pvt.h"
-#include "util/Bits.h"
-#include "util/log/Log.h"
 
-#include <inttypes.h>
-#include <stdio.h>
 #include <stdlib.h>
-#include <stdbool.h>
-
-/** This provides the padding for each line based on the depth in the stack. */
-struct Unroller;
-struct Unroller
-{
-    const char* const content;
-    const struct Unroller* const last;
-};
-static void writeUnroller(const struct Unroller* unroller)
-{
-    if (unroller) {
-        writeUnroller(unroller->last);
-        fprintf(stderr, "%s", unroller->content);
-    }
-}
-static void unroll(struct MallocAllocator_pvt* context, struct Unroller* unroller)
-{
-    writeUnroller(unroller);
-    const char* ident = (context->identFile) ? strrchr(context->identFile, '/') : "UNKNOWN";
-    ident = ident ? ident : context->identFile;
-
-    fprintf(stderr, "%s:%d [%lu] bytes%s\n",
-            ident,
-            context->identLine,
-            context->allocatedHere,
-            (context->freeing) ? " (freeing)" : "");
-
-    if (context->firstChild) {
-        unroll(context->firstChild, &(struct Unroller) {
-            .content = ((context->nextSibling) ? "| " : "  "),
-            .last = unroller
-        });
-    }
-    if (context->nextSibling) {
-        unroll(context->nextSibling, unroller);
-    }
-}
-
-Gcc_NORETURN
-static void failure(struct MallocAllocator_pvt* context,
-                    const char* message,
-                    const char* identFile,
-                    int identLine)
-{
-    // get the root allocator.
-    struct MallocAllocator_pvt* rootAlloc = context;
-    while (rootAlloc->lastSibling && rootAlloc->lastSibling != rootAlloc) {
-        rootAlloc = rootAlloc->lastSibling;
-    }
-    // can't use this allocator because it failed.
-    unroll(rootAlloc, NULL);
-
-    Assert_failure("%s:%d Fatal error: [%s] totalBytes [%ld] remaining [%ld]",
-                   identFile, identLine, message,
-                   (long)context->rootAlloc->maxSpace,
-                   (long)context->rootAlloc->spaceAvailable);
-}
-
-static inline unsigned long getRealSize(unsigned long requestedSize)
-{
-    return ((requestedSize + (sizeof(char*) - 1)) & ~(sizeof(char*) - 1)) // align
-        + sizeof(struct MallocAllocator_Allocation)
-    #ifdef MallocAllocator_USE_CANARIES
-        + sizeof(long)
-    #endif
-    ;
-}
-
-#define END_CANARY(alloc) ((long*) alloc)[ (alloc->size / sizeof(long)) - 1 ]
-
-static inline void setCanaries(struct MallocAllocator_Allocation* alloc,
-                               struct MallocAllocator_pvt* context)
-{
-    #ifdef MallocAllocator_USE_CANARIES
-        alloc->beginCanary = context->canary;
-        END_CANARY(alloc) = alloc->beginCanary;
-    #endif
-}
-
-static inline void checkCanaries(struct MallocAllocator_Allocation* alloc,
-                                 struct MallocAllocator_pvt* context)
-{
-    #ifdef MallocAllocator_USE_CANARIES
-        char* canary;
-        if (alloc->beginCanary != context->canary) {
-            canary = "begin";
-        } else if (END_CANARY(alloc) != alloc->beginCanary) {
-            canary = "end";
-        } else {
-            return;
-        }
-        Assert_failure("%s:%d Fatal error: invalid [%s] canary",
-                       context->identFile, context->identLine, canary);
-    #endif
-}
-
-static inline void* newAllocation(struct MallocAllocator_pvt* context,
-                                  unsigned long size,
-                                  const char* identFile,
-                                  int identLine)
-{
-    int64_t realSize = getRealSize(size);
-    if (context->rootAlloc->spaceAvailable <= realSize) {
-        failure(context, "Out of memory, limit exceeded", identFile, identLine);
-    }
-
-    context->rootAlloc->spaceAvailable -= realSize;
-    context->allocatedHere += realSize;
-
-    struct MallocAllocator_Allocation* alloc = malloc(realSize);
-    if (alloc == NULL) {
-        failure(context, "Out of memory, malloc() returned NULL", identFile, identLine);
-    }
-    alloc->next = context->allocations;
-    alloc->size = realSize;
-    context->allocations = alloc;
-    setCanaries(alloc, context);
-
-    return (void*) (alloc + 1);
-}
-
-static int removeJob(struct MallocAllocator_OnFreeJob* job)
-{
-    struct MallocAllocator_pvt* context = Identity_cast(job->alloc);
-    struct MallocAllocator_OnFreeJob* j = context->onFree;
-    struct MallocAllocator_OnFreeJob** jP = &context->onFree;
-    while (j && j != job) {
-        jP = &j->next;
-        j = j->next;
-    }
-    if (j == job) {
-        *jP = j->next;
-        return 0;
-    } else {
-        return -1;
-        failure(context, "OnFreeJob->complete() called multiple times", job->file, job->line);
-    }
-}
-
-static void releaseAllocation(struct MallocAllocator_pvt* context,
-                              struct MallocAllocator_Allocation* allocation)
-{
-    checkCanaries(allocation, context);
-
-    // TODO: make this optional.
-    Bits_memset(allocation, 0xee, allocation->size);
-    free(allocation);
-}
-
-static void releaseMemory(struct MallocAllocator_pvt* context)
-{
-    // Free all of the allocations including the one which holds the allocator.
-    #ifdef PARANOIA
-        unsigned long allocatedHere = context->allocatedHere;
-    #endif
-
-    context->rootAlloc->spaceAvailable += context->allocatedHere;
-
-    struct MallocAllocator_Allocation* loc = context->allocations;
-    while (loc != NULL) {
-        #ifdef PARANOIA
-            allocatedHere -= loc->size;
-        #endif
-
-        struct MallocAllocator_Allocation* nextLoc = loc->next;
-        releaseAllocation(context, loc);
-        loc = nextLoc;
-    }
-    #ifdef PARANOIA
-        Assert_always(allocatedHere == 0);
-    #endif
-}
-
-/**
- * Change the root allocator for a given subtree.
- * @param alloc an allocator.
- */
-static void changeRoot(struct MallocAllocator_pvt* alloc,
-                       struct MallocAllocator_FirstCtx* root,
-                       struct MallocAllocator_pvt* first,
-                       const char* file,
-                       int line)
-{
-    Assert_true(first != alloc);
-    if (!first) {
-        first = alloc;
-    }
-    if (alloc->rootAlloc != NULL) {
-        alloc->rootAlloc->spaceAvailable += alloc->allocatedHere;
-    }
-    if (root != NULL) {
-        if (root->spaceAvailable < (int64_t)alloc->allocatedHere) {
-            failure(alloc, "Out of memory, limit exceeded", file, line);
-        }
-        root->spaceAvailable -= alloc->allocatedHere;
-    }
-    alloc->rootAlloc = root;
-
-    struct MallocAllocator_pvt* child = alloc->firstChild;
-    while (child) {
-        struct MallocAllocator_pvt* nextChild = child->nextSibling;
-        changeRoot(child, root, first, file, line);
-        child = nextChild;
-    }
-}
-
-// disconnect an allocator from it's parent.
-static void disconnect(struct MallocAllocator_pvt* context)
-{
-    // Remove this allocator from the sibling list.
-    Assert_true(context->lastSibling);
-
-    if (context->lastSibling->nextSibling == context) {
-        context->lastSibling->nextSibling = context->nextSibling;
-
-    } else if (context->lastSibling->firstChild == context) {
-        context->lastSibling->firstChild = context->nextSibling;
-
-    } else if (context->lastSibling == context) {
-        // root alloc
-        Assert_true(!context->nextSibling);
-    }
-
-    if (context->nextSibling) {
-        context->nextSibling->lastSibling = context->lastSibling;
-        context->nextSibling = NULL;
-    }
-
-    context->lastSibling = context;
-}
-
-// connect an allocator to a new parent.
-static void connect(struct MallocAllocator_pvt* parent,
-                    struct MallocAllocator_pvt* child,
-                    const char* file,
-                    int line)
-{
-    Assert_true(child->lastSibling == child);
-    Assert_true(child->nextSibling == NULL);
-    child->nextSibling = parent->firstChild;
-    parent->firstChild = child;
-    child->lastSibling = parent;
-    changeRoot(child, parent->rootAlloc, NULL, file, line);
-}
-
-static struct MallocAllocator_pvt* getParent(struct MallocAllocator_pvt* child)
-{
-    struct MallocAllocator_pvt* ls = child->lastSibling;
-    while (ls) {
-        if (ls->firstChild == child) {
-           return ls;
-        }
-        if (ls == ls->lastSibling) {
-            // root alloc
-            return NULL;
-        }
-        child = ls;
-        ls = ls->lastSibling;
-    }
-    Assert_true(0);
-}
-
-static void freeAllocator(struct MallocAllocator_pvt* context, const char* file, int line);
-
-static void childFreed(struct MallocAllocator_pvt* child)
-{
-    struct MallocAllocator_pvt* parent = getParent(child);
-    // disconnect the child and if there are no children left then call freeAllocator()
-    // on the parent a second time.
-    disconnect(child);
-    if (parent && !parent->firstChild && parent->freeing) {
-        freeAllocator(parent, child->identFile, child->identLine);
-    }
-}
-
-static int onFreeComplete(struct Allocator_OnFreeJob* onFreeJob)
-{
-    struct MallocAllocator_OnFreeJob* job = (struct MallocAllocator_OnFreeJob*) onFreeJob;
-    struct MallocAllocator_pvt* context = Identity_cast(job->alloc);
-
-    if (removeJob(job)) {
-        failure(context, "OnFreeJob->complete() called multiple times", job->file, job->line);
-    }
-
-    if (!context->onFree) {
-        //childFreed(context);
-        // There are no more jobs, release the memory.
-        freeAllocator(context, context->identFile, context->identLine);
-    }
-    return 0;
-}
-
-static void disconnectAdopted(struct MallocAllocator_pvt* parent, struct MallocAllocator_pvt* child)
-{
-    Assert_true(parent->adoptions);
-    Assert_true(parent->adoptions->children);
-    struct MallocAllocator_List** cpp = &parent->adoptions->children;
-    struct MallocAllocator_List* cp;
-    int found = 0;
-    while ((cp = *cpp)) {
-        if (cp->alloc == child) {
-            *cpp = cp->next;
-            found = 1;
-            break;
-        }
-        cpp = &cp->next;
-    }
-    Assert_true(found);
-
-    Assert_true(child->adoptions);
-    Assert_true(child->adoptions->parents);
-    cpp = &child->adoptions->parents;
-    found = 0;
-    while ((cp = *cpp)) {
-        if (cp->alloc == parent) {
-            *cpp = cp->next;
-            found = 1;
-            break;
-        }
-        cpp = &cp->next;
-    }
-    Assert_true(found);
-}
-
-/**
- * Triggered when freeAllocator() is called and the allocator nolonger
- * has any remaining links to the allocator tree.
- */
-static void freeAllocator(struct MallocAllocator_pvt* context, const char* file, int line)
-{
-    // When the last child calls us back via childFreed() we will be called the last time and
-    // if this is not set, the child will be disconnected from us and we will be left.
-    context->freeing = 1;
-
-    if (context->adoptions && context->adoptions->parents) {
-        disconnect(context);
-        connect(context->adoptions->parents->alloc, context, file, line);
-        disconnectAdopted(context->adoptions->parents->alloc, context);
-        return;
-    }
-    // from now on, identFile/line will point to the place of freeing.
-    // this allows childFreed() to tell the truth when calling us back.
-    context->identFile = file;
-    context->identLine = line;
-
-    // Disconnect adopted children.
-    struct MallocAllocator_List* childL = context->adoptions ? context->adoptions->children : NULL;
-    while (childL) {
-        disconnectAdopted(context, childL->alloc);
-        childL = childL->next;
-    }
-
-    // Do the onFree jobs.
-    struct MallocAllocator_OnFreeJob** jobP = &context->onFree;
-    while (*jobP != NULL) {
-        struct MallocAllocator_OnFreeJob* job = *jobP;
-        if (!job->generic.callback) {
-            // no callback, remove the job
-            Assert_true(!removeJob(job));
-            continue;
-        } else if (!job->done) {
-            if  (job->generic.callback(&job->generic) != Allocator_ONFREE_ASYNC) {
-                Assert_true(!removeJob(job));
-                continue;
-            }
-            // asynchronously completing, don't bother it again.
-            job->done = true;
-        }
-        jobP = &job->next;
-    }
-
-    if (context->onFree) {
-        // onFreeComplete() will call us back.
-        return;
-    }
-
-    // Free children
-    struct MallocAllocator_pvt* child = context->firstChild;
-    if (child) {
-        while (child) {
-            struct MallocAllocator_pvt* nextChild = child->nextSibling;
-            freeAllocator(child, file, line);
-            child = nextChild;
-        }
-        // childFreed() will call us back.
-        return;
-    }
-
-    childFreed(context);
-    releaseMemory(context);
-}
-
-/**
- * Disconnect an allocator from it's parent and free it if there are no more links to the tree.
- */
-static void disconnectAllocator(struct Allocator* allocator, const char* file, int line)
-{
-    struct MallocAllocator_pvt* context = Identity_cast((struct MallocAllocator_pvt*) allocator);
-    freeAllocator(context, file, line);
-}
-
-/** @see Allocator->malloc() */
-static void* allocatorMalloc(unsigned long length,
-                             struct Allocator* allocator,
-                             const char* identFile,
-                             int identLine)
-{
-    struct MallocAllocator_pvt* ctx = Identity_cast((struct MallocAllocator_pvt*) allocator);
-    return newAllocation(ctx, length, identFile, identLine);
-}
-
-/** @see Allocator->calloc() */
-static void* allocatorCalloc(unsigned long length,
-                             unsigned long count,
-                             struct Allocator* allocator,
-                             const char* identFile,
-                             int identLine)
-{
-    void* pointer = allocatorMalloc(length * count, allocator, identFile, identLine);
-    Bits_memset(pointer, 0, length * count);
-    return pointer;
-}
-
-/** @see Allocator->clone() */
-static void* allocatorClone(unsigned long length,
-                            struct Allocator* allocator,
-                            const void* toClone,
-                            const char* identFile,
-                            int identLine)
-{
-    void* pointer = allocatorMalloc(length, allocator, identFile, identLine);
-    Bits_memcpy(pointer, toClone, length);
-    return pointer;
-}
 
-/** @see Allocator->realloc() */
-static void* allocatorRealloc(const void* original,
-                              unsigned long size,
-                              struct Allocator* allocator,
-                              const char* identFile,
-                              int identLine)
+static void* provideMemory(void* vNULL,
+                           struct Allocator_Allocation* original,
+                           unsigned long size,
+                           struct Allocator* group)
 {
     if (original == NULL) {
-        return allocatorMalloc(size, allocator, identFile, identLine);
-    }
-
-    struct MallocAllocator_pvt* context = Identity_cast((struct MallocAllocator_pvt*) allocator);
-    struct MallocAllocator_Allocation** locPtr = &context->allocations;
-    struct MallocAllocator_Allocation* origLoc =
-        ((struct MallocAllocator_Allocation*) original) - 1;
-    for (;;) {
-        struct MallocAllocator_Allocation* loc = *locPtr;
-        if (loc == NULL) {
-            failure(context,
-                    "Reallocation of memory which was not allocated using this allocator.",
-                    identFile,
-                    identLine);
-        }
-        checkCanaries(loc, context);
-        if (loc == origLoc) {
-            break;
+        if (size == 0) {
+            return NULL;
         }
-        locPtr = &loc->next;
+        return malloc(size);
     }
 
-    struct MallocAllocator_Allocation* nextLoc = origLoc->next;
-
     if (size == 0) {
-        // realloc(0) means free()
-        *locPtr = nextLoc;
-        Assert_true(origLoc->size <= context->allocatedHere);
-        context->rootAlloc->spaceAvailable += origLoc->size;
-        context->allocatedHere -= origLoc->size;
-        releaseAllocation(context, origLoc);
+        free(original);
         return NULL;
     }
 
-    size_t realSize = getRealSize(size);
-    if (context->rootAlloc->spaceAvailable + origLoc->size < realSize) {
-        failure(context, "Out of memory, limit exceeded.", identFile, identLine);
-    }
-    context->rootAlloc->spaceAvailable += origLoc->size;
-    context->rootAlloc->spaceAvailable -= realSize;
-    context->allocatedHere -= origLoc->size;
-    context->allocatedHere += realSize;
-
-    struct MallocAllocator_Allocation* alloc = realloc(origLoc, realSize);
-
-    if (alloc == NULL) {
-        failure(context, "Out of memory, realloc() returned NULL.", identFile, identLine);
-    }
-    alloc->next = nextLoc;
-    alloc->size = realSize;
-    *locPtr = alloc;
-
-    setCanaries(alloc, context);
-
-    return (void*) (alloc + 1);
-}
-
-/** @see Allocator_child() */
-static struct Allocator* childAllocator(struct Allocator* allocator, const char* file, int line)
-{
-    struct MallocAllocator_pvt* parent = Identity_cast((struct MallocAllocator_pvt*) allocator);
-
-    struct MallocAllocator_pvt* child =
-        newAllocation(parent, sizeof(struct MallocAllocator_pvt), file, line);
-
-    Bits_memset(child, 0, sizeof(struct MallocAllocator_pvt));
-    Bits_memcpyConst(&child->pub, &parent->pub, sizeof(struct Allocator));
-    Identity_set(child);
-
-    // Add the child to it's own allocation list.
-    child->allocations = parent->allocations;
-
-    // Remove the child from the parent's allocation list.
-    parent->allocations = parent->allocations->next;
-    parent->allocatedHere -= getRealSize(sizeof(struct MallocAllocator_pvt));
-    child->allocatedHere += getRealSize(sizeof(struct MallocAllocator_pvt));
-
-    // Drop the rest of the linked list from the child's allocation
-    child->allocations->next = NULL;
-
-    // Link the child into the parent's allocator list
-    child->lastSibling = parent;
-    child->nextSibling = parent->firstChild;
-    if (parent->firstChild != NULL) {
-        parent->firstChild->lastSibling = child;
-    }
-    child->rootAlloc = parent->rootAlloc;
-    child->identFile = file;
-    child->identLine = line;
-    child->nextCanary = child->canary = parent->nextCanary;
-    parent->firstChild = child;
-
-    child->rootAlloc = parent->rootAlloc;
-
-    // Now set the child to use it's own canaries so it will free properly.
-    setCanaries(child->allocations, child);
-
-    return &child->pub;
-}
-
-static int removeOnFreeJob(struct Allocator_OnFreeJob* toRemove)
-{
-    struct MallocAllocator_OnFreeJob* job = (struct MallocAllocator_OnFreeJob*) toRemove;
-    struct MallocAllocator_pvt* context = Identity_cast(job->alloc);
-    struct MallocAllocator_OnFreeJob** jobPtr = &(context->onFree);
-    while (*jobPtr != NULL) {
-        if (*jobPtr == job) {
-            *jobPtr = (*jobPtr)->next;
-            return 0;
-        }
-        jobPtr = &(*jobPtr)->next;
-    }
-    return -1;
-}
-
-static bool isAncestorOf(struct MallocAllocator_pvt* maybeParent,
-                         struct MallocAllocator_pvt* maybeChild)
-{
-    if (maybeParent == maybeChild) {
-        return true;
-    }
-    if (maybeParent == NULL || maybeChild == NULL) {
-        return false;
-    }
-    if (isAncestorOf(maybeParent, getParent(maybeChild))) {
-        return true;
-    }
-    if (maybeChild->adoptions) {
-        struct MallocAllocator_List* al = maybeChild->adoptions->parents;
-        while (al) {
-            if (isAncestorOf(maybeParent, al->alloc)) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-static void adopt(struct Allocator* adoptedParent,
-                  struct Allocator* childToAdopt,
-                  const char* file,
-                  int line)
-{
-    struct MallocAllocator_pvt* parent = Identity_cast((struct MallocAllocator_pvt*) adoptedParent);
-    struct MallocAllocator_pvt* child = Identity_cast((struct MallocAllocator_pvt*) childToAdopt);
-
-    if (isAncestorOf(child, parent)) {
-        // The child is a parent of the parent, this means an adoption would be meaningless
-        // because if the child is otherwise freed, it will take the parent along with it.
-        return;
-    }
-
-    if (!parent->adoptions) {
-        parent->adoptions =
-            allocatorCalloc(sizeof(struct MallocAllocator_Adoptions), 1, adoptedParent, file, line);
-    }
-    if (!child->adoptions) {
-        child->adoptions =
-            allocatorCalloc(sizeof(struct MallocAllocator_Adoptions), 1, childToAdopt, file, line);
-    }
-
-    struct MallocAllocator_List* pl =
-        allocatorCalloc(sizeof(struct MallocAllocator_List), 1, adoptedParent, file, line);
-    pl->alloc = child;
-    pl->next = parent->adoptions->children;
-    parent->adoptions->children = pl;
-
-    struct MallocAllocator_List* cl =
-        allocatorCalloc(sizeof(struct MallocAllocator_List), 1, childToAdopt, file, line);
-    cl->alloc = parent;
-    cl->next = child->adoptions->parents;
-    child->adoptions->parents = cl;
-}
-
-/** @see Allocator_onFree() */
-static struct Allocator_OnFreeJob* addOnFreeJob(struct Allocator* allocator,
-                                                const char* file,
-                                                int line)
-{
-    struct MallocAllocator_pvt* context = Identity_cast((struct MallocAllocator_pvt*) allocator);
-
-    struct MallocAllocator_OnFreeJob* newJob =
-        Allocator_clone(allocator, (&(struct MallocAllocator_OnFreeJob) {
-            .generic = {
-                .cancel = removeOnFreeJob,
-                .complete = onFreeComplete
-            },
-            .alloc = context,
-            .file = file,
-            .line = line
-        }));
-    Identity_set(&newJob->generic);
-
-    struct MallocAllocator_OnFreeJob* job = context->onFree;
-    if (job == NULL) {
-        context->onFree = newJob;
-    } else {
-        while (job->next != NULL) {
-            job = job->next;
-        }
-        job->next = newJob;
-    }
-    return &newJob->generic;
-}
-
-/** @see MallocAllocator.h */
-struct Allocator* MallocAllocator_newWithIdentity(unsigned long sizeLimit,
-                                                  const char* identFile,
-                                                  int identLine)
-{
-    // Add in the size of the allocator so that a very small sizeLimit is sane.
-    sizeLimit += getRealSize(sizeof(struct MallocAllocator_FirstCtx));
-
-    struct MallocAllocator_FirstCtx stackContext = {
-        .spaceAvailable = (sizeLimit == 0) ? SIZE_MAX : sizeLimit,
-        .context = {
-            .identFile = identFile,
-            .identLine = identLine,
-            .canary = (long) 0x09F911029D74E35Bll,
-            .nextCanary = (long) 0xD84156C5635688C0ll,
-        }
-    };
-    stackContext.maxSpace = stackContext.spaceAvailable;
-    stackContext.context.rootAlloc = &stackContext;
-    Identity_set(&stackContext.context);
-
-    struct MallocAllocator_FirstCtx* firstContext =
-        allocatorClone(sizeof(struct MallocAllocator_FirstCtx),
-                       &stackContext.context.pub,
-                       &stackContext,
-                       identFile,
-                       identLine);
-
-    struct MallocAllocator_pvt* context = &firstContext->context;
-    context->rootAlloc = firstContext;
-
-    struct Allocator allocator = {
-        .free = disconnectAllocator,
-        .malloc = allocatorMalloc,
-        .calloc = allocatorCalloc,
-        .clone = allocatorClone,
-        .realloc = allocatorRealloc,
-        .child = childAllocator,
-        .onFree = addOnFreeJob,
-        .adopt = adopt
-    };
-
-    Bits_memcpyConst(&context->pub, &allocator, sizeof(struct Allocator));
-
-    context->lastSibling = context;
-
-    Identity_set(context);
-    return &context->pub;
-}
-
-unsigned long MallocAllocator_bytesAllocated(struct Allocator* allocator)
-{
-    struct MallocAllocator_pvt* context = Identity_cast((struct MallocAllocator_pvt*) allocator);
-    return context->rootAlloc->maxSpace - context->rootAlloc->spaceAvailable;
+    return realloc(original, size);
 }
 
-void MallocAllocator_setCanary(struct Allocator* alloc, long value)
+struct Allocator* MallocAllocator__new(unsigned long sizeLimit, const char* file, int line)
 {
-    #ifdef MallocAllocator_USE_CANARIES
-        struct MallocAllocator_pvt* context = Identity_cast((struct MallocAllocator_pvt*) alloc);
-        context->nextCanary ^= value;
-    #endif
+    return Allocator_new(sizeLimit, provideMemory, NULL, file, line);
 }

+ 2 - 28
memory/MallocAllocator.h

@@ -17,40 +17,14 @@
 
 #include "memory/Allocator.h"
 
-#if !defined(MallocAllocator_USE_CANARIES) && defined(PARANOIA)
-    #define MallocAllocator_USE_CANARIES
-#endif
-
 /**
  * Create a new Allocator which is a wrapper around malloc().
  *
  * @param sizeLimit the number of bytes which are allowed to be allocated by
  *                  this allocator or any of its children before the program
  *                  will be halted with an error.
- * @param identFile the file where this allocator was created.
- * @param identLine the line where this was called from.
- */
-struct Allocator* MallocAllocator_newWithIdentity(unsigned long sizeLimit,
-                                                  const char* identFile,
-                                                  int identLine);
-#define MallocAllocator_new(sl) \
-    MallocAllocator_newWithIdentity((sl), __FILE__, __LINE__)
-
-/**
- * Get the number of bytes allocated so far by this allocator,
- * all of its parents, and all of its children.
- *
- * @param allocator this *must* be a MallocAllocator.
- * @return the number of bytes which have been allocated so far.
- */
-unsigned long MallocAllocator_bytesAllocated(struct Allocator* allocator);
-
-/**
- * Mix randomness into the canary value.
- *
- * @param allocator this *must* be a MallocAllocator.
- * @param value a random machine size word.
  */
-void MallocAllocator_setCanary(struct Allocator* alloc, long value);
+struct Allocator* MallocAllocator__new(unsigned long sizeLimit, const char* file, int line);
+#define MallocAllocator_new(sl) MallocAllocator__new((sl),__FILE__,__LINE__)
 
 #endif

+ 12 - 11
memory/test/MallocAllocator_test.c → memory/test/Allocator_test.c

@@ -18,38 +18,39 @@
 #include <stdio.h>
 
 #include "memory/Allocator.h"
-#include "memory/MallocAllocator_pvt.h"
+#include "memory/Allocator_pvt.h"
+#include "memory/MallocAllocator.h"
 
-#ifdef MallocAllocator_USE_CANARIES
-    #define ALLOCATION_SIZE sizeof(struct MallocAllocator_Allocation) + sizeof(long)
+#ifdef Allocator_USE_CANARIES
+    #define ALLOCATION_SIZE sizeof(struct Allocator_Allocation_pvt) + sizeof(long)
 #else
-    #define ALLOCATION_SIZE sizeof(struct MallocAllocator_Allocation)
+    #define ALLOCATION_SIZE sizeof(struct Allocator_Allocation_pvt)
 #endif
-#define ALLOCATOR_SIZE sizeof(struct MallocAllocator_pvt)
+#define ALLOCATOR_SIZE sizeof(struct Allocator_pvt)
 
 int main()
 {
     struct Allocator* alloc = MallocAllocator_new(2048);
     size_t bytesUsed;
 
-    bytesUsed = MallocAllocator_bytesAllocated(alloc);
-    Assert_always(bytesUsed == ALLOCATION_SIZE + sizeof(struct MallocAllocator_FirstCtx));
+    bytesUsed = Allocator_bytesAllocated(alloc);
+    Assert_always(bytesUsed == ALLOCATION_SIZE + sizeof(struct Allocator_FirstCtx));
     Allocator_malloc(alloc, 25);
     bytesUsed += (((25 / sizeof(char*)) + 1) * sizeof(char*)) + ALLOCATION_SIZE;
-    Assert_always(MallocAllocator_bytesAllocated(alloc) == bytesUsed);
+    Assert_always(Allocator_bytesAllocated(alloc) == bytesUsed);
 
     struct Allocator* child = Allocator_child(alloc);
     bytesUsed += ALLOCATION_SIZE + ALLOCATOR_SIZE;
-    Assert_always(MallocAllocator_bytesAllocated(alloc) == bytesUsed);
+    Assert_always(Allocator_bytesAllocated(alloc) == bytesUsed);
 
     Allocator_malloc(child, 30);
     bytesUsed += 32 + ALLOCATION_SIZE;
-    Assert_always(MallocAllocator_bytesAllocated(alloc) == bytesUsed);
+    Assert_always(Allocator_bytesAllocated(alloc) == bytesUsed);
 
     Allocator_free(child);
     bytesUsed -= 32 + ALLOCATION_SIZE;
     bytesUsed -= ALLOCATION_SIZE + ALLOCATOR_SIZE;
-    Assert_always(MallocAllocator_bytesAllocated(alloc) == bytesUsed);
+    Assert_always(Allocator_bytesAllocated(alloc) == bytesUsed);
 
     Allocator_free(alloc);
 }

+ 1 - 2
memory/test/CMakeLists.txt

@@ -10,8 +10,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 set(Test_FILES
-    MallocAllocator_test.c
-#    MallocAllocator_oom_test.c
+    Allocator_test.c
 )
 set(Test_LIBRARIES
     cjdmemory

+ 0 - 51
memory/test/MallocAllocator_oom_test.c.disabled

@@ -1,51 +0,0 @@
-/* vim: set expandtab ts=4 sw=4: */
-/*
- * You may redistribute this program and/or modify it under the terms of
- * the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-#include "crypto/random/Random.h"
-#include "memory/Allocator.h"
-#include "memory/MallocAllocator.h"
-#include "util/Assert.h"
-#include "util/Security.h"
-
-#include <stdlib.h>
-
-void overflow(struct Allocator* alloc, struct Random* rand)
-{
-    uint32_t x = Random_uint32(rand);
-    for (int i = 0; i < 32; i++) {
-        struct Allocator* a = Allocator_child(alloc);
-        uint32_t count = ((x>>i) & 31);
-        while (count--) {
-            a = Allocator_child(alloc);
-            if (count == 27) {
-                overflow(a, rand);
-            }
-        }
-    }
-}
-
-void abort()
-{
-    exit(0);
-}
-
-int main()
-{
-    Security_maxMemory(1<<21, NULL);
-    struct Allocator* alloc = MallocAllocator_new(1<<19);
-    struct Random* rand = Random_new(alloc, NULL, NULL);
-    overflow(alloc, rand);
-    // sometimes it doesn't use up all of it's space.
-    exit(1);
-}

+ 2 - 12
net/Ducttape.c

@@ -77,19 +77,9 @@ static inline uint8_t incomingDHT(struct Message* message,
     Bits_memcpy(dht.bytes, message->bytes, length);
 
     dht.address = addr;
+    dht.allocator = message->alloc;
 
-    uint8_t buffer[PER_MESSAGE_BUF_SZ];
-    dht.allocator = BufferAllocator_new(buffer, PER_MESSAGE_BUF_SZ);
-
-    struct Jmp j;
-    Jmp_try(j) {
-        BufferAllocator_onOOM(dht.allocator, &j.handler);
-        DHTModuleRegistry_handleIncoming(&dht, context->registry);
-    } Jmp_catch {
-        uint8_t printed[60];
-        Address_print(printed, addr);
-        Log_warn(context->logger, "Parsing message from [%s] failed; out of memory.", printed);
-    }
+    DHTModuleRegistry_handleIncoming(&dht, context->registry);
 
     // TODO: return something meaningful.
     return Error_NONE;

+ 18 - 3
test/cjdroute_routerPing_test.c

@@ -65,20 +65,35 @@ int main()
 
     // bad checksum
     udp->checksum_be = 1;
-    struct Message m = { .bytes = buff+PADDING, .length = buffLen, .padding = PADDING };
+    struct Message m = {
+        .bytes = buff+PADDING,
+        .length = buffLen,
+        .padding = PADDING,
+        .alloc = alloc
+    };
     Ducttape_injectIncomingForMe(&m, &dt->pub, herPublicKey);
     Assert_always(!dt->switchInterface.receiverContext);
 
     // zero checksum
     udp->checksum_be = 0;
-    struct Message m2 = { .bytes = buff+PADDING, .length = buffLen, .padding = PADDING };
+    struct Message m2 = {
+        .bytes = buff+PADDING,
+        .length = buffLen,
+        .padding = PADDING,
+        .alloc = alloc
+    };
     Ducttape_injectIncomingForMe(&m2, &dt->pub, herPublicKey);
     Assert_always(!dt->switchInterface.receiverContext);
 
     // good checksum
     udp->checksum_be =
         Checksum_udpIp6(ip6->sourceAddr, (uint8_t*) udp, strlen(pingBenc) + Headers_UDPHeader_SIZE);
-    struct Message m3 = { .bytes = buff+PADDING, .length = buffLen, .padding = PADDING };
+    struct Message m3 = {
+        .bytes = buff+PADDING,
+        .length = buffLen,
+        .padding = PADDING,
+        .alloc = alloc
+    };
     Ducttape_injectIncomingForMe(&m3, &dt->pub, herPublicKey);
     Assert_always(dt->switchInterface.receiverContext);
 

+ 18 - 19
tunnel/IpTunnel.c

@@ -168,15 +168,12 @@ static uint8_t sendToNode(struct Message* message,
 
 static uint8_t sendControlMessage(Dict* dict,
                                   struct IpTunnel_Connection* connection,
+                                  struct Allocator* requestAlloc,
                                   struct IpTunnel_pvt* context)
 {
-    struct Message* message;
-    Message_STACK(message, 512, 512);
+    struct Message* message = Message_new(512, 512, requestAlloc);
 
-    struct Allocator* alloc;
-    BufferAllocator_STACK(alloc, 256);
-
-    struct Writer* w = ArrayWriter_new(message->bytes, message->length, alloc);
+    struct Writer* w = ArrayWriter_new(message->bytes, message->length, requestAlloc);
     StandardBencSerializer_get()->serializeDictionary(w, dict);
     message->length = w->bytesWritten;
 
@@ -232,7 +229,10 @@ static uint8_t requestAddresses(struct IpTunnel_Connection* conn,
         String_CONST("txid"), String_OBJ((&(String){ .len = 4, .bytes = (char*)&number })),
         NULL
     ));
-    return sendControlMessage(&d, conn, context);
+    struct Allocator* msgAlloc = Allocator_child(context->allocator);
+    uint8_t ret = sendControlMessage(&d, conn, msgAlloc, context);
+    Allocator_free(msgAlloc);
+    return ret;
 }
 
 /**
@@ -308,7 +308,7 @@ static uint8_t isControlMessageInvalid(struct Message* message, struct IpTunnel_
 
 static uint8_t requestForAddresses(Dict* request,
                                    struct IpTunnel_Connection* conn,
-                                   struct Allocator* alloc,
+                                   struct Allocator* requestAlloc,
                                    struct IpTunnel_pvt* context)
 {
     #ifdef Log_DEBUG
@@ -321,20 +321,20 @@ static uint8_t requestForAddresses(Dict* request,
         Log_warn(context->logger, "got request for addresses from outgoing connection");
         return Error_INVALID;
     }
-    Dict* addresses = Dict_new(alloc);
+    Dict* addresses = Dict_new(requestAlloc);
     bool noAddresses = true;
     if (!Bits_isZero(conn->connectionIp6, 16)) {
         Dict_putString(addresses,
                        String_CONST("ip6"),
-                       String_newBinary((char*)conn->connectionIp6, 16, alloc),
-                       alloc);
+                       String_newBinary((char*)conn->connectionIp6, 16, requestAlloc),
+                       requestAlloc);
         noAddresses = false;
     }
     if (!Bits_isZero(conn->connectionIp4, 4)) {
         Dict_putString(addresses,
                        String_CONST("ip4"),
-                       String_newBinary((char*)conn->connectionIp4, 4, alloc),
-                       alloc);
+                       String_newBinary((char*)conn->connectionIp4, 4, requestAlloc),
+                       requestAlloc);
         noAddresses = false;
     }
     if (noAddresses) {
@@ -342,15 +342,15 @@ static uint8_t requestForAddresses(Dict* request,
         return 0;
     }
 
-    Dict* msg = Dict_new(alloc);
-    Dict_putDict(msg, String_CONST("addresses"), addresses, alloc);
+    Dict* msg = Dict_new(requestAlloc);
+    Dict_putDict(msg, String_CONST("addresses"), addresses, requestAlloc);
 
     String* txid = Dict_getString(request, String_CONST("txid"));
     if (txid) {
-        Dict_putString(msg, String_CONST("txid"), txid, alloc);
+        Dict_putString(msg, String_CONST("txid"), txid, requestAlloc);
     }
 
-    return sendControlMessage(msg, conn, context);
+    return sendControlMessage(msg, conn, requestAlloc, context);
 }
 
 static void addAddressCallback(Dict* responseMessage, void* vcontext)
@@ -492,8 +492,7 @@ static uint8_t incomingControlMessage(struct Message* message,
         message->bytes[message->length - 1] = lastChar;
     #endif
 
-    struct Allocator* alloc;
-    BufferAllocator_STACK(alloc, 1024);
+    struct Allocator* alloc = Allocator_child(message->alloc);
 
     struct Reader* r = ArrayReader_new(message->bytes, message->length, alloc);
     Dict dStore;

+ 1 - 0
tunnel/test/IpTunnel_test.c

@@ -101,6 +101,7 @@ int main()
 
     struct Message* message;
     Message_STACK(message, 64, 512);
+    message->alloc = alloc;
 
     const char* requestForAddresses =
         "d"

+ 1 - 2
util/events/libuv/Event.c

@@ -43,8 +43,7 @@ static void handleEvent(uv_poll_t* handle, int status, int events)
 
 static void freeEvent2(uv_handle_t* handle)
 {
-    struct Allocator_OnFreeJob* job = Identity_cast((struct Allocator_OnFreeJob*)handle->data);
-    job->complete(job);
+    Allocator_onFreeComplete((struct Allocator_OnFreeJob*)handle->data);
 }
 
 static int freeEvent(struct Allocator_OnFreeJob* job)

+ 1 - 1
util/events/libuv/EventBase.c

@@ -87,7 +87,7 @@ void EventBase_beginLoop(struct EventBase* eventBase)
 
     if (ctx->onFree) {
         uv_loop_delete(ctx->loop);
-        ctx->onFree->complete(ctx->onFree);
+        Allocator_onFreeComplete(ctx->onFree);
         return;
     }
 }

+ 3 - 7
util/events/libuv/Pipe.c

@@ -158,9 +158,7 @@ static void onClose(uv_handle_t* handle)
     struct Pipe_pvt* pipe = Identity_cast((struct Pipe_pvt*)handle->data);
     handle->data = NULL;
     if (pipe->closeHandlesOnFree && !pipe->server.data && !pipe->peer.data) {
-        struct Allocator_OnFreeJob* job =
-            Identity_cast((struct Allocator_OnFreeJob*) pipe->closeHandlesOnFree);
-        job->complete(job);
+        Allocator_onFreeComplete((struct Allocator_OnFreeJob*) pipe->closeHandlesOnFree);
     }
 }
 
@@ -177,7 +175,7 @@ static void incoming(uv_stream_t* stream, ssize_t nread, uv_buf_t buf)
     struct Allocator* alloc = buf.base ? ALLOC(buf.base) : NULL;
     pipe->isInCallback = 1;
 
-    Assert_true(!alloc || alloc->free == pipe->alloc->free);
+    Assert_true(!alloc || alloc->fileName == pipe->alloc->fileName);
 
     if (nread < 0) {
         if (uv_last_error(pipe->peer.loop).code == UV_EOF) {
@@ -210,9 +208,7 @@ static void incoming(uv_stream_t* stream, ssize_t nread, uv_buf_t buf)
 
     pipe->isInCallback = 0;
     if (pipe->blockFreeInsideCallback) {
-        struct Allocator_OnFreeJob* job =
-            Identity_cast((struct Allocator_OnFreeJob*) pipe->blockFreeInsideCallback);
-        job->complete(job);
+        Allocator_onFreeComplete((struct Allocator_OnFreeJob*) pipe->blockFreeInsideCallback);
     }
 }
 

+ 1 - 2
util/events/libuv/Process.c

@@ -30,8 +30,7 @@ struct Process_pvt
 
 static void onFree2(uv_handle_t* process)
 {
-    struct Allocator_OnFreeJob* j = Identity_cast((struct Allocator_OnFreeJob*) process->data);
-    j->complete(j);
+    Allocator_onFreeComplete((struct Allocator_OnFreeJob*) process->data);
 }
 
 static int onFree(struct Allocator_OnFreeJob* job)

+ 1 - 2
util/events/libuv/Timeout.c

@@ -41,8 +41,7 @@ static void handleEvent(uv_timer_t* handle, int status)
 
 static void onFree2(uv_handle_t* timer)
 {
-    struct Allocator_OnFreeJob* j = Identity_cast((struct Allocator_OnFreeJob*)timer->data);
-    j->complete(j);
+    Allocator_onFreeComplete(timer->data);
 }
 
 static int onFree(struct Allocator_OnFreeJob* job)

+ 2 - 6
util/events/libuv/UDPAddrInterface.c

@@ -175,9 +175,7 @@ static void incoming(uv_udp_t* handle,
 
     context->inCallback = 0;
     if (context->blockFreeInsideCallback) {
-        struct Allocator_OnFreeJob* job =
-            Identity_cast((struct Allocator_OnFreeJob*) context->blockFreeInsideCallback);
-        job->complete(job);
+        Allocator_onFreeComplete((struct Allocator_OnFreeJob*) context->blockFreeInsideCallback);
     }
 }
 
@@ -201,9 +199,7 @@ static void onClosed(uv_handle_t* wasClosed)
 {
     struct UDPAddrInterface_pvt* context =
         Identity_cast((struct UDPAddrInterface_pvt*) wasClosed->data);
-    struct Allocator_OnFreeJob* job =
-        Identity_cast((struct Allocator_OnFreeJob*) context->closeHandleOnFree);
-    job->complete(job);
+    Allocator_onFreeComplete((struct Allocator_OnFreeJob*) context->closeHandleOnFree);
 }
 
 static int closeHandleOnFree(struct Allocator_OnFreeJob* job)