From 38fbd393e8bb4943f398eaafa0173a0e2a9329f7 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 9 Apr 2026 11:14:45 +0200 Subject: [PATCH 1/2] Use enif_alloc/enif_free for std::vector allocations std::vector's default std::allocator uses malloc/free, which bypasses BEAM memory bookkeeping. Introduce a custom enif_allocator that delegates to enif_alloc/enif_free so all NIF heap usage is visible to erlang:memory() and crash dumps. --- c_src/exml.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/c_src/exml.cpp b/c_src/exml.cpp index 71eda1c..6b77bdf 100644 --- a/c_src/exml.cpp +++ b/c_src/exml.cpp @@ -17,7 +17,41 @@ #include #include -using ustring = std::vector; +template +struct enif_allocator { + using value_type = T; + + enif_allocator() noexcept = default; + + template + enif_allocator(const enif_allocator &) noexcept {} + + T *allocate(std::size_t n) { + if (n > std::size_t(-1) / sizeof(T)) + throw std::bad_alloc(); + void *p = enif_alloc(n * sizeof(T)); + if (!p) + throw std::bad_alloc(); + return static_cast(p); + } + + void deallocate(T *p, std::size_t) noexcept { enif_free(p); } +}; + +template +constexpr bool operator==(const enif_allocator &, const enif_allocator &) noexcept { + return true; +} + +template +constexpr bool operator!=(const enif_allocator &, const enif_allocator &) noexcept { + return false; +} + +template +using nif_vector = std::vector>; + +using ustring = nif_vector; class xml_document { public: @@ -86,8 +120,8 @@ struct Parser { std::uint64_t max_element_size = 0; bool infinite_stream = false; - static thread_local std::vector buffer; - static thread_local std::vector term_buffer; + static thread_local nif_vector buffer; + static thread_local nif_vector term_buffer; bool copy_buffer(ErlNifEnv *env, ERL_NIF_TERM buf) { buffer.clear(); @@ -116,8 +150,8 @@ struct Parser { } }; -thread_local std::vector Parser::buffer; -thread_local std::vector Parser::term_buffer; +thread_local nif_vector Parser::buffer; +thread_local nif_vector Parser::term_buffer; struct ParseCtx { ErlNifEnv *env; @@ -170,7 +204,7 @@ ERL_NIF_TERM merge_data_nodes(ParseCtx &ctx, } void append_pending_data_nodes(ParseCtx &ctx, - std::vector &children, + nif_vector &children, rapidxml::xml_node *node, const std::size_t pending) { if (pending == 0) @@ -186,7 +220,7 @@ ERL_NIF_TERM make_xmlel(ParseCtx &ctx, rapidxml::xml_node *node); ERL_NIF_TERM get_children_tuple(ParseCtx &ctx, rapidxml::xml_node *node) { - std::vector &children = Parser::term_buffer; + nif_vector &children = Parser::term_buffer; std::size_t begin = children.size(); rapidxml::xml_node *first_data_node = nullptr; @@ -395,7 +429,7 @@ bool build_children(ErlNifEnv *env, xml_document &doc, ERL_NIF_TERM children, ERL_NIF_TERM node_to_binary(ErlNifEnv *env, rapidxml::xml_node &node, int flags) { - static thread_local std::vector print_buffer; + static thread_local nif_vector print_buffer; print_buffer.clear(); rapidxml::print(std::back_inserter(print_buffer), node, flags); From 508489724e0fa67fd774b1701ad90dccdbb1a11e Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 13 May 2026 12:17:05 +0200 Subject: [PATCH 2/2] Apply suggestion from @kamilwaz: use std::numeric_limits<> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kamil Wąż --- c_src/exml.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/c_src/exml.cpp b/c_src/exml.cpp index 6b77bdf..e9f4dfe 100644 --- a/c_src/exml.cpp +++ b/c_src/exml.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +28,7 @@ struct enif_allocator { enif_allocator(const enif_allocator &) noexcept {} T *allocate(std::size_t n) { - if (n > std::size_t(-1) / sizeof(T)) + if (n > std::numeric_limits::max() / sizeof(T)) throw std::bad_alloc(); void *p = enif_alloc(n * sizeof(T)); if (!p)