cmark

My personal build of CMark ✏️

Commit
25429c96f6554ffac415f9d865934b1183f3398e
Parent
ab6c81b960e86b26c7fda366f51ff29d1683a555
Author
Vicent Marti <tanoku@gmail.com>
Date

cmark: Implement support for custom allocators

Diffstat

22 files changed, 343 insertions, 285 deletions

Status File Name N° Changes Insertions Deletions
Modified man/man3/cmark.3 81 57 24
Modified src/blocks.c 110 65 45
Modified src/buffer.c 19 12 7
Modified src/buffer.h 9 5 4
Modified src/chunk.h 17 9 8
Modified src/cmark.c 30 0 30
Modified src/cmark.h 38 24 14
Modified src/html.c 2 1 1
Modified src/inlines.c 158 81 77
Modified src/inlines.h 8 4 4
Modified src/iterator.c 10 6 4
Modified src/iterator.h 2 2 0
Modified src/main.c 2 1 1
Deleted src/memory.h 12 0 12
Modified src/node.c 63 36 27
Modified src/node.h 10 6 4
Modified src/parser.h 2 2 0
Modified src/references.c 35 19 16
Modified src/references.h 4 3 1
Modified src/render.c 12 7 5
Modified src/render.h 2 2 0
Modified src/xml.c 2 1 1
diff --git a/man/man3/cmark.3 b/man/man3/cmark.3
@@ -1,4 +1,4 @@
-.TH cmark 3 "May 14, 2016" "LOCAL" "Library Functions Manual"
+.TH cmark 3 "June 02, 2016" "LOCAL" "Library Functions Manual"
 .SH
 NAME
 .PP
@@ -15,7 +15,7 @@ Simple Interface
 .PP
 Convert \f[I]text\f[] (assumed to be a UTF\-8 encoded string with length
 \f[I]len\f[]) from CommonMark Markdown to HTML, returning a
-null\-terminated, UTF\-8\-encoded string. It is the caller's
+null\-terminated, UTF\-8\-encoded string. It is the caller\[cq]s
 responsibility to free the returned buffer.
 
 .SS
@@ -96,6 +96,25 @@ typedef enum {
 
 
 .SS
+Custom memory allocator support
+
+.PP
+.nf
+\fC
+.RS 0n
+typedef struct cmark_mem {
+	void *(*calloc)(size_t, size_t);
+	void (*free)(void *);
+} cmark_mem;
+.RE
+\f[]
+.fi
+
+.PP
+Defines the memory allocation functions to be used by CMark when parsing
+and allocating a document tree
+
+.SS
 Creating and Destroying Nodes
 
 .PP
@@ -103,8 +122,15 @@ Creating and Destroying Nodes
 
 .PP
 Creates a new node of type \f[I]type\f[]. Note that the node may have
-other required properties, which it is the caller's responsibility to
-assign.
+other required properties, which it is the caller\[cq]s responsibility
+to assign.
+
+.PP
+\fIcmark_node *\f[] \fBcmark_node_new2\f[](\fIcmark_node_type type\f[], \fIcmark_mem *mem\f[])
+
+.PP
+Same as \f[C]cmark_node_new\f[], but explicitly listing the memory
+allocator used to allocate the node
 
 .PP
 \fIvoid\f[] \fBcmark_node_free\f[](\fIcmark_node *node\f[])
@@ -378,7 +404,8 @@ Returns 1 if \f[I]node\f[] is a tight list, 0 otherwise.
 \fIint\f[] \fBcmark_node_set_list_tight\f[](\fIcmark_node *node\f[], \fIint tight\f[])
 
 .PP
-Sets the "tightness" of a list. Returns 1 on success, 0 on failure.
+Sets the \[lq]tightness\[rq] of a list. Returns 1 on success, 0 on
+failure.
 
 .PP
 \fIconst char *\f[] \fBcmark_node_get_fence_info\f[](\fIcmark_node *node\f[])
@@ -425,31 +452,31 @@ on failure.
 \fIconst char *\f[] \fBcmark_node_get_on_enter\f[](\fIcmark_node *node\f[])
 
 .PP
-Returns the literal "on enter" text for a custom \f[I]node\f[], or an
-empty string if no on_enter is set.
+Returns the literal \[lq]on enter\[rq] text for a custom \f[I]node\f[],
+or an empty string if no on_enter is set.
 
 .PP
 \fIint\f[] \fBcmark_node_set_on_enter\f[](\fIcmark_node *node\f[], \fIconst char *on_enter\f[])
 
 .PP
-Sets the literal text to render "on enter" for a custom \f[I]node\f[].
-Any children of the node will be rendered after this text. Returns 1 on
-success 0 on failure.
+Sets the literal text to render \[lq]on enter\[rq] for a custom
+\f[I]node\f[]. Any children of the node will be rendered after this
+text. Returns 1 on success 0 on failure.
 
 .PP
 \fIconst char *\f[] \fBcmark_node_get_on_exit\f[](\fIcmark_node *node\f[])
 
 .PP
-Returns the literal "on exit" text for a custom \f[I]node\f[], or an
-empty string if no on_exit is set.
+Returns the literal \[lq]on exit\[rq] text for a custom \f[I]node\f[],
+or an empty string if no on_exit is set.
 
 .PP
 \fIint\f[] \fBcmark_node_set_on_exit\f[](\fIcmark_node *node\f[], \fIconst char *on_exit\f[])
 
 .PP
-Sets the literal text to render "on exit" for a custom \f[I]node\f[].
-Any children of the node will be rendered before this text. Returns 1 on
-success 0 on failure.
+Sets the literal text to render \[lq]on exit\[rq] for a custom
+\f[I]node\f[]. Any children of the node will be rendered before this
+text. Returns 1 on success 0 on failure.
 
 .PP
 \fIint\f[] \fBcmark_node_get_start_line\f[](\fIcmark_node *node\f[])
@@ -563,6 +590,12 @@ cmark_parser_free(parser);
 Creates a new parser object.
 
 .PP
+\fIcmark_parser *\f[] \fBcmark_parser_new2\f[](\fIint options\f[], \fIcmark_mem *mem\f[])
+
+.PP
+Creates a new parser object with the given memory allocator
+
+.PP
 \fIvoid\f[] \fBcmark_parser_free\f[](\fIcmark_parser *parser\f[])
 
 .PP
@@ -604,36 +637,36 @@ Rendering
 \fIchar *\f[] \fBcmark_render_xml\f[](\fIcmark_node *root\f[], \fIint options\f[])
 
 .PP
-Render a \f[I]node\f[] tree as XML. It is the caller's responsibility to
-free the returned buffer.
+Render a \f[I]node\f[] tree as XML. It is the caller\[cq]s
+responsibility to free the returned buffer.
 
 .PP
 \fIchar *\f[] \fBcmark_render_html\f[](\fIcmark_node *root\f[], \fIint options\f[])
 
 .PP
 Render a \f[I]node\f[] tree as an HTML fragment. It is up to the user to
-add an appropriate header and footer. It is the caller's responsibility
-to free the returned buffer.
+add an appropriate header and footer. It is the caller\[cq]s
+responsibility to free the returned buffer.
 
 .PP
 \fIchar *\f[] \fBcmark_render_man\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIint width\f[])
 
 .PP
 Render a \f[I]node\f[] tree as a groff man page, without the header. It
-is the caller's responsibility to free the returned buffer.
+is the caller\[cq]s responsibility to free the returned buffer.
 
 .PP
 \fIchar *\f[] \fBcmark_render_commonmark\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIint width\f[])
 
 .PP
-Render a \f[I]node\f[] tree as a commonmark document. It is the caller's
-responsibility to free the returned buffer.
+Render a \f[I]node\f[] tree as a commonmark document. It is the
+caller\[cq]s responsibility to free the returned buffer.
 
 .PP
 \fIchar *\f[] \fBcmark_render_latex\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIint width\f[])
 
 .PP
-Render a \f[I]node\f[] tree as a LaTeX document. It is the caller's
+Render a \f[I]node\f[] tree as a LaTeX document. It is the caller\[cq]s
 responsibility to free the returned buffer.
 
 .SS
@@ -744,7 +777,7 @@ with the replacement character U+FFFD.
 .fi
 
 .PP
-Convert straight quotes to curly, \-\-\- to em dashes, \-\- to en
+Convert straight quotes to curly, \[em] to em dashes, \[en] to en
 dashes.
 
 .SS
diff --git a/src/blocks.c b/src/blocks.c
@@ -44,36 +44,38 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
 static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
                            bufsize_t bytes);
 
