Implement <xml_memory_pool> concept similar to C++17's std::memory_resource. Allows construction of pugi::xml_document objects using preallocated memory buffers. By default serves memory requests via standard memory (de)alocation functions.

This commit is contained in:
Ambal 2019-12-23 19:42:03 +02:00
parent 41b6ff21c4
commit 694659e286
3 changed files with 493 additions and 96 deletions

View File

@ -191,6 +191,8 @@ PUGI__NS_BEGIN
free(ptr); free(ptr);
} }
void* aligned_allocate(xml_memory_pool* pool, size_t size);
template <typename T> template <typename T>
struct xml_memory_management_function_storage struct xml_memory_management_function_storage
{ {
@ -282,14 +284,56 @@ PUGI__NS_BEGIN
return result; return result;
} }
}; };
template<> struct auto_deleter<void>
{
xml_memory_pool* pool;
void* data;
auto_deleter(xml_memory_pool* pool_, size_t size) : pool(pool_), data(impl::aligned_allocate(pool_, size))
{
}
~auto_deleter()
{
if (data) pool->deallocate(data);
}
void* release()
{
void* result = data;
data = 0;
return result;
}
};
PUGI__NS_END PUGI__NS_END
// standard memory pool using global allocate/deallocate functions
PUGI__NS_BEGIN
class xml_global_memory_pool : public xml_memory_pool
{
public:
virtual void* allocate(size_t size) PUGIXML_OVERRIDE
{
return xml_memory::allocate(size);
}
virtual void deallocate(void* ptr) PUGIXML_OVERRIDE
{
return xml_memory::deallocate(ptr);
}
};
PUGI__NS_END
#ifdef PUGIXML_COMPACT #ifdef PUGIXML_COMPACT
PUGI__NS_BEGIN PUGI__NS_BEGIN
class compact_hash_table class compact_hash_table
{ {
public: public:
compact_hash_table(): _items(0), _capacity(0), _count(0) explicit compact_hash_table(xml_memory_pool* pool): _pool(pool), _items(0), _capacity(0), _count(0)
{ {
} }
@ -297,7 +341,7 @@ PUGI__NS_BEGIN
{ {
if (_items) if (_items)
{ {
xml_memory::deallocate(_items); _pool->deallocate(_items);
_items = 0; _items = 0;
_capacity = 0; _capacity = 0;
_count = 0; _count = 0;
@ -346,6 +390,7 @@ PUGI__NS_BEGIN
void* value; void* value;
}; };
xml_memory_pool* _pool;
item_t* _items; item_t* _items;
size_t _capacity; size_t _capacity;
@ -397,9 +442,9 @@ PUGI__NS_BEGIN
while (count >= capacity - capacity / 4) while (count >= capacity - capacity / 4)
capacity *= 2; capacity *= 2;
compact_hash_table rt; compact_hash_table rt(_pool);
rt._capacity = capacity; rt._capacity = capacity;
rt._items = static_cast<item_t*>(xml_memory::allocate(sizeof(item_t) * capacity)); rt._items = static_cast<item_t*>(impl::aligned_allocate(_pool, sizeof(item_t) * capacity));
if (!rt._items) if (!rt._items)
return false; return false;
@ -411,7 +456,7 @@ PUGI__NS_BEGIN
rt.insert(_items[i].key, _items[i].value); rt.insert(_items[i].key, _items[i].value);
if (_items) if (_items)
xml_memory::deallocate(_items); _pool->deallocate(_items);
_capacity = capacity; _capacity = capacity;
_items = rt._items; _items = rt._items;
@ -453,6 +498,24 @@ PUGI__NS_BEGIN
#define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header) #define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header)
#define PUGI__NODETYPE(n) static_cast<xml_node_type>((n)->header & impl::xml_memory_page_type_mask) #define PUGI__NODETYPE(n) static_cast<xml_node_type>((n)->header & impl::xml_memory_page_type_mask)
static xml_global_memory_pool xml_default_memory_pool;
PUGI__FN xml_memory_pool* get_default_memory_pool() PUGIXML_NOEXCEPT
{
return &xml_default_memory_pool;
}
PUGI__FN size_t xml_calculate_aligned_size(size_t size) PUGIXML_NOEXCEPT
{
// round size up to block alignment boundary
return (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1);
}
PUGI__FN void* aligned_allocate(xml_memory_pool* pool, size_t size)
{
return pool->allocate(xml_calculate_aligned_size(size));
}
struct xml_allocator; struct xml_allocator;
struct xml_memory_page struct xml_memory_page
@ -507,7 +570,7 @@ PUGI__NS_BEGIN
struct xml_allocator struct xml_allocator
{ {
xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) xml_allocator(xml_memory_pool* pool, xml_memory_page* root): _pool(pool), _root(root), _busy_size(root->busy_size)
{ {
#ifdef PUGIXML_COMPACT #ifdef PUGIXML_COMPACT
_hash = 0; _hash = 0;
@ -519,7 +582,7 @@ PUGI__NS_BEGIN
size_t size = sizeof(xml_memory_page) + data_size; size_t size = sizeof(xml_memory_page) + data_size;
// allocate block with some alignment, leaving memory for worst-case padding // allocate block with some alignment, leaving memory for worst-case padding
void* memory = xml_memory::allocate(size); void* memory = _pool->allocate(size);
if (!memory) return 0; if (!memory) return 0;
// prepare page structure // prepare page structure
@ -531,9 +594,9 @@ PUGI__NS_BEGIN
return page; return page;
} }
static void deallocate_page(xml_memory_page* page) void deallocate_page(xml_memory_page* page)
{ {
xml_memory::deallocate(page); _pool->deallocate(page);
} }
void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); void* allocate_memory_oob(size_t size, xml_memory_page*& out_page);
@ -644,7 +707,7 @@ PUGI__NS_BEGIN
size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t);
// round size up to block alignment boundary // round size up to block alignment boundary
size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1); size_t full_size = xml_calculate_aligned_size(size);
xml_memory_page* page; xml_memory_page* page;
xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page)); xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page));
@ -696,6 +759,7 @@ PUGI__NS_BEGIN
#endif #endif
} }
xml_memory_pool* _pool;
xml_memory_page* _root; xml_memory_page* _root;
size_t _busy_size; size_t _busy_size;
@ -1140,10 +1204,22 @@ PUGI__NS_BEGIN
struct xml_document_struct: public xml_node_struct, public xml_allocator struct xml_document_struct: public xml_node_struct, public xml_allocator
{ {
xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) xml_document_struct(xml_memory_pool* pool, xml_memory_page* page)
: xml_node_struct(page, node_document)
, xml_allocator(pool, page)
, buffer(0)
, extra_buffers(0)
#ifdef PUGIXML_COMPACT
, hash(pool)
#endif
{ {
} }
xml_memory_pool* get_memory_pool() PUGIXML_NOEXCEPT
{
return this->xml_allocator::_pool;
}
const char_t* buffer; const char_t* buffer;
xml_extra_buffer* extra_buffers; xml_extra_buffer* extra_buffers;
@ -2037,7 +2113,7 @@ PUGI__NS_BEGIN
return guess_buffer_encoding(data, size); return guess_buffer_encoding(data, size);
} }
PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{ {
size_t length = size / sizeof(char_t); size_t length = size / sizeof(char_t);
@ -2048,7 +2124,7 @@ PUGI__NS_BEGIN
} }
else else
{ {
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false; if (!buffer) return false;
if (contents) if (contents)
@ -2072,7 +2148,7 @@ PUGI__NS_BEGIN
(le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be);
} }
PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{ {
const char_t* data = static_cast<const char_t*>(contents); const char_t* data = static_cast<const char_t*>(contents);
size_t length = size / sizeof(char_t); size_t length = size / sizeof(char_t);
@ -2088,7 +2164,7 @@ PUGI__NS_BEGIN
} }
else else
{ {
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false; if (!buffer) return false;
convert_wchar_endian_swap(buffer, data, length); convert_wchar_endian_swap(buffer, data, length);
@ -2101,7 +2177,7 @@ PUGI__NS_BEGIN
return true; return true;
} }
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, xml_memory_pool* pool, D)
{ {
const typename D::type* data = static_cast<const typename D::type*>(contents); const typename D::type* data = static_cast<const typename D::type*>(contents);
size_t data_length = size / sizeof(typename D::type); size_t data_length = size / sizeof(typename D::type);
@ -2110,7 +2186,7 @@ PUGI__NS_BEGIN
size_t length = D::process(data, data_length, 0, wchar_counter()); size_t length = D::process(data, data_length, 0, wchar_counter());
// allocate buffer of suitable length // allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false; if (!buffer) return false;
// second pass: convert utf16 input to wchar_t // second pass: convert utf16 input to wchar_t
@ -2126,22 +2202,22 @@ PUGI__NS_BEGIN
return true; return true;
} }
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{ {
// get native encoding // get native encoding
xml_encoding wchar_encoding = get_wchar_encoding(); xml_encoding wchar_encoding = get_wchar_encoding();
// fast path: no conversion required // fast path: no conversion required
if (encoding == wchar_encoding) if (encoding == wchar_encoding)
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// only endian-swapping is required // only endian-swapping is required
if (need_endian_swap_utf(encoding, wchar_encoding)) if (need_endian_swap_utf(encoding, wchar_encoding))
return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable, pool);
// source encoding is utf8 // source encoding is utf8
if (encoding == encoding_utf8) if (encoding == encoding_utf8)
return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder()); return convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf8_decoder());
// source encoding is utf16 // source encoding is utf16
if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
@ -2149,8 +2225,8 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
return (native_encoding == encoding) ? return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) : convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>()); convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_true>());
} }
// source encoding is utf32 // source encoding is utf32
@ -2159,19 +2235,19 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
return (native_encoding == encoding) ? return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) : convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>()); convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_true>());
} }
// source encoding is latin1 // source encoding is latin1
if (encoding == encoding_latin1) if (encoding == encoding_latin1)
return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder()); return convert_buffer_generic(out_buffer, out_length, contents, size, pool, latin1_decoder());
assert(false && "Invalid encoding"); // unreachable assert(false && "Invalid encoding"); // unreachable
return false; return false;
} }
#else #else
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, xml_memory_pool* pool, D)
{ {
const typename D::type* data = static_cast<const typename D::type*>(contents); const typename D::type* data = static_cast<const typename D::type*>(contents);
size_t data_length = size / sizeof(typename D::type); size_t data_length = size / sizeof(typename D::type);
@ -2180,7 +2256,7 @@ PUGI__NS_BEGIN
size_t length = D::process(data, data_length, 0, utf8_counter()); size_t length = D::process(data, data_length, 0, utf8_counter());
// allocate buffer of suitable length // allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false; if (!buffer) return false;
// second pass: convert utf16 input to utf8 // second pass: convert utf16 input to utf8
@ -2205,7 +2281,7 @@ PUGI__NS_BEGIN
return size; return size;
} }
PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{ {
const uint8_t* data = static_cast<const uint8_t*>(contents); const uint8_t* data = static_cast<const uint8_t*>(contents);
size_t data_length = size; size_t data_length = size;
@ -2218,13 +2294,13 @@ PUGI__NS_BEGIN
size_t postfix_length = data_length - prefix_length; size_t postfix_length = data_length - prefix_length;
// if no conversion is needed, just return the original buffer // if no conversion is needed, just return the original buffer
if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// first pass: get length in utf8 units // first pass: get length in utf8 units
size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter()); size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter());
// allocate buffer of suitable length // allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false; if (!buffer) return false;
// second pass: convert latin1 input to utf8 // second pass: convert latin1 input to utf8
@ -2242,11 +2318,11 @@ PUGI__NS_BEGIN
return true; return true;
} }
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{ {
// fast path: no conversion required // fast path: no conversion required
if (encoding == encoding_utf8) if (encoding == encoding_utf8)
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// source encoding is utf16 // source encoding is utf16
if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
@ -2254,8 +2330,8 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
return (native_encoding == encoding) ? return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) : convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>()); convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_true>());
} }
// source encoding is utf32 // source encoding is utf32
@ -2264,13 +2340,13 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
return (native_encoding == encoding) ? return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) : convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>()); convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_true>());
} }
// source encoding is latin1 // source encoding is latin1
if (encoding == encoding_latin1) if (encoding == encoding_latin1)
return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable, pool);
assert(false && "Invalid encoding"); // unreachable assert(false && "Invalid encoding"); // unreachable
return false; return false;
@ -4701,10 +4777,11 @@ PUGI__NS_BEGIN
size_t length = 0; size_t length = 0;
// coverity[var_deref_model] // coverity[var_deref_model]
if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); xml_memory_pool* pool = doc->get_memory_pool();
if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable, pool)) return impl::make_parse_result(status_out_of_memory);
// delete original buffer if we performed a conversion // delete original buffer if we performed a conversion
if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); if (own && buffer != contents && contents) pool->deallocate(contents);
// grab onto buffer if it's our buffer, user is responsible for deallocating contents himself // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
if (own || buffer != contents) *out_buffer = buffer; if (own || buffer != contents) *out_buffer = buffer;
@ -4798,7 +4875,8 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t); size_t max_suffix_size = sizeof(char_t);
// allocate buffer for the whole file // allocate buffer for the whole file
char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size)); xml_memory_pool* pool = doc->get_memory_pool();
char* contents = static_cast<char*>(impl::aligned_allocate(pool, size + max_suffix_size));
if (!contents) return make_parse_result(status_out_of_memory); if (!contents) return make_parse_result(status_out_of_memory);
// read file in memory // read file in memory
@ -4806,7 +4884,7 @@ PUGI__NS_BEGIN
if (read_size != size) if (read_size != size)
{ {
xml_memory::deallocate(contents); pool->deallocate(contents);
return make_parse_result(status_io_error); return make_parse_result(status_io_error);
} }
@ -4823,12 +4901,12 @@ PUGI__NS_BEGIN
#ifndef PUGIXML_NO_STL #ifndef PUGIXML_NO_STL
template <typename T> struct xml_stream_chunk template <typename T> struct xml_stream_chunk
{ {
static xml_stream_chunk* create() static xml_stream_chunk* create(xml_memory_pool* p)
{ {
void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); void* memory = p->allocate(sizeof(xml_stream_chunk));
if (!memory) return 0; if (!memory) return 0;
return new (memory) xml_stream_chunk(); return new (memory) xml_stream_chunk(p);
} }
static void destroy(xml_stream_chunk* chunk) static void destroy(xml_stream_chunk* chunk)
@ -4838,23 +4916,25 @@ PUGI__NS_BEGIN
{ {
xml_stream_chunk* next_ = chunk->next; xml_stream_chunk* next_ = chunk->next;
xml_memory::deallocate(chunk); xml_memory_pool* p = chunk->pool;
p->deallocate(chunk);
chunk = next_; chunk = next_;
} }
} }
xml_stream_chunk(): next(0), size(0) explicit xml_stream_chunk(xml_memory_pool* p): pool(p), next(0), size(0)
{ {
} }
xml_memory_pool* pool;
xml_stream_chunk* next; xml_stream_chunk* next;
size_t size; size_t size;
T data[xml_memory_page_size / sizeof(T)]; T data[xml_memory_page_size / sizeof(T)];
}; };
template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size) template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool)
{ {
auto_deleter<xml_stream_chunk<T> > chunks(0, xml_stream_chunk<T>::destroy); auto_deleter<xml_stream_chunk<T> > chunks(0, xml_stream_chunk<T>::destroy);
@ -4865,7 +4945,7 @@ PUGI__NS_BEGIN
while (!stream.eof()) while (!stream.eof())
{ {
// allocate new chunk // allocate new chunk
xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create(); xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create(pool);
if (!chunk) return status_out_of_memory; if (!chunk) return status_out_of_memory;
// append chunk to list // append chunk to list
@ -4887,7 +4967,7 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t); size_t max_suffix_size = sizeof(char_t);
// copy chunk list to a contiguous buffer // copy chunk list to a contiguous buffer
char* buffer = static_cast<char*>(xml_memory::allocate(total + max_suffix_size)); char* buffer = static_cast<char*>(impl::aligned_allocate(pool, total + max_suffix_size));
if (!buffer) return status_out_of_memory; if (!buffer) return status_out_of_memory;
char* write = buffer; char* write = buffer;
@ -4908,7 +4988,7 @@ PUGI__NS_BEGIN
return status_ok; return status_ok;
} }
template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size) template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool)
{ {
// get length of remaining data in stream // get length of remaining data in stream
typename std::basic_istream<T>::pos_type pos = stream.tellg(); typename std::basic_istream<T>::pos_type pos = stream.tellg();
@ -4926,7 +5006,7 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t); size_t max_suffix_size = sizeof(char_t);
// read stream data into memory (guard against stream exceptions with buffer holder) // read stream data into memory (guard against stream exceptions with buffer holder)
auto_deleter<void> buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); auto_deleter<void> buffer(pool, read_length * sizeof(T) + max_suffix_size);
if (!buffer.data) return status_out_of_memory; if (!buffer.data) return status_out_of_memory;
stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length)); stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length));
@ -4957,10 +5037,10 @@ PUGI__NS_BEGIN
if (stream.tellg() < 0) if (stream.tellg() < 0)
{ {
stream.clear(); // clear error flags that could be set by a failing tellg stream.clear(); // clear error flags that could be set by a failing tellg
status = load_stream_data_noseek(stream, &buffer, &size); status = load_stream_data_noseek(stream, &buffer, &size, doc->get_memory_pool());
} }
else else
status = load_stream_data_seek(stream, &buffer, &size); status = load_stream_data_seek(stream, &buffer, &size, doc->get_memory_pool());
if (status != status_ok) return make_parse_result(status); if (status != status_ok) return make_parse_result(status);
@ -4985,7 +5065,7 @@ PUGI__NS_BEGIN
size_t size = as_utf8_begin(str, length); size_t size = as_utf8_begin(str, length);
// allocate resulting string // allocate resulting string
char* result = static_cast<char*>(xml_memory::allocate(size + 1)); char* result = static_cast<char*>(get_default_memory_pool()->allocate(size + 1));
if (!result) return 0; if (!result) return 0;
// second pass: convert to utf8 // second pass: convert to utf8
@ -5011,7 +5091,7 @@ PUGI__NS_BEGIN
FILE* result = fopen(path_utf8, mode_ascii); FILE* result = fopen(path_utf8, mode_ascii);
// free dummy buffer // free dummy buffer
xml_memory::deallocate(path_utf8); get_default_memory_pool()->deallocate(path_utf8);
return result; return result;
} }
@ -6912,18 +6992,24 @@ namespace pugi
PUGI__FN xml_document::xml_document(): _buffer(0) PUGI__FN xml_document::xml_document(): _buffer(0)
{ {
_create(); _create(impl::get_default_memory_pool());
}
PUGI__FN xml_document::xml_document(xml_memory_pool& pool): _buffer(0)
{
_create(&pool);
} }
PUGI__FN xml_document::~xml_document() PUGI__FN xml_document::~xml_document()
{ {
_destroy(); (void)_destroy();
} }
#ifdef PUGIXML_HAS_MOVE #ifdef PUGIXML_HAS_MOVE
PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0) PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0)
{ {
_create(); impl::xml_document_struct* other = static_cast<impl::xml_document_struct*>(rhs._root);
_create(other->get_memory_pool());
_move(rhs); _move(rhs);
} }
@ -6931,8 +7017,8 @@ namespace pugi
{ {
if (this == &rhs) return *this; if (this == &rhs) return *this;
_destroy(); xml_memory_pool* pool = _destroy();
_create(); _create(pool);
_move(rhs); _move(rhs);
return *this; return *this;
@ -6941,8 +7027,8 @@ namespace pugi
PUGI__FN void xml_document::reset() PUGI__FN void xml_document::reset()
{ {
_destroy(); xml_memory_pool* pool = _destroy();
_create(); _create(pool);
} }
PUGI__FN void xml_document::reset(const xml_document& proto) PUGI__FN void xml_document::reset(const xml_document& proto)
@ -6953,7 +7039,7 @@ namespace pugi
append_copy(cur); append_copy(cur);
} }
PUGI__FN void xml_document::_create() PUGI__FN void xml_document::_create(xml_memory_pool* pool)
{ {
assert(!_root); assert(!_root);
@ -6981,7 +7067,7 @@ namespace pugi
#endif #endif
// allocate new root // allocate new root
_root = new (reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page); _root = new (reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(pool, page);
_root->prev_sibling_c = _root; _root->prev_sibling_c = _root;
// setup sentinel page // setup sentinel page
@ -6996,21 +7082,23 @@ namespace pugi
assert(reinterpret_cast<char*>(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); assert(reinterpret_cast<char*>(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory));
} }
PUGI__FN void xml_document::_destroy() PUGI__FN xml_memory_pool* xml_document::_destroy()
{ {
assert(_root); assert(_root);
impl::xml_document_struct* doc = static_cast<impl::xml_document_struct*>(_root);
xml_memory_pool* pool = doc->get_memory_pool();
// destroy static storage // destroy static storage
if (_buffer) if (_buffer)
{ {
impl::xml_memory::deallocate(_buffer); pool->deallocate(_buffer);
_buffer = 0; _buffer = 0;
} }
// destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator)
for (impl::xml_extra_buffer* extra = static_cast<impl::xml_document_struct*>(_root)->extra_buffers; extra; extra = extra->next) for (impl::xml_extra_buffer* extra = doc->extra_buffers; extra; extra = extra->next)
{ {
if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); if (extra->buffer) pool->deallocate(extra->buffer);
} }
// destroy dynamic storage, leave sentinel page (it's in static memory) // destroy dynamic storage, leave sentinel page (it's in static memory)
@ -7022,7 +7110,7 @@ namespace pugi
{ {
impl::xml_memory_page* next = page->next; impl::xml_memory_page* next = page->next;
impl::xml_allocator::deallocate_page(page); doc->deallocate_page(page);
page = next; page = next;
} }
@ -7033,6 +7121,8 @@ namespace pugi
#endif #endif
_root = 0; _root = 0;
return pool;
} }
#ifdef PUGIXML_HAS_MOVE #ifdef PUGIXML_HAS_MOVE
@ -7137,7 +7227,7 @@ namespace pugi
} }
// reset other document // reset other document
new (other) impl::xml_document_struct(PUGI__GETPAGE(other)); new (other) impl::xml_document_struct(other->get_memory_pool(), PUGI__GETPAGE(other));
rhs._buffer = 0; rhs._buffer = 0;
} }
#endif #endif
@ -7637,7 +7727,7 @@ PUGI__NS_BEGIN
size_t block_size = block_capacity + offsetof(xpath_memory_block, data); size_t block_size = block_capacity + offsetof(xpath_memory_block, data);
xpath_memory_block* block = static_cast<xpath_memory_block*>(xml_memory::allocate(block_size)); xpath_memory_block* block = static_cast<xpath_memory_block*>(get_default_memory_pool()->allocate(block_size));
if (!block) if (!block)
{ {
if (_error) *_error = true; if (_error) *_error = true;
@ -7692,7 +7782,7 @@ PUGI__NS_BEGIN
if (next) if (next)
{ {
xml_memory::deallocate(_root->next); get_default_memory_pool()->deallocate(_root->next);
_root->next = next; _root->next = next;
} }
} }
@ -7710,7 +7800,7 @@ PUGI__NS_BEGIN
{ {
xpath_memory_block* next = cur->next; xpath_memory_block* next = cur->next;
xml_memory::deallocate(cur); get_default_memory_pool()->deallocate(cur);
cur = next; cur = next;
} }
@ -7729,7 +7819,7 @@ PUGI__NS_BEGIN
{ {
xpath_memory_block* next = cur->next; xpath_memory_block* next = cur->next;
xml_memory::deallocate(cur); get_default_memory_pool()->deallocate(cur);
cur = next; cur = next;
} }
@ -8397,7 +8487,7 @@ PUGI__NS_BEGIN
#endif #endif
} }
PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result, xml_memory_pool* pool)
{ {
size_t length = static_cast<size_t>(end - begin); size_t length = static_cast<size_t>(end - begin);
char_t* scratch = buffer; char_t* scratch = buffer;
@ -8405,7 +8495,7 @@ PUGI__NS_BEGIN
if (length >= sizeof(buffer) / sizeof(buffer[0])) if (length >= sizeof(buffer) / sizeof(buffer[0]))
{ {
// need to make dummy on-heap copy // need to make dummy on-heap copy
scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); scratch = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!scratch) return false; if (!scratch) return false;
} }
@ -8416,7 +8506,7 @@ PUGI__NS_BEGIN
*out_result = convert_string_to_number(scratch); *out_result = convert_string_to_number(scratch);
// free dummy buffer // free dummy buffer
if (scratch != buffer) xml_memory::deallocate(scratch); if (scratch != buffer) pool->deallocate(scratch);
return true; return true;
} }
@ -8658,7 +8748,7 @@ PUGI__NS_BEGIN
~xpath_variable_string() ~xpath_variable_string()
{ {
if (value) xml_memory::deallocate(value); if (value) get_default_memory_pool()->deallocate(value);
} }
char_t* value; char_t* value;
@ -8702,7 +8792,7 @@ PUGI__NS_BEGIN
if (length == 0) return 0; // empty variable names are invalid if (length == 0) return 0; // empty variable names are invalid
// $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters
void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); void* memory = get_default_memory_pool()->allocate(sizeof(T) + length * sizeof(char_t));
if (!memory) return 0; if (!memory) return 0;
T* result = new (memory) T(); T* result = new (memory) T();
@ -8736,7 +8826,7 @@ PUGI__NS_BEGIN
template <typename T> PUGI__FN void delete_xpath_variable(T* var) template <typename T> PUGI__FN void delete_xpath_variable(T* var)
{ {
var->~T(); var->~T();
xml_memory::deallocate(var); get_default_memory_pool()->deallocate(var);
} }
PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var)
@ -8786,7 +8876,7 @@ PUGI__NS_BEGIN
} }
} }
PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result, xml_memory_pool* pool)
{ {
size_t length = static_cast<size_t>(end - begin); size_t length = static_cast<size_t>(end - begin);
char_t* scratch = buffer; char_t* scratch = buffer;
@ -8794,7 +8884,7 @@ PUGI__NS_BEGIN
if (length >= sizeof(buffer) / sizeof(buffer[0])) if (length >= sizeof(buffer) / sizeof(buffer[0]))
{ {
// need to make dummy on-heap copy // need to make dummy on-heap copy
scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); scratch = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!scratch) return false; if (!scratch) return false;
} }
@ -8805,7 +8895,7 @@ PUGI__NS_BEGIN
*out_result = set->get(scratch); *out_result = set->get(scratch);
// free dummy buffer // free dummy buffer
if (scratch != buffer) xml_memory::deallocate(scratch); if (scratch != buffer) pool->deallocate(scratch);
return true; return true;
} }
@ -11444,7 +11534,7 @@ PUGI__NS_BEGIN
return error("Unknown variable: variable set is not provided"); return error("Unknown variable: variable set is not provided");
xpath_variable* var = 0; xpath_variable* var = 0;
if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var)) if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var, get_default_memory_pool()))
return error_oom(); return error_oom();
if (!var) if (!var)
@ -11484,7 +11574,7 @@ PUGI__NS_BEGIN
{ {
double value = 0; double value = 0;
if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value, get_default_memory_pool()))
return error_oom(); return error_oom();
_lexer.next(); _lexer.next();
@ -12023,7 +12113,7 @@ PUGI__NS_BEGIN
{ {
static xpath_query_impl* create() static xpath_query_impl* create()
{ {
void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); void* memory = get_default_memory_pool()->allocate(sizeof(xpath_query_impl));
if (!memory) return 0; if (!memory) return 0;
return new (memory) xpath_query_impl(); return new (memory) xpath_query_impl();
@ -12035,7 +12125,7 @@ PUGI__NS_BEGIN
impl->alloc.release(); impl->alloc.release();
// free allocator memory (with the first page) // free allocator memory (with the first page)
xml_memory::deallocate(impl); get_default_memory_pool()->deallocate(impl);
} }
xpath_query_impl(): root(0), alloc(&block, &oom), oom(false) xpath_query_impl(): root(0), alloc(&block, &oom), oom(false)
@ -12072,6 +12162,18 @@ PUGI__NS_END
namespace pugi namespace pugi
{ {
PUGI__FN void* xml_monotonic_memory_pool::allocate(size_t size)
{
if (PUGI__UNLIKELY(size > _capacity))
return 0;
void* block = _buffer;
_buffer = reinterpret_cast<char*>(_buffer) + size;
_capacity -= size;
return block;
}
#ifndef PUGIXML_NO_EXCEPTIONS #ifndef PUGIXML_NO_EXCEPTIONS
PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_)
{ {
@ -12159,7 +12261,7 @@ namespace pugi
size_t size_ = static_cast<size_t>(end_ - begin_); size_t size_ = static_cast<size_t>(end_ - begin_);
// use internal buffer for 0 or 1 elements, heap buffer otherwise // use internal buffer for 0 or 1 elements, heap buffer otherwise
xpath_node* storage = (size_ <= 1) ? _storage : static_cast<xpath_node*>(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); xpath_node* storage = (size_ <= 1) ? _storage : static_cast<xpath_node*>(impl::get_default_memory_pool()->allocate(size_ * sizeof(xpath_node)));
if (!storage) if (!storage)
{ {
@ -12172,7 +12274,7 @@ namespace pugi
// deallocate old buffer // deallocate old buffer
if (_begin != _storage) if (_begin != _storage)
impl::xml_memory::deallocate(_begin); impl::get_default_memory_pool()->deallocate(_begin);
// size check is necessary because for begin_ = end_ = nullptr, memcpy is UB // size check is necessary because for begin_ = end_ = nullptr, memcpy is UB
if (size_) if (size_)
@ -12209,7 +12311,7 @@ namespace pugi
PUGI__FN xpath_node_set::~xpath_node_set() PUGI__FN xpath_node_set::~xpath_node_set()
{ {
if (_begin != _storage) if (_begin != _storage)
impl::xml_memory::deallocate(_begin); impl::get_default_memory_pool()->deallocate(_begin);
} }
PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage) PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage)
@ -12237,7 +12339,7 @@ namespace pugi
if (this == &rhs) return *this; if (this == &rhs) return *this;
if (_begin != _storage) if (_begin != _storage)
impl::xml_memory::deallocate(_begin); impl::get_default_memory_pool()->deallocate(_begin);
_move(rhs); _move(rhs);
@ -12377,13 +12479,13 @@ namespace pugi
// duplicate string // duplicate string
size_t size = (impl::strlength(value) + 1) * sizeof(char_t); size_t size = (impl::strlength(value) + 1) * sizeof(char_t);
char_t* copy = static_cast<char_t*>(impl::xml_memory::allocate(size)); char_t* copy = static_cast<char_t*>(impl::get_default_memory_pool()->allocate(size));
if (!copy) return false; if (!copy) return false;
memcpy(copy, value, size); memcpy(copy, value, size);
// replace old string // replace old string
if (var->value) impl::xml_memory::deallocate(var->value); if (var->value) impl::get_default_memory_pool()->deallocate(var->value);
var->value = copy; var->value = copy;
return true; return true;

View File

@ -307,6 +307,31 @@ namespace pugi
It _begin, _end; It _begin, _end;
}; };
// Interface for pool allocators
class PUGIXML_CLASS xml_memory_pool
{
public:
virtual ~xml_memory_pool() {}
virtual void* allocate(size_t size) = 0;
virtual void deallocate(void* ptr) = 0;
};
// Non-owning allocate-only fixed size memory pool, deallocation is no-op
// Requires buffer of at least PUGIXML_MEMORY_PAGE_SIZE bytes for xml_document construction
class PUGIXML_CLASS xml_monotonic_memory_pool : public xml_memory_pool
{
public:
xml_monotonic_memory_pool(void* buffer, size_t size) PUGIXML_NOEXCEPT : _buffer(buffer), _capacity(size) {}
virtual void* allocate(size_t size) PUGIXML_OVERRIDE;
virtual void deallocate(void*) PUGIXML_OVERRIDE {}
private:
void* _buffer;
size_t _capacity;
};
// Writer interface for node printing (see xml_node::print) // Writer interface for node printing (see xml_node::print)
class PUGIXML_CLASS xml_writer class PUGIXML_CLASS xml_writer
{ {
@ -1021,14 +1046,17 @@ namespace pugi
xml_document(const xml_document&); xml_document(const xml_document&);
xml_document& operator=(const xml_document&); xml_document& operator=(const xml_document&);
void _create(); void _create(xml_memory_pool* pool);
void _destroy(); xml_memory_pool* _destroy();
void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT;
public: public:
// Default constructor, makes empty document // Default constructor, makes empty document
xml_document(); xml_document();
// Create empty document using supplied memory pool
explicit xml_document(xml_memory_pool& pool);
// Destructor, invalidates all node/attribute handles to this document // Destructor, invalidates all node/attribute handles to this document
~xml_document(); ~xml_document();

View File

@ -30,6 +30,47 @@ namespace
page_deallocs += is_page(memory_size(ptr)); page_deallocs += is_page(memory_size(ptr));
memory_deallocate(ptr); memory_deallocate(ptr);
} }
void* null_allocate(size_t)
{
#ifndef PUGIXML_NO_EXCEPTIONS
throw std::runtime_error("null_allocate was invoked");
#else
return 0;
#endif
}
void null_deallocate(void*)
{
#ifndef PUGIXML_NO_EXCEPTIONS
throw std::runtime_error("null_deallocate was invoked");
#endif
}
class test_memory_pool : public pugi::xml_memory_pool
{
public:
test_memory_pool(pugi::allocation_function alloc, pugi::deallocation_function dealloc) PUGIXML_NOEXCEPT
: _allocate(alloc)
, _deallocate(dealloc)
{
}
void* allocate(size_t size) PUGIXML_OVERRIDE
{
return _allocate(size);
}
void deallocate(void* ptr) PUGIXML_OVERRIDE
{
_deallocate(ptr);
}
private:
pugi::allocation_function _allocate;
pugi::deallocation_function _deallocate;
};
} }
TEST(memory_custom_memory_management) TEST(memory_custom_memory_management)
@ -76,6 +117,53 @@ TEST(memory_custom_memory_management)
set_memory_management_functions(old_allocate, old_deallocate); set_memory_management_functions(old_allocate, old_deallocate);
} }
TEST(memory_pool_custom_memory_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
// parse document
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
CHECK(doc.load_string(STR("<node />")));
CHECK(page_allocs == 1 && page_deallocs == 0);
// modify document (no new page)
CHECK(doc.first_child().set_name(STR("foobars")));
CHECK(page_allocs == 1 && page_deallocs == 0);
// modify document (new page)
std::basic_string<char_t> s(65536, 'x');
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 2 && page_deallocs == 0);
// modify document (new page, old one should die)
s += s;
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 3 && page_deallocs == 1);
}
CHECK(page_allocs == 3 && page_deallocs == 3);
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_large_allocations) TEST(memory_large_allocations)
{ {
page_allocs = page_deallocs = 0; page_allocs = page_deallocs = 0;
@ -140,6 +228,73 @@ TEST(memory_large_allocations)
set_memory_management_functions(old_allocate, old_deallocate); set_memory_management_functions(old_allocate, old_deallocate);
} }
TEST(memory_pool_large_allocations)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
// initial fill
for (size_t i = 0; i < 128; ++i)
{
std::basic_string<char_t> s(i * 128, 'x');
CHECK(doc.append_child(node_pcdata).set_value(s.c_str()));
}
CHECK(page_allocs > 0 && page_deallocs == 0);
// grow-prune loop
while (doc.first_child())
{
xml_node node;
// grow
for (node = doc.first_child(); node; node = node.next_sibling())
{
std::basic_string<char_t> s = node.value();
CHECK(node.set_value((s + s).c_str()));
}
// prune
for (node = doc.first_child(); node; )
{
xml_node next = node.next_sibling().next_sibling();
node.parent().remove_child(node);
node = next;
}
}
CHECK(page_allocs == page_deallocs + 1); // only one live page left (it waits for new allocations)
char buffer;
CHECK(doc.load_buffer_inplace(&buffer, 0, parse_fragment, get_native_encoding()));
CHECK(page_allocs == page_deallocs); // no live pages left
}
CHECK(page_allocs == page_deallocs); // everything is freed
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_page_management) TEST(memory_page_management)
{ {
page_allocs = page_deallocs = 0; page_allocs = page_deallocs = 0;
@ -197,6 +352,66 @@ TEST(memory_page_management)
set_memory_management_functions(old_allocate, old_deallocate); set_memory_management_functions(old_allocate, old_deallocate);
} }
TEST(memory_resource_page_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
// initial fill
std::vector<xml_node> nodes;
for (size_t i = 0; i < 4000; ++i)
{
xml_node node = doc.append_child(STR("n"));
CHECK(node);
nodes.push_back(node);
}
CHECK(page_allocs > 0 && page_deallocs == 0);
// grow-prune loop
size_t offset = 0;
size_t prime = 15485863;
while (nodes.size() > 0)
{
offset = (offset + prime) % nodes.size();
doc.remove_child(nodes[offset]);
nodes[offset] = nodes.back();
nodes.pop_back();
}
CHECK(page_allocs == page_deallocs + 1); // only one live page left (it waits for new allocations)
char buffer;
CHECK(doc.load_buffer_inplace(&buffer, 0, parse_fragment, get_native_encoding()));
CHECK(page_allocs == page_deallocs); // no live pages left
}
CHECK(page_allocs == page_deallocs); // everything is freed
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_string_allocate_increasing) TEST(memory_string_allocate_increasing)
{ {
xml_document doc; xml_document doc;
@ -301,3 +516,55 @@ TEST(memory_string_allocate_decreasing_inplace)
CHECK(result == "x"); CHECK(result == "x");
} }
TEST(monotonic_memory_pool_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
const size_t buffer_size = 512 * 1024;
void* buffer = old_allocate(buffer_size);
{
// create custom memory pool
xml_monotonic_memory_pool pool(buffer, buffer_size);
// parse document
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
CHECK(doc.load_string(STR("<node />")));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (no new page)
CHECK(doc.first_child().set_name(STR("foobars")));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (new page)
std::basic_string<char_t> s(65536, 'x');
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (new page, old one should die)
s += s;
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 0 && page_deallocs == 0);
}
CHECK(page_allocs == 0 && page_deallocs == 0);
old_deallocate(buffer);
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}