-static cmark_node *make_block(cmark_node_type tag, int start_line,
+static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag, int start_line,
                               int start_column) {
   cmark_node *e;
 
-  e = (cmark_node *)cmark_calloc(1, sizeof(*e));
+  e = (cmark_node *)mem->calloc(1, sizeof(*e));
+  cmark_strbuf_init(mem, &e->content, 32);
   e->type = tag;
   e->open = true;
   e->start_line = start_line;
   e->start_column = start_column;
   e->end_line = start_line;
-  cmark_strbuf_init(&e->string_content, 32);
 
   return e;
 }
 
 // Create a root document node.
-static cmark_node *make_document() {
-  cmark_node *e = make_block(CMARK_NODE_DOCUMENT, 1, 1);
+static cmark_node *make_document(cmark_mem *mem) {
+  cmark_node *e = make_block(mem, CMARK_NODE_DOCUMENT, 1, 1);
   return e;
 }
 
-cmark_parser *cmark_parser_new(int options) {
-  cmark_parser *parser = (cmark_parser *)cmark_calloc(1, sizeof(cmark_parser));
-  cmark_node *document = make_document();
-  cmark_strbuf *line = (cmark_strbuf *)cmark_calloc(1, sizeof(cmark_strbuf));
-  cmark_strbuf *buf = (cmark_strbuf *)cmark_calloc(1, sizeof(cmark_strbuf));
-  cmark_strbuf_init(line, 256);
-  cmark_strbuf_init(buf, 0);
-
-  parser->refmap = cmark_reference_map_new();
+cmark_parser *cmark_parser_new2(int options, cmark_mem *mem) {
+  cmark_parser *parser = mem->calloc(1, sizeof(cmark_parser));
+  parser->mem = mem;
+
+  cmark_node *document = make_document(mem);
+  cmark_strbuf *line = mem->calloc(1, sizeof(cmark_strbuf));
+  cmark_strbuf *buf = mem->calloc(1, sizeof(cmark_strbuf));
+  cmark_strbuf_init(mem, line, 256);
+  cmark_strbuf_init(mem, buf, 0);
+
+  parser->refmap = cmark_reference_map_new(mem);
   parser->root = document;
   parser->current = document;
   parser->line_number = 0;
@@ -93,13 +95,30 @@ cmark_parser *cmark_parser_new(int options) {
   return parser;
 }
 
+static void *xcalloc(size_t nmem, size_t size) {
+  void *ptr = calloc(nmem, size);
+  if (!ptr) abort();
+  return ptr;
+}
+
+static void xfree(void *ptr) {
+  free(ptr);
+}
+
+cmark_mem DEFAULT_MEM_ALLOCATOR = { xcalloc, xfree };
+
+cmark_parser *cmark_parser_new(int options) {
+  return cmark_parser_new2(options, &DEFAULT_MEM_ALLOCATOR);
+}
+
 void cmark_parser_free(cmark_parser *parser) {
+  cmark_mem *mem = parser->mem;
   cmark_strbuf_free(parser->curline);
-  free(parser->curline);
+  mem->free(parser->curline);
   cmark_strbuf_free(parser->linebuf);
-  free(parser->linebuf);
+  mem->free(parser->linebuf);
   cmark_reference_map_free(parser->refmap);
-  free(parser);
+  mem->free(parser);
 }
 
 static cmark_node *finalize(cmark_parser *parser, cmark_node *b);
@@ -153,10 +172,10 @@ static void add_line(cmark_node *node, cmark_chunk *ch, cmark_parser *parser) {
     // add space characters:
     chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);
     for (i = 0; i < chars_to_tab; i++) {
-      cmark_strbuf_putc(&node->string_content, ' ');
+      cmark_strbuf_putc(&node->content, ' ');
     }
   }
-  cmark_strbuf_put(&node->string_content, ch->data + parser->offset,
+  cmark_strbuf_put(&node->content, ch->data + parser->offset,
                    ch->len - parser->offset);
 }
 
@@ -229,7 +248,6 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
   cmark_node *parent;
 
   parent = b->parent;
-
   assert(b->open); // shouldn't call finalize on closed blocks
   b->open = false;
 
@@ -251,15 +269,17 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
     b->end_column = parser->last_line_length;
   }
 
+  cmark_strbuf *node_content = &b->content;
+
   switch (b->type) {
   case CMARK_NODE_PARAGRAPH:
-    while (cmark_strbuf_at(&b->string_content, 0) == '[' &&
-           (pos = cmark_parse_reference_inline(&b->string_content,
+    while (cmark_strbuf_at(node_content, 0) == '[' &&
+           (pos = cmark_parse_reference_inline(parser->mem, node_content,
                                                parser->refmap))) {
 
-      cmark_strbuf_drop(&b->string_content, pos);
+      cmark_strbuf_drop(node_content, pos);
     }
-    if (is_blank(&b->string_content, 0)) {
+    if (is_blank(node_content, 0)) {
       // remove blank node (former reference def)
       cmark_node_free(b);
     }
@@ -267,34 +287,33 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
 
   case CMARK_NODE_CODE_BLOCK:
     if (!b->as.code.fenced) { // indented code
-      remove_trailing_blank_lines(&b->string_content);
-      cmark_strbuf_putc(&b->string_content, '\n');
+      remove_trailing_blank_lines(node_content);
+      cmark_strbuf_putc(node_content, '\n');
     } else {
-
       // first line of contents becomes info
-      for (pos = 0; pos < b->string_content.size; ++pos) {
-        if (S_is_line_end_char(b->string_content.ptr[pos]))
+      for (pos = 0; pos < node_content->size; ++pos) {
+        if (S_is_line_end_char(node_content->ptr[pos]))
           break;
       }
-      assert(pos < b->string_content.size);
+      assert(pos < node_content->size);
 
-      cmark_strbuf tmp = GH_BUF_INIT;
-      houdini_unescape_html_f(&tmp, b->string_content.ptr, pos);
+      cmark_strbuf tmp = CMARK_BUF_INIT(parser->mem);
+      houdini_unescape_html_f(&tmp, node_content->ptr, pos);
       cmark_strbuf_trim(&tmp);
       cmark_strbuf_unescape(&tmp);
       b->as.code.info = cmark_chunk_buf_detach(&tmp);
 
-      if (b->string_content.ptr[pos] == '\r')
+      if (node_content->ptr[pos] == '\r')
         pos += 1;
-      if (b->string_content.ptr[pos] == '\n')
+      if (node_content->ptr[pos] == '\n')
         pos += 1;
-      cmark_strbuf_drop(&b->string_content, pos);
+      cmark_strbuf_drop(node_content, pos);
     }
-    b->as.code.literal = cmark_chunk_buf_detach(&b->string_content);
+    b->as.code.literal = cmark_chunk_buf_detach(node_content);
     break;
 
   case CMARK_NODE_HTML_BLOCK:
-    b->as.literal = cmark_chunk_buf_detach(&b->string_content);
+    b->as.literal = cmark_chunk_buf_detach(node_content);
     break;
 
   case CMARK_NODE_LIST:      // determine tight/loose status
@@ -328,6 +347,7 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
   default:
     break;
   }
+
   return parent;
 }
 
@@ -342,7 +362,7 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
     parent = finalize(parser, parent);
   }
 
-  cmark_node *child = make_block(block_type, parser->line_number, start_column);
+  cmark_node *child = make_block(parser->mem, block_type, parser->line_number, start_column);
   child->parent = parent;
 
   if (parent->last_child) {
@@ -358,7 +378,7 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
 
 // Walk through node and all children, recursively, parsing
 // string content into inline content where appropriate.
-static void process_inlines(cmark_node *root, cmark_reference_map *refmap,
+static void process_inlines(cmark_mem *mem, cmark_node *root, cmark_reference_map *refmap,
                             int options) {
   cmark_iter *iter = cmark_iter_new(root);
   cmark_node *cur;
@@ -368,7 +388,7 @@ static void process_inlines(cmark_node *root, cmark_reference_map *refmap,
     cur = cmark_iter_get_node(iter);
     if (ev_type == CMARK_EVENT_ENTER) {
       if (contains_inlines(cur->type)) {
-        cmark_parse_inlines(cur, refmap, options);
+        cmark_parse_inlines(mem, cur, refmap, options);
       }
     }
   }
@@ -379,7 +399,7 @@ static void process_inlines(cmark_node *root, cmark_reference_map *refmap,
 // Attempts to parse a list item marker (bullet or enumerated).
 // On success, returns length of the marker, and populates
 // data with the details.  On failure, returns 0.
-static bufsize_t parse_list_marker(cmark_chunk *input, bufsize_t pos,
+static bufsize_t parse_list_marker(cmark_mem *mem, cmark_chunk *input, bufsize_t pos,
                                    cmark_list **dataptr) {
   unsigned char c;
   bufsize_t startpos;
@@ -393,7 +413,7 @@ static bufsize_t parse_list_marker(cmark_chunk *input, bufsize_t pos,
     if (!cmark_isspace(peek_at(input, pos))) {
       return 0;
     }
-    data = (cmark_list *)cmark_calloc(1, sizeof(*data));
+    data = (cmark_list *)mem->calloc(1, sizeof(*data));
     data->marker_offset = 0; // will be adjusted later
     data->list_type = CMARK_BULLET_LIST;
     data->bullet_char = c;
@@ -419,7 +439,7 @@ static bufsize_t parse_list_marker(cmark_chunk *input, bufsize_t pos,
       if (!cmark_isspace(peek_at(input, pos))) {
         return 0;
       }
-      data = (cmark_list *)cmark_calloc(1, sizeof(*data));
+      data = (cmark_list *)mem->calloc(1, sizeof(*data));
       data->marker_offset = 0; // will be adjusted later
       data->list_type = CMARK_ORDERED_LIST;
       data->bullet_char = 0;
@@ -451,7 +471,7 @@ static cmark_node *finalize_document(cmark_parser *parser) {
   }
 
   finalize(parser, parser->root);
-  process_inlines(parser->root, parser->refmap, parser->options);
+  process_inlines(parser->mem, parser->root, parser->refmap, parser->options);
 
   return parser->root;
 }
@@ -899,7 +919,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
                              parser->first_nonspace + 1);
       S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
     } else if ((matched =
-                    parse_list_marker(input, parser->first_nonspace, &data)) &&
+                    parse_list_marker(parser->mem, input, parser->first_nonspace, &data)) &&
                (!indented || cont_type == CMARK_NODE_LIST)) {
       // Note that we can have new list items starting with >= 4
       // spaces indent, as long as the list container is still open.
diff --git a/src/buffer.c b/src/buffer.c
@@ -21,7 +21,8 @@ unsigned char cmark_strbuf__initbuf[1];
 #define MIN(x, y) ((x < y) ? x : y)
 #endif
 
-void cmark_strbuf_init(cmark_strbuf *buf, bufsize_t initial_size) {
+void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, bufsize_t initial_size) {
+  buf->mem = mem;
   buf->asize = 0;
   buf->size = 0;
   buf->ptr = cmark_strbuf__initbuf;
@@ -41,7 +42,7 @@ void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size) {
     return;
 
   if (target_size > (bufsize_t)(SIZE_MAX / 4))
-    cmark_trigger_oom();
+    abort();
 
   /* Oversize the buffer by 50% to guarantee amortized linear time
    * complexity on append operations. */
@@ -49,7 +50,11 @@ void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size) {
   new_size += 1;
   new_size = (new_size + 7) & ~7;
 
-  unsigned char *new_ptr = cmark_realloc(buf->asize ? buf->ptr : NULL, new_size);
+  unsigned char *new_ptr = buf->mem->calloc(new_size, 1);
+  if (buf->ptr != cmark_strbuf__initbuf) {
+    memcpy(new_ptr, buf->ptr, buf->size);
+    buf->mem->free(buf->ptr);
+  }
 
   buf->asize = new_size;
   buf->ptr = new_ptr;
@@ -62,9 +67,9 @@ void cmark_strbuf_free(cmark_strbuf *buf) {
     return;
 
   if (buf->ptr != cmark_strbuf__initbuf)
-    free(buf->ptr);
+    buf->mem->free(buf->ptr);
 
-  cmark_strbuf_init(buf, 0);
+  cmark_strbuf_init(buf->mem, buf, 0);
 }
 
 void cmark_strbuf_clear(cmark_strbuf *buf) {
@@ -147,10 +152,10 @@ unsigned char *cmark_strbuf_detach(cmark_strbuf *buf) {
 
   if (buf->asize == 0) {
     /* return an empty string */
-    return cmark_calloc(1, 1);
+    return buf->mem->calloc(1, 1);
   }
 
-  cmark_strbuf_init(buf, 0);
+  cmark_strbuf_init(buf->mem, buf, 0);
   return data;
 }
 
diff --git a/src/buffer.h b/src/buffer.h
@@ -7,6 +7,7 @@
 #include <limits.h>
 #include <stdbool.h>
 #include "config.h"
+#include "cmark.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -15,22 +16,22 @@ extern "C" {
 typedef ssize_t bufsize_t;
 
 typedef struct {
+  cmark_mem *mem;
   unsigned char *ptr;
   bufsize_t asize, size;
 } cmark_strbuf;
 
 extern unsigned char cmark_strbuf__initbuf[];
 
-#define GH_BUF_INIT                                                            \
-  { cmark_strbuf__initbuf, 0, 0 }
+#define CMARK_BUF_INIT(mem) {mem, cmark_strbuf__initbuf, 0, 0}
 
 /**
  * Initialize a cmark_strbuf structure.
  *
- * For the cases where GH_BUF_INIT cannot be used to do static
+ * For the cases where CMARK_BUF_INIT cannot be used to do static
  * initialization.
  */
-void cmark_strbuf_init(cmark_strbuf *buf, bufsize_t initial_size);
+void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, bufsize_t initial_size);
 
 /**
  * Grow the buffer to hold at least `target_size` bytes.
diff --git a/src/chunk.h b/src/chunk.h
@@ -4,9 +4,10 @@
 #include <string.h>
 #include <stdlib.h>
 #include <assert.h>
+#include "cmark.h"
+#include "buffer.h"
 #include "memory.h"
 #include "cmark_ctype.h"
-#include "buffer.h"
 
 #define CMARK_CHUNK_EMPTY                                                      \
   { NULL, 0, 0 }
@@ -17,9 +18,9 @@ typedef struct {
   bufsize_t alloc; // also implies a NULL-terminated string
 } cmark_chunk;
 
-static CMARK_INLINE void cmark_chunk_free(cmark_chunk *c) {
+static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) {
   if (c->alloc)
-    free(c->data);
+    mem->free(c->data);
 
   c->data = NULL;
   c->alloc = 0;
@@ -56,13 +57,13 @@ static CMARK_INLINE bufsize_t
   return p ? (bufsize_t)(p - ch->data) : ch->len;
 }
 
-static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_chunk *c) {
+static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem, cmark_chunk *c) {
   unsigned char *str;
 
   if (c->alloc) {
     return (char *)c->data;
   }
-  str = (unsigned char *)cmark_calloc(c->len + 1, 1);
+  str = (unsigned char *)mem->calloc(c->len + 1, 1);
   if (c->len > 0) {
     memcpy(str, c->data, c->len);
   }
@@ -73,9 +74,9 @@ static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_chunk *c) {
   return (char *)str;
 }
 
-static CMARK_INLINE void cmark_chunk_set_cstr(cmark_chunk *c, const char *str) {
+static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c, const char *str) {
   if (c->alloc) {
-    free(c->data);
+    mem->free(c->data);
   }
   if (str == NULL) {
     c->len = 0;
@@ -83,7 +84,7 @@ static CMARK_INLINE void cmark_chunk_set_cstr(cmark_chunk *c, const char *str) {
     c->alloc = 0;
   } else {
     c->len = strlen(str);
-    c->data = (unsigned char *)cmark_calloc(c->len + 1, 1);
+    c->data = (unsigned char *)mem->calloc(c->len + 1, 1);
     c->alloc = 1;
     memcpy(c->data, str, c->len + 1);
   }
diff --git a/src/cmark.c b/src/cmark.c
@@ -10,36 +10,6 @@ int cmark_version() { return CMARK_VERSION; }
 
 const char *cmark_version_string() { return CMARK_VERSION_STRING; }
 
-void (*_cmark_on_oom)(void) = NULL;
-
-void cmark_trigger_oom(void)
-{
-	if (_cmark_on_oom)
-		_cmark_on_oom();
-	abort();
-}
-
-void cmark_set_oom_handler(void (*handler)(void))
-{
-	_cmark_on_oom = handler;
-}
-
-void *cmark_calloc(size_t nmem, size_t size)
-{
-	void *ptr = calloc(nmem, size);
-	if (!ptr)
-		cmark_trigger_oom();
-	return ptr;
-}
-
-void *cmark_realloc(void *ptr, size_t size)
-{
-	void *ptr_new = realloc(ptr, size);
-	if (!ptr_new)
-		cmark_trigger_oom();
-	return ptr_new;
-}
-
 char *cmark_markdown_to_html(const char *text, size_t len, int options) {
   cmark_node *doc;
   char *result;
diff --git a/src/cmark.h b/src/cmark.h
@@ -4,6 +4,7 @@
 #include <stdio.h>
 #include <cmark_export.h>
 #include <cmark_version.h>
+#include "memory.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -88,6 +89,19 @@ typedef struct cmark_parser cmark_parser;
 typedef struct cmark_iter cmark_iter;
 
 /**
+ * ## Custom memory allocator support
+ */
+
+/** Defines the memory allocation functions to be used by CMark
+ * when parsing and allocating a document tree
+ */
+typedef struct cmark_mem {
+	void *(*calloc)(size_t, size_t);
+	void (*free)(void *);
+} cmark_mem;
+
+
+/**
  * ## Creating and Destroying Nodes
  */
 
@@ -97,6 +111,11 @@ typedef struct cmark_iter cmark_iter;
  */
 CMARK_EXPORT cmark_node *cmark_node_new(cmark_node_type type);
 
+/** Same as `cmark_node_new`, but explicitly listing the memory
+ * allocator used to allocate the node
+ */
+CMARK_EXPORT cmark_node *cmark_node_new2(cmark_node_type type, cmark_mem *mem);
+
 /** Frees the memory allocated for a node and any children.
  */
 CMARK_EXPORT void cmark_node_free(cmark_node *node);
@@ -437,6 +456,11 @@ CMARK_EXPORT void cmark_consolidate_text_nodes(cmark_node *root);
 CMARK_EXPORT
 cmark_parser *cmark_parser_new(int options);
 
+/** Creates a new parser object with the given memory allocator
+ */
+CMARK_EXPORT
+cmark_parser *cmark_parser_new2(int options, cmark_mem *mem);
+
 /** Frees memory allocated for a parser object.
  */
 CMARK_EXPORT
@@ -573,20 +597,6 @@ int cmark_version();
 CMARK_EXPORT
 const char *cmark_version_string();
 
-/** Set the callback function that will be issued whenever the
- * library hits an out of memory situation.
- *
- * This can happen when the heap memory allocator fails to allocate
- * a block of memory, or when the index of an in-memory buffer overflows
- *
- * If no OOM handler is set, the library will call `abort` and
- * terminate itself and the running process. If the custom OOM handler
- * you set does return (i.e. it does not gracefully terminate the
- * application), the behavior of the library will be unspecified.
- */
-CMARK_EXPORT
-void cmark_set_oom_handler(void (*handler)(void));
-
 /** # AUTHORS
  *
  * John MacFarlane, Vicent Marti,  Kārlis Gaņģis, Nick Wellnhofer.
diff --git a/src/html.c b/src/html.c
@@ -324,7 +324,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 
 char *cmark_render_html(cmark_node *root, int options) {
   char *result;
-  cmark_strbuf html = GH_BUF_INIT;
+  cmark_strbuf html = CMARK_BUF_INIT(cmark_node_mem(root));
   cmark_event_type ev_type;
   cmark_node *cur;
   struct render_state state = {&html, NULL};
diff --git a/src/inlines.c b/src/inlines.c
@@ -22,13 +22,13 @@ static const char *LEFTSINGLEQUOTE = "\xE2\x80\x98";
 static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";
 
 // Macros for creating various kinds of simple.
-#define make_str(s) make_literal(CMARK_NODE_TEXT, s)
-#define make_code(s) make_literal(CMARK_NODE_CODE, s)
-#define make_raw_html(s) make_literal(CMARK_NODE_HTML_INLINE, s)
-#define make_linebreak() make_simple(CMARK_NODE_LINEBREAK)
-#define make_softbreak() make_simple(CMARK_NODE_SOFTBREAK)
-#define make_emph() make_simple(CMARK_NODE_EMPH)
-#define make_strong() make_simple(CMARK_NODE_STRONG)
+#define make_str(mem, s) make_literal(mem, CMARK_NODE_TEXT, s)
+#define make_code(mem, s) make_literal(mem, CMARK_NODE_CODE, s)
+#define make_raw_html(mem, s) make_literal(mem, CMARK_NODE_HTML_INLINE, s)
+#define make_linebreak(mem) make_simple(mem, CMARK_NODE_LINEBREAK)
+#define make_softbreak(mem) make_simple(mem, CMARK_NODE_SOFTBREAK)
+#define make_emph(mem) make_simple(mem, CMARK_NODE_EMPH)
+#define make_strong(mem) make_simple(mem, CMARK_NODE_STRONG)
 
 typedef struct delimiter {
   struct delimiter *previous;
@@ -42,6 +42,7 @@ typedef struct delimiter {
 } delimiter;
 
 typedef struct {
+  cmark_mem *mem;
   cmark_chunk input;
   bufsize_t pos;
   cmark_reference_map *refmap;
@@ -57,44 +58,46 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
 
 static int parse_inline(subject *subj, cmark_node *parent, int options);
 
-static void subject_from_buf(subject *e, cmark_strbuf *buffer,
+static void subject_from_buf(cmark_mem *mem, subject *e, cmark_strbuf *buffer,
                              cmark_reference_map *refmap);
 static bufsize_t subject_find_special_char(subject *subj, int options);
 
 // Create an inline with a literal string value.
-static CMARK_INLINE cmark_node *make_literal(cmark_node_type t, cmark_chunk s) {
-  cmark_node *e = (cmark_node *)cmark_calloc(1, sizeof(*e));
+static CMARK_INLINE cmark_node *make_literal(cmark_mem *mem, cmark_node_type t, cmark_chunk s) {
+  cmark_node *e = (cmark_node *)mem->calloc(1, sizeof(*e));
+  cmark_strbuf_init(mem, &e->content, 0);
   e->type = t;
   e->as.literal = s;
   return e;
 }
 
 // Create an inline with no value.
-static CMARK_INLINE cmark_node *make_simple(cmark_node_type t) {
-  cmark_node *e = (cmark_node *)cmark_calloc(1, sizeof(*e));
+static CMARK_INLINE cmark_node *make_simple(cmark_mem *mem, cmark_node_type t) {
+  cmark_node *e = (cmark_node *)mem->calloc(1, sizeof(*e));
+  cmark_strbuf_init(mem, &e->content, 0);
   e->type = t;
   return e;
 }
 
 // Like make_str, but parses entities.
-static cmark_node *make_str_with_entities(cmark_chunk *content) {
-  cmark_strbuf unescaped = GH_BUF_INIT;
+static cmark_node *make_str_with_entities(cmark_mem *mem, cmark_chunk *content) {
+  cmark_strbuf unescaped = CMARK_BUF_INIT(mem);
 
   if (houdini_unescape_html(&unescaped, content->data, content->len)) {
-    return make_str(cmark_chunk_buf_detach(&unescaped));
+    return make_str(mem, cmark_chunk_buf_detach(&unescaped));
   } else {
-    return make_str(*content);
+    return make_str(mem, *content);
   }
 }
 
 // Duplicate a chunk by creating a copy of the buffer not by reusing the
 // buffer like cmark_chunk_dup does.
-static cmark_chunk chunk_clone(cmark_chunk *src) {
+static cmark_chunk chunk_clone(cmark_mem *mem, cmark_chunk *src) {
   cmark_chunk c;
   bufsize_t len = src->len;
 
   c.len = len;
-  c.data = (unsigned char *)cmark_calloc(len + 1, 1);
+  c.data = (unsigned char *)mem->calloc(len + 1, 1);
   c.alloc = 1;
   memcpy(c.data, src->data, len);
   c.data[len] = '\0';
@@ -102,8 +105,8 @@ static cmark_chunk chunk_clone(cmark_chunk *src) {
   return c;
 }
 
-static cmark_chunk cmark_clean_autolink(cmark_chunk *url, int is_email) {
-  cmark_strbuf buf = GH_BUF_INIT;
+static cmark_chunk cmark_clean_autolink(cmark_mem *mem, cmark_chunk *url, int is_email) {
+  cmark_strbuf buf = CMARK_BUF_INIT(mem);
 
   cmark_chunk_trim(url);
 
@@ -119,16 +122,17 @@ static cmark_chunk cmark_clean_autolink(cmark_chunk *url, int is_email) {
   return cmark_chunk_buf_detach(&buf);
 }
 
-static CMARK_INLINE cmark_node *make_autolink(cmark_chunk url, int is_email) {
-  cmark_node *link = make_simple(CMARK_NODE_LINK);
-  link->as.link.url = cmark_clean_autolink(&url, is_email);
+static CMARK_INLINE cmark_node *make_autolink(cmark_mem *mem, cmark_chunk url, int is_email) {
+  cmark_node *link = make_simple(mem, CMARK_NODE_LINK);
+  link->as.link.url = cmark_clean_autolink(mem, &url, is_email);
   link->as.link.title = cmark_chunk_literal("");
-  cmark_node_append_child(link, make_str_with_entities(&url));
+  cmark_node_append_child(link, make_str_with_entities(mem, &url));
   return link;
 }
 
-static void subject_from_buf(subject *e, cmark_strbuf *buffer,
+static void subject_from_buf(cmark_mem *mem, subject *e, cmark_strbuf *buffer,
                              cmark_reference_map *refmap) {
+  e->mem = mem;
   e->input.data = buffer->ptr;
   e->input.len = buffer->size;
   e->input.alloc = 0;
@@ -229,16 +233,16 @@ static cmark_node *handle_backticks(subject *subj) {
 
   if (endpos == 0) {      // not found
     subj->pos = startpos; // rewind
-    return make_str(openticks);
+    return make_str(subj->mem, openticks);
   } else {
-    cmark_strbuf buf = GH_BUF_INIT;
+    cmark_strbuf buf = CMARK_BUF_INIT(subj->mem);
 
     cmark_strbuf_set(&buf, subj->input.data + startpos,
                      endpos - startpos - openticks.len);
     cmark_strbuf_trim(&buf);
     cmark_strbuf_normalize_whitespace(&buf);
 
-    return make_code(cmark_chunk_buf_detach(&buf));
+    return make_code(subj->mem, cmark_chunk_buf_detach(&buf));
   }
 }
 
@@ -340,7 +344,7 @@ static void remove_delimiter(subject *subj, delimiter *delim) {
 
 static void push_delimiter(subject *subj, unsigned char c, bool can_open,
                            bool can_close, cmark_node *inl_text) {
-  delimiter *delim = (delimiter *)cmark_calloc(1, sizeof(delimiter));
+  delimiter *delim = (delimiter *)subj->mem->calloc(1, sizeof(delimiter));
   delim->delim_char = c;
   delim->can_open = can_open;
   delim->can_close = can_close;
@@ -373,7 +377,7 @@ static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart) {
     contents = cmark_chunk_dup(&subj->input, subj->pos - numdelims, numdelims);
   }
 
-  inl_text = make_str(contents);
+  inl_text = make_str(subj->mem, contents);
 
   if ((can_open || can_close) && (!(c == '\'' || c == '"') || smart)) {
     push_delimiter(subj, c, can_open, can_close, inl_text);
@@ -389,7 +393,7 @@ static cmark_node *handle_hyphen(subject *subj, bool smart) {
   advance(subj);
 
   if (!smart || peek_char(subj) != '-') {
-    return make_str(cmark_chunk_literal("-"));
+    return make_str(subj->mem, cmark_chunk_literal("-"));
   }
 
   while (smart && peek_char(subj) == '-') {
@@ -400,7 +404,7 @@ static cmark_node *handle_hyphen(subject *subj, bool smart) {
   int en_count = 0;
   int em_count = 0;
   int i;
-  cmark_strbuf buf = GH_BUF_INIT;
+  cmark_strbuf buf = CMARK_BUF_INIT(subj->mem);
 
   if (numhyphens % 3 == 0) { // if divisible by 3, use all em dashes
     em_count = numhyphens / 3;
@@ -422,7 +426,7 @@ static cmark_node *handle_hyphen(subject *subj, bool smart) {
     cmark_strbuf_puts(&buf, ENDASH);
   }
 
-  return make_str(cmark_chunk_buf_detach(&buf));
+  return make_str(subj->mem, cmark_chunk_buf_detach(&buf));
 }
 
 // Assumes we have a period at the current position.
@@ -432,12 +436,12 @@ static cmark_node *handle_period(subject *subj, bool smart) {
     advance(subj);
     if (peek_char(subj) == '.') {
       advance(subj);
-      return make_str(cmark_chunk_literal(ELLIPSES));
+      return make_str(subj->mem, cmark_chunk_literal(ELLIPSES));
     } else {
-      return make_str(cmark_chunk_literal(".."));
+      return make_str(subj->mem, cmark_chunk_literal(".."));
     }
   } else {
-    return make_str(cmark_chunk_literal("."));
+    return make_str(subj->mem, cmark_chunk_literal("."));
   }
 }
 
@@ -483,18 +487,18 @@ static void process_emphasis(subject *subj, delimiter *stack_bottom) {
           closer = closer->next;
         }
       } else if (closer->delim_char == '\'') {
-        cmark_chunk_free(&closer->inl_text->as.literal);
+        cmark_chunk_free(subj->mem, &closer->inl_text->as.literal);
         closer->inl_text->as.literal = cmark_chunk_literal(RIGHTSINGLEQUOTE);
         if (opener_found) {
-          cmark_chunk_free(&opener->inl_text->as.literal);
+          cmark_chunk_free(subj->mem, &opener->inl_text->as.literal);
           opener->inl_text->as.literal = cmark_chunk_literal(LEFTSINGLEQUOTE);
         }
         closer = closer->next;
       } else if (closer->delim_char == '"') {
-        cmark_chunk_free(&closer->inl_text->as.literal);
+        cmark_chunk_free(subj->mem, &closer->inl_text->as.literal);
         closer->inl_text->as.literal = cmark_chunk_literal(RIGHTDOUBLEQUOTE);
         if (opener_found) {
-          cmark_chunk_free(&opener->inl_text->as.literal);
+          cmark_chunk_free(subj->mem, &opener->inl_text->as.literal);
           opener->inl_text->as.literal = cmark_chunk_literal(LEFTDOUBLEQUOTE);
         }
         closer = closer->next;
@@ -553,7 +557,7 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
 
   // create new emph or strong, and splice it in to our inlines
   // between the opener and closer
-  emph = use_delims == 1 ? make_emph() : make_strong();
+  emph = use_delims == 1 ? make_emph(subj->mem) : make_strong(subj->mem);
 
   tmp = opener_inl->next;
   while (tmp && tmp != closer_inl) {
@@ -589,18 +593,18 @@ static cmark_node *handle_backslash(subject *subj) {
   if (cmark_ispunct(
           nextchar)) { // only ascii symbols and newline can be escaped
     advance(subj);
-    return make_str(cmark_chunk_dup(&subj->input, subj->pos - 1, 1));
+    return make_str(subj->mem, cmark_chunk_dup(&subj->input, subj->pos - 1, 1));
   } else if (!is_eof(subj) && skip_line_end(subj)) {
-    return make_linebreak();
+    return make_linebreak(subj->mem);
   } else {
-    return make_str(cmark_chunk_literal("\\"));
+    return make_str(subj->mem, cmark_chunk_literal("\\"));
   }
 }
 
 // Parse an entity or a regular "&" string.
 // Assumes the subject has an '&' character at the current position.
 static cmark_node *handle_entity(subject *subj) {
-  cmark_strbuf ent = GH_BUF_INIT;
+  cmark_strbuf ent = CMARK_BUF_INIT(subj->mem);
   bufsize_t len;
 
   advance(subj);
@@ -609,16 +613,16 @@ static cmark_node *handle_entity(subject *subj) {
                              subj->input.len - subj->pos);
 
   if (len == 0)
-    return make_str(cmark_chunk_literal("&"));
+    return make_str(subj->mem, cmark_chunk_literal("&"));
 
   subj->pos += len;
-  return make_str(cmark_chunk_buf_detach(&ent));
+  return make_str(subj->mem, cmark_chunk_buf_detach(&ent));
 }
 
 // Clean a URL: remove surrounding whitespace and surrounding <>,
 // and remove \ that escape punctuation.
-cmark_chunk cmark_clean_url(cmark_chunk *url) {
-  cmark_strbuf buf = GH_BUF_INIT;
+cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url) {
+  cmark_strbuf buf = CMARK_BUF_INIT(mem);
 
   cmark_chunk_trim(url);
 
@@ -637,8 +641,8 @@ cmark_chunk cmark_clean_url(cmark_chunk *url) {
   return cmark_chunk_buf_detach(&buf);
 }
 
-cmark_chunk cmark_clean_title(cmark_chunk *title) {
-  cmark_strbuf buf = GH_BUF_INIT;
+cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title) {
+  cmark_strbuf buf = CMARK_BUF_INIT(mem);
   unsigned char first, last;
 
   if (title->len == 0) {
@@ -675,7 +679,7 @@ static cmark_node *handle_pointy_brace(subject *subj) {
     contents = cmark_chunk_dup(&subj->input, subj->pos, matchlen - 1);
     subj->pos += matchlen;
 
-    return make_autolink(contents, 0);
+    return make_autolink(subj->mem, contents, 0);
   }
 
   // next try to match an email autolink
@@ -684,7 +688,7 @@ static cmark_node *handle_pointy_brace(subject *subj) {
     contents = cmark_chunk_dup(&subj->input, subj->pos, matchlen - 1);
     subj->pos += matchlen;
 
-    return make_autolink(contents, 1);
+    return make_autolink(subj->mem, contents, 1);
   }
 
   // finally, try to match an html tag
@@ -692,11 +696,11 @@ static cmark_node *handle_pointy_brace(subject *subj) {
   if (matchlen > 0) {
     contents = cmark_chunk_dup(&subj->input, subj->pos - 1, matchlen + 1);
     subj->pos += matchlen;
-    return make_raw_html(contents);
+    return make_raw_html(subj->mem, contents);
   }
 
   // if nothing matches, just return the opening <:
-  return make_str(cmark_chunk_literal("<"));
+  return make_str(subj->mem, cmark_chunk_literal("<"));
 }
 
 // Parse a link label.  Returns 1 if successful.
@@ -774,13 +778,13 @@ static cmark_node *handle_close_bracket(subject *subj) {
   }
 
   if (opener == NULL) {
-    return make_str(cmark_chunk_literal("]"));
+    return make_str(subj->mem, cmark_chunk_literal("]"));
   }
 
   if (!opener->active) {
     // take delimiter off stack
     remove_delimiter(subj, opener);
-    return make_str(cmark_chunk_literal("]"));
+    return make_str(subj->mem, cmark_chunk_literal("]"));
   }
 
   // If we got here, we matched a potential link/image text.
@@ -811,10 +815,10 @@ static cmark_node *handle_close_bracket(subject *subj) {
       url_chunk = cmark_chunk_dup(&subj->input, starturl, endurl - starturl);
       title_chunk =
           cmark_chunk_dup(&subj->input, starttitle, endtitle - starttitle);
-      url = cmark_clean_url(&url_chunk);
-      title = cmark_clean_title(&title_chunk);
-      cmark_chunk_free(&url_chunk);
-      cmark_chunk_free(&title_chunk);
+      url = cmark_clean_url(subj->mem, &url_chunk);
+      title = cmark_clean_title(subj->mem, &title_chunk);
+      cmark_chunk_free(subj->mem, &url_chunk);
+      cmark_chunk_free(subj->mem, &title_chunk);
       goto match;
 
     } else {
@@ -827,7 +831,7 @@ static cmark_node *handle_close_bracket(subject *subj) {
   raw_label = cmark_chunk_literal("");
   found_label = link_label(subj, &raw_label);
   if (!found_label || raw_label.len == 0) {
-    cmark_chunk_free(&raw_label);
+    cmark_chunk_free(subj->mem, &raw_label);
     raw_label = cmark_chunk_dup(&subj->input, opener->position,
                                 initial_pos - opener->position - 1);
   }
@@ -839,11 +843,11 @@ static cmark_node *handle_close_bracket(subject *subj) {
   }
 
   ref = cmark_reference_lookup(subj->refmap, &raw_label);
-  cmark_chunk_free(&raw_label);
+  cmark_chunk_free(subj->mem, &raw_label);
 
   if (ref != NULL) { // found
-    url = chunk_clone(&ref->url);
-    title = chunk_clone(&ref->title);
+    url = chunk_clone(subj->mem, &ref->url);
+    title = chunk_clone(subj->mem, &ref->title);
     goto match;
   } else {
     goto noMatch;
@@ -853,10 +857,10 @@ noMatch:
   // If we fall through to here, it means we didn't match a link:
   remove_delimiter(subj, opener); // remove this opener from delimiter list
   subj->pos = initial_pos;
-  return make_str(cmark_chunk_literal("]"));
+  return make_str(subj->mem, cmark_chunk_literal("]"));
 
 match:
-  inl = make_simple(is_image ? CMARK_NODE_IMAGE : CMARK_NODE_LINK);
+  inl = make_simple(subj->mem, is_image ? CMARK_NODE_IMAGE : CMARK_NODE_LINK);
   inl->as.link.url = url;
   inl->as.link.title = title;
   cmark_node_insert_before(opener->inl_text, inl);
@@ -909,9 +913,9 @@ static cmark_node *handle_newline(subject *subj) {
   skip_spaces(subj);
   if (nlpos > 1 && peek_at(subj, nlpos - 1) == ' ' &&
       peek_at(subj, nlpos - 2) == ' ') {
-    return make_linebreak();
+    return make_linebreak(subj->mem);
   } else {
-    return make_softbreak();
+    return make_softbreak(subj->mem);
   }
 }
 
@@ -1000,7 +1004,7 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
     break;
   case '[':
     advance(subj);
-    new_inl = make_str(cmark_chunk_literal("["));
+    new_inl = make_str(subj->mem, cmark_chunk_literal("["));
     push_delimiter(subj, '[', true, false, new_inl);
     break;
   case ']':
@@ -1010,10 +1014,10 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
     advance(subj);
     if (peek_char(subj) == '[') {
       advance(subj);
-      new_inl = make_str(cmark_chunk_literal("!["));
+      new_inl = make_str(subj->mem, cmark_chunk_literal("!["));
       push_delimiter(subj, '!', false, true, new_inl);
     } else {
-      new_inl = make_str(cmark_chunk_literal("!"));
+      new_inl = make_str(subj->mem, cmark_chunk_literal("!"));
     }
     break;
   default:
@@ -1026,7 +1030,7 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
       cmark_chunk_rtrim(&contents);
     }
 
-    new_inl = make_str(contents);
+    new_inl = make_str(subj->mem, contents);
   }
   if (new_inl != NULL) {
     cmark_node_append_child(parent, new_inl);
@@ -1036,10 +1040,10 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
 }
 
 // Parse inlines from parent's string_content, adding as children of parent.
-extern void cmark_parse_inlines(cmark_node *parent, cmark_reference_map *refmap,
+extern void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent, cmark_reference_map *refmap,
                                 int options) {
   subject subj;
-  subject_from_buf(&subj, &parent->string_content, refmap);
+  subject_from_buf(mem, &subj, &parent->content, refmap);
   cmark_chunk_rtrim(&subj.input);
 
   while (!is_eof(&subj) && parse_inline(&subj, parent, options))
@@ -1060,7 +1064,7 @@ static void spnl(subject *subj) {
 // Modify refmap if a reference is encountered.
 // Return 0 if no reference found, otherwise position of subject
 // after reference is parsed.
-bufsize_t cmark_parse_reference_inline(cmark_strbuf *input,
+bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
                                        cmark_reference_map *refmap) {
   subject subj;
 
@@ -1071,7 +1075,7 @@ bufsize_t cmark_parse_reference_inline(cmark_strbuf *input,
   bufsize_t matchlen = 0;
   bufsize_t beforetitle;
 
-  subject_from_buf(&subj, input, NULL);
+  subject_from_buf(mem, &subj, input, NULL);
 
   // parse label:
   if (!link_label(&subj, &lab) || lab.len == 0)
diff --git a/src/inlines.h b/src/inlines.h
@@ -5,13 +5,13 @@
 extern "C" {
 #endif
 
-cmark_chunk cmark_clean_url(cmark_chunk *url);
-cmark_chunk cmark_clean_title(cmark_chunk *title);
+cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url);
+cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title);
 
-void cmark_parse_inlines(cmark_node *parent, cmark_reference_map *refmap,
+void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent, cmark_reference_map *refmap,
                          int options);
 
-bufsize_t cmark_parse_reference_inline(cmark_strbuf *input,
+bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
                                        cmark_reference_map *refmap);
 
 #ifdef __cplusplus
diff --git a/src/iterator.c b/src/iterator.c
@@ -16,7 +16,9 @@ cmark_iter *cmark_iter_new(cmark_node *root) {
   if (root == NULL) {
     return NULL;
   }
-  cmark_iter *iter = (cmark_iter *)cmark_calloc(1, sizeof(cmark_iter));
+  cmark_mem *mem = root->content.mem;
+  cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter));
+  iter->mem = mem;
   iter->root = root;
   iter->cur.ev_type = CMARK_EVENT_NONE;
   iter->cur.node = NULL;
@@ -25,7 +27,7 @@ cmark_iter *cmark_iter_new(cmark_node *root) {
   return iter;
 }
 
-void cmark_iter_free(cmark_iter *iter) { free(iter); }
+void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); }
 
 static bool S_is_leaf(cmark_node *node) {
   return ((1 << node->type) & S_leaf_mask) != 0;
@@ -90,7 +92,7 @@ void cmark_consolidate_text_nodes(cmark_node *root) {
     return;
   }
   cmark_iter *iter = cmark_iter_new(root);
-  cmark_strbuf buf = GH_BUF_INIT;
+  cmark_strbuf buf = CMARK_BUF_INIT(iter->mem);
   cmark_event_type ev_type;
   cmark_node *cur, *tmp, *next;
 
@@ -108,7 +110,7 @@ void cmark_consolidate_text_nodes(cmark_node *root) {
         cmark_node_free(tmp);
         tmp = next;
       }
-      cmark_chunk_free(&cur->as.literal);
+      cmark_chunk_free(iter->mem, &cur->as.literal);
       cur->as.literal = cmark_chunk_buf_detach(&buf);
     }
   }
diff --git a/src/iterator.h b/src/iterator.h
@@ -6,6 +6,7 @@ extern "C" {
 #endif
 
 #include "cmark.h"
+#include "memory.h"
 
 typedef struct {
   cmark_event_type ev_type;
@@ -13,6 +14,7 @@ typedef struct {
 } cmark_iter_state;
 
 struct cmark_iter {
+  cmark_mem *mem;
   cmark_node *root;
   cmark_iter_state cur;
   cmark_iter_state next;
diff --git a/src/main.c b/src/main.c
@@ -81,7 +81,7 @@ int main(int argc, char *argv[]) {
   _setmode(_fileno(stdout), _O_BINARY);
 #endif
 
-  files = (int *)cmark_calloc(argc, sizeof(*files));
+  files = (int *)calloc(argc, sizeof(*files));
 
   for (i = 1; i < argc; i++) {
     if (strcmp(argv[i], "--version") == 0) {
diff --git a/src/memory.h b/src/memory.h
@@ -1,12 +0,0 @@
-#ifndef CMARK_MEM_H
-#define CMARK_MEM_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void *cmark_calloc(size_t nmem, size_t size);
-void *cmark_realloc(void *ptr, size_t size);
-void cmark_trigger_oom(void);
-
-#endif
diff --git a/src/node.c b/src/node.c
@@ -6,6 +6,8 @@
 
 static void S_node_unlink(cmark_node *node);
 
+#define NODE_MEM(node) cmark_node_mem(node)
+
 static CMARK_INLINE bool S_is_block(cmark_node *node) {
   if (node == NULL) {
     return false;
@@ -28,6 +30,9 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) {
   if (node == NULL || child == NULL) {
     return false;
   }
+  if (NODE_MEM(node) != NODE_MEM(child)) {
+    return 0;
+  }
 
   // Verify that child is not an ancestor of node or equal to node.
   cur = node;
@@ -70,8 +75,9 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) {
   return false;
 }
 
-cmark_node *cmark_node_new(cmark_node_type type) {
-  cmark_node *node = (cmark_node *)cmark_calloc(1, sizeof(*node));
+cmark_node *cmark_node_new2(cmark_node_type type, cmark_mem *mem) {
+  cmark_node *node = (cmark_node *)mem->calloc(1, sizeof(*node));
+  cmark_strbuf_init(mem, &node->content, 0);
   node->type = type;
 
   switch (node->type) {
@@ -94,33 +100,36 @@ cmark_node *cmark_node_new(cmark_node_type type) {
   return node;
 }
 
+cmark_node *cmark_node_new(cmark_node_type type) {
+  extern cmark_mem DEFAULT_MEM_ALLOCATOR;
+  return cmark_node_new2(type, &DEFAULT_MEM_ALLOCATOR);
+}
+
 // Free a cmark_node list and any children.
 static void S_free_nodes(cmark_node *e) {
   cmark_node *next;
   while (e != NULL) {
-    if (S_is_block(e)) {
-      cmark_strbuf_free(&e->string_content);
-    }
+    cmark_strbuf_free(&e->content);
     switch (e->type) {
     case CMARK_NODE_CODE_BLOCK:
-      cmark_chunk_free(&e->as.code.info);
-      cmark_chunk_free(&e->as.code.literal);
+      cmark_chunk_free(NODE_MEM(e), &e->as.code.info);
+      cmark_chunk_free(NODE_MEM(e), &e->as.code.literal);
       break;
     case CMARK_NODE_TEXT:
     case CMARK_NODE_HTML_INLINE:
     case CMARK_NODE_CODE:
     case CMARK_NODE_HTML_BLOCK:
-      cmark_chunk_free(&e->as.literal);
+      cmark_chunk_free(NODE_MEM(e), &e->as.literal);
       break;
     case CMARK_NODE_LINK:
     case CMARK_NODE_IMAGE:
-      cmark_chunk_free(&e->as.link.url);
-      cmark_chunk_free(&e->as.link.title);
+      cmark_chunk_free(NODE_MEM(e), &e->as.link.url);
+      cmark_chunk_free(NODE_MEM(e), &e->as.link.title);
       break;
     case CMARK_NODE_CUSTOM_BLOCK:
     case CMARK_NODE_CUSTOM_INLINE:
-      cmark_chunk_free(&e->as.custom.on_enter);
-      cmark_chunk_free(&e->as.custom.on_exit);
+      cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_enter);
+      cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_exit);
       break;
     default:
       break;
@@ -131,7 +140,7 @@ static void S_free_nodes(cmark_node *e) {
       e->next = e->first_child;
     }
     next = e->next;
-    free(e);
+    NODE_MEM(e)->free(e);
     e = next;
   }
 }
@@ -269,10 +278,10 @@ const char *cmark_node_get_literal(cmark_node *node) {
   case CMARK_NODE_TEXT:
   case CMARK_NODE_HTML_INLINE:
   case CMARK_NODE_CODE:
-    return cmark_chunk_to_cstr(&node->as.literal);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);
 
   case CMARK_NODE_CODE_BLOCK:
-    return cmark_chunk_to_cstr(&node->as.code.literal);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.literal);
 
   default:
     break;
@@ -291,11 +300,11 @@ int cmark_node_set_literal(cmark_node *node, const char *content) {
   case CMARK_NODE_TEXT:
   case CMARK_NODE_HTML_INLINE:
   case CMARK_NODE_CODE:
-    cmark_chunk_set_cstr(&node->as.literal, content);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.literal, content);
     return 1;
 
   case CMARK_NODE_CODE_BLOCK:
-    cmark_chunk_set_cstr(&node->as.code.literal, content);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
     return 1;
 
   default:
@@ -452,7 +461,7 @@ const char *cmark_node_get_fence_info(cmark_node *node) {
   }
 
   if (node->type == CMARK_NODE_CODE_BLOCK) {
-    return cmark_chunk_to_cstr(&node->as.code.info);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.info);
   } else {
     return NULL;
   }
@@ -464,7 +473,7 @@ int cmark_node_set_fence_info(cmark_node *node, const char *info) {
   }
 
   if (node->type == CMARK_NODE_CODE_BLOCK) {
-    cmark_chunk_set_cstr(&node->as.code.info, info);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
     return 1;
   } else {
     return 0;
@@ -479,7 +488,7 @@ const char *cmark_node_get_url(cmark_node *node) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    return cmark_chunk_to_cstr(&node->as.link.url);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.url);
   default:
     break;
   }
@@ -495,7 +504,7 @@ int cmark_node_set_url(cmark_node *node, const char *url) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    cmark_chunk_set_cstr(&node->as.link.url, url);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.url, url);
     return 1;
   default:
     break;
@@ -512,7 +521,7 @@ const char *cmark_node_get_title(cmark_node *node) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    return cmark_chunk_to_cstr(&node->as.link.title);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.title);
   default:
     break;
   }
@@ -528,7 +537,7 @@ int cmark_node_set_title(cmark_node *node, const char *title) {
   switch (node->type) {
   case CMARK_NODE_LINK:
   case CMARK_NODE_IMAGE:
-    cmark_chunk_set_cstr(&node->as.link.title, title);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.title, title);
     return 1;
   default:
     break;
@@ -545,7 +554,7 @@ const char *cmark_node_get_on_enter(cmark_node *node) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    return cmark_chunk_to_cstr(&node->as.custom.on_enter);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_enter);
   default:
     break;
   }
@@ -561,7 +570,7 @@ int cmark_node_set_on_enter(cmark_node *node, const char *on_enter) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    cmark_chunk_set_cstr(&node->as.custom.on_enter, on_enter);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_enter, on_enter);
     return 1;
   default:
     break;
@@ -578,7 +587,7 @@ const char *cmark_node_get_on_exit(cmark_node *node) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    return cmark_chunk_to_cstr(&node->as.custom.on_exit);
+    return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_exit);
   default:
     break;
   }
@@ -594,7 +603,7 @@ int cmark_node_set_on_exit(cmark_node *node, const char *on_exit) {
   switch (node->type) {
   case CMARK_NODE_CUSTOM_INLINE:
   case CMARK_NODE_CUSTOM_BLOCK:
-    cmark_chunk_set_cstr(&node->as.custom.on_exit, on_exit);
+    cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_exit, on_exit);
     return 1;
   default:
     break;
diff --git a/src/node.h b/src/node.h
@@ -48,6 +48,8 @@ typedef struct {
 } cmark_custom;
 
 struct cmark_node {
+  cmark_strbuf content;
+
   struct cmark_node *next;
   struct cmark_node *prev;
   struct cmark_node *parent;
@@ -62,11 +64,8 @@ struct cmark_node {
   int end_column;
 
   cmark_node_type type;
-
-  bool open;
   bool last_line_blank;
-
-  cmark_strbuf string_content;
+  bool open;
 
   union {
     cmark_chunk literal;
@@ -79,6 +78,9 @@ struct cmark_node {
   } as;
 };
 
+static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) {
+  return node->content.mem;
+}
 CMARK_EXPORT int cmark_node_check(cmark_node *node, FILE *out);
 
 #ifdef __cplusplus
diff --git a/src/parser.h b/src/parser.h
@@ -4,6 +4,7 @@
 #include <stdio.h>
 #include "node.h"
 #include "buffer.h"
+#include "memory.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -12,6 +13,7 @@ extern "C" {
 #define MAX_LINK_LABEL_LENGTH 1000
 
 struct cmark_parser {
+	struct cmark_mem *mem;
   struct cmark_reference_map *refmap;
   struct cmark_node *root;
   struct cmark_node *current;
diff --git a/src/references.c b/src/references.c
@@ -14,12 +14,13 @@ static unsigned int refhash(const unsigned char *link_ref) {
   return hash;
 }
 
-static void reference_free(cmark_reference *ref) {
+static void reference_free(cmark_reference_map *map, cmark_reference *ref) {
+  cmark_mem *mem = map->mem;
   if (ref != NULL) {
-    free(ref->label);
-    cmark_chunk_free(&ref->url);
-    cmark_chunk_free(&ref->title);
-    free(ref);
+    mem->free(ref->label);
+    cmark_chunk_free(mem, &ref->url);
+    cmark_chunk_free(mem, &ref->title);
+    mem->free(ref);
   }
 }
 
@@ -27,8 +28,8 @@ static void reference_free(cmark_reference *ref) {
 // remove leading/trailing whitespace, case fold
 // Return NULL if the reference name is actually empty (i.e. composed
 // solely from whitespace)
-static unsigned char *normalize_reference(cmark_chunk *ref) {
-  cmark_strbuf normalized = GH_BUF_INIT;
+static unsigned char *normalize_reference(cmark_mem *mem, cmark_chunk *ref) {
+  cmark_strbuf normalized = CMARK_BUF_INIT(mem);
   unsigned char *result;
 
   if (ref == NULL)
@@ -57,7 +58,7 @@ static void add_reference(cmark_reference_map *map, cmark_reference *ref) {
 
   while (t) {
     if (t->hash == ref->hash && !strcmp((char *)t->label, (char *)ref->label)) {
-      reference_free(ref);
+      reference_free(map, ref);
       return;
     }
 
@@ -70,17 +71,17 @@ static void add_reference(cmark_reference_map *map, cmark_reference *ref) {
 void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label,
                             cmark_chunk *url, cmark_chunk *title) {
   cmark_reference *ref;
-  unsigned char *reflabel = normalize_reference(label);
+  unsigned char *reflabel = normalize_reference(map->mem, label);
 
   /* empty reference name, or composed from only whitespace */
   if (reflabel == NULL)
     return;
 
-  ref = (cmark_reference *)cmark_calloc(1, sizeof(*ref));
+  ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref));
   ref->label = reflabel;
   ref->hash = refhash(ref->label);
-  ref->url = cmark_clean_url(url);
-  ref->title = cmark_clean_title(title);
+  ref->url = cmark_clean_url(map->mem, url);
+  ref->title = cmark_clean_title(map->mem, title);
   ref->next = NULL;
 
   add_reference(map, ref);
@@ -100,7 +101,7 @@ cmark_reference *cmark_reference_lookup(cmark_reference_map *map,
   if (map == NULL)
     return NULL;
 
-  norm = normalize_reference(label);
+  norm = normalize_reference(map->mem, label);
   if (norm == NULL)
     return NULL;
 
@@ -129,7 +130,7 @@ void cmark_reference_map_free(cmark_reference_map *map) {
 
     while (ref) {
       next = ref->next;
-      reference_free(ref);
+      reference_free(map, ref);
       ref = next;
     }
   }
@@ -137,6 +138,8 @@ void cmark_reference_map_free(cmark_reference_map *map) {
   free(map);
 }
 
-cmark_reference_map *cmark_reference_map_new(void) {
-  return (cmark_reference_map *)cmark_calloc(1, sizeof(cmark_reference_map));
+cmark_reference_map *cmark_reference_map_new(cmark_mem *mem) {
+  cmark_reference_map *map = mem->calloc(1, sizeof(cmark_reference_map));
+  map->mem = mem;
+  return map;
 }
diff --git a/src/references.h b/src/references.h
@@ -1,6 +1,7 @@
 #ifndef CMARK_REFERENCES_H
 #define CMARK_REFERENCES_H
 
+#include "memory.h"
 #include "chunk.h"
 
 #ifdef __cplusplus
@@ -20,12 +21,13 @@ struct cmark_reference {
 typedef struct cmark_reference cmark_reference;
 
 struct cmark_reference_map {
+  cmark_mem *mem;
   cmark_reference *table[REFMAP_SIZE];
 };
 
 typedef struct cmark_reference_map cmark_reference_map;
 
-cmark_reference_map *cmark_reference_map_new(void);
+cmark_reference_map *cmark_reference_map_new(cmark_mem *mem);
 void cmark_reference_map_free(cmark_reference_map *map);
 cmark_reference *cmark_reference_lookup(cmark_reference_map *map,
                                         cmark_chunk *label);
diff --git a/src/render.c b/src/render.c
@@ -4,6 +4,7 @@
 #include "cmark.h"
 #include "utf8.h"
 #include "render.h"
+#include "node.h"
 
 static CMARK_INLINE void S_cr(cmark_renderer *renderer) {
   if (renderer->need_cr < 1) {
@@ -108,7 +109,7 @@ static void S_out(cmark_renderer *renderer, const char *source, bool wrap,
         !renderer->begin_line && renderer->last_breakable > 0) {
 
       // copy from last_breakable to remainder
-      cmark_chunk_set_cstr(&remainder, (char *)renderer->buffer->ptr +
+      cmark_chunk_set_cstr(renderer->mem, &remainder, (char *)renderer->buffer->ptr +
                                            renderer->last_breakable + 1);
       // truncate at last_breakable
       cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable);
@@ -118,7 +119,7 @@ static void S_out(cmark_renderer *renderer, const char *source, bool wrap,
                        renderer->prefix->size);
       cmark_strbuf_put(renderer->buffer, remainder.data, remainder.len);
       renderer->column = renderer->prefix->size + remainder.len;
-      cmark_chunk_free(&remainder);
+      cmark_chunk_free(renderer->mem, &remainder);
       renderer->last_breakable = 0;
       renderer->begin_line = false;
       renderer->begin_content = false;
@@ -146,14 +147,15 @@ char *cmark_render(cmark_node *root, int options, int width,
                    int (*render_node)(cmark_renderer *renderer,
                                       cmark_node *node,
                                       cmark_event_type ev_type, int options)) {
-  cmark_strbuf pref = GH_BUF_INIT;
-  cmark_strbuf buf = GH_BUF_INIT;
+  cmark_mem *mem = cmark_node_mem(root);
+  cmark_strbuf pref = CMARK_BUF_INIT(mem);
+  cmark_strbuf buf = CMARK_BUF_INIT(mem);
   cmark_node *cur;
   cmark_event_type ev_type;
   char *result;
   cmark_iter *iter = cmark_iter_new(root);
 
-  cmark_renderer renderer = {&buf, &pref, 0,           width, 0,
+  cmark_renderer renderer = {mem, &buf, &pref, 0, width, 0,
                              0,    true,  true,        false, false,
                              outc, S_cr,  S_blankline, S_out};
 
diff --git a/src/render.h b/src/render.h
@@ -8,10 +8,12 @@ extern "C" {
 #include <stdlib.h>
 #include "buffer.h"
 #include "chunk.h"
+#include "memory.h"
 
 typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping;
 
 struct cmark_renderer {
+  cmark_mem *mem;
   cmark_strbuf *buffer;
   cmark_strbuf *prefix;
   int column;
diff --git a/src/xml.c b/src/xml.c
@@ -149,7 +149,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 
 char *cmark_render_xml(cmark_node *root, int options) {
   char *result;
-  cmark_strbuf xml = GH_BUF_INIT;
+  cmark_strbuf xml = CMARK_BUF_INIT(cmark_node_mem(root));
   cmark_event_type ev_type;
   cmark_node *cur;
   struct render_state state = {&xml, 0};