diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 156a1f4..97cd5f7 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -191,6 +191,8 @@ PUGI__NS_BEGIN free(ptr); } + void* aligned_allocate(xml_memory_pool* pool, size_t size); + template struct xml_memory_management_function_storage { @@ -282,14 +284,56 @@ PUGI__NS_BEGIN return result; } }; + + template<> struct auto_deleter + { + 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 +// 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 PUGI__NS_BEGIN class compact_hash_table { 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) { - xml_memory::deallocate(_items); + _pool->deallocate(_items); _items = 0; _capacity = 0; _count = 0; @@ -346,6 +390,7 @@ PUGI__NS_BEGIN void* value; }; + xml_memory_pool* _pool; item_t* _items; size_t _capacity; @@ -397,9 +442,9 @@ PUGI__NS_BEGIN while (count >= capacity - capacity / 4) capacity *= 2; - compact_hash_table rt; + compact_hash_table rt(_pool); rt._capacity = capacity; - rt._items = static_cast(xml_memory::allocate(sizeof(item_t) * capacity)); + rt._items = static_cast(impl::aligned_allocate(_pool, sizeof(item_t) * capacity)); if (!rt._items) return false; @@ -411,7 +456,7 @@ PUGI__NS_BEGIN rt.insert(_items[i].key, _items[i].value); if (_items) - xml_memory::deallocate(_items); + _pool->deallocate(_items); _capacity = capacity; _items = rt._items; @@ -453,6 +498,24 @@ PUGI__NS_BEGIN #define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header) #define PUGI__NODETYPE(n) static_cast((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_memory_page @@ -507,7 +570,7 @@ PUGI__NS_BEGIN 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 _hash = 0; @@ -519,7 +582,7 @@ PUGI__NS_BEGIN size_t size = sizeof(xml_memory_page) + data_size; // 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; // prepare page structure @@ -531,9 +594,9 @@ PUGI__NS_BEGIN 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); @@ -644,7 +707,7 @@ PUGI__NS_BEGIN size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); // 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_string_header* header = static_cast(allocate_memory(full_size, page)); @@ -696,6 +759,7 @@ PUGI__NS_BEGIN #endif } + xml_memory_pool* _pool; xml_memory_page* _root; size_t _busy_size; @@ -1140,10 +1204,22 @@ PUGI__NS_BEGIN 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; xml_extra_buffer* extra_buffers; @@ -2037,7 +2113,7 @@ PUGI__NS_BEGIN 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); @@ -2048,7 +2124,7 @@ PUGI__NS_BEGIN } else { - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + char_t* buffer = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!buffer) return false; 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); } - 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(contents); size_t length = size / sizeof(char_t); @@ -2088,7 +2164,7 @@ PUGI__NS_BEGIN } else { - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + char_t* buffer = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!buffer) return false; convert_wchar_endian_swap(buffer, data, length); @@ -2101,7 +2177,7 @@ PUGI__NS_BEGIN return true; } - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + template 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(contents); 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()); // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + char_t* buffer = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert utf16 input to wchar_t @@ -2126,22 +2202,22 @@ PUGI__NS_BEGIN 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 xml_encoding wchar_encoding = get_wchar_encoding(); // fast path: no conversion required 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 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 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 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; return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder()); } // source encoding is utf32 @@ -2159,19 +2235,19 @@ PUGI__NS_BEGIN xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder()); } // source encoding is 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 return false; } #else - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + template 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(contents); 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()); // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + char_t* buffer = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert utf16 input to utf8 @@ -2205,7 +2281,7 @@ PUGI__NS_BEGIN 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(contents); size_t data_length = size; @@ -2218,13 +2294,13 @@ PUGI__NS_BEGIN size_t postfix_length = data_length - prefix_length; // 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 size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter()); // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + char_t* buffer = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!buffer) return false; // second pass: convert latin1 input to utf8 @@ -2242,11 +2318,11 @@ PUGI__NS_BEGIN 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 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 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; return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder()); } // source encoding is utf32 @@ -2264,13 +2340,13 @@ PUGI__NS_BEGIN xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder()); } // source encoding is 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 return false; @@ -4701,10 +4777,11 @@ PUGI__NS_BEGIN size_t length = 0; // 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 - 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 if (own || buffer != contents) *out_buffer = buffer; @@ -4798,7 +4875,8 @@ PUGI__NS_BEGIN size_t max_suffix_size = sizeof(char_t); // allocate buffer for the whole file - char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); + xml_memory_pool* pool = doc->get_memory_pool(); + char* contents = static_cast(impl::aligned_allocate(pool, size + max_suffix_size)); if (!contents) return make_parse_result(status_out_of_memory); // read file in memory @@ -4806,7 +4884,7 @@ PUGI__NS_BEGIN if (read_size != size) { - xml_memory::deallocate(contents); + pool->deallocate(contents); return make_parse_result(status_io_error); } @@ -4823,12 +4901,12 @@ PUGI__NS_BEGIN #ifndef PUGIXML_NO_STL template 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; - return new (memory) xml_stream_chunk(); + return new (memory) xml_stream_chunk(p); } static void destroy(xml_stream_chunk* chunk) @@ -4838,23 +4916,25 @@ PUGI__NS_BEGIN { xml_stream_chunk* next_ = chunk->next; - xml_memory::deallocate(chunk); + xml_memory_pool* p = chunk->pool; + p->deallocate(chunk); 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; size_t size; T data[xml_memory_page_size / sizeof(T)]; }; - template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool) { auto_deleter > chunks(0, xml_stream_chunk::destroy); @@ -4865,7 +4945,7 @@ PUGI__NS_BEGIN while (!stream.eof()) { // allocate new chunk - xml_stream_chunk* chunk = xml_stream_chunk::create(); + xml_stream_chunk* chunk = xml_stream_chunk::create(pool); if (!chunk) return status_out_of_memory; // append chunk to list @@ -4887,7 +4967,7 @@ PUGI__NS_BEGIN size_t max_suffix_size = sizeof(char_t); // copy chunk list to a contiguous buffer - char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); + char* buffer = static_cast(impl::aligned_allocate(pool, total + max_suffix_size)); if (!buffer) return status_out_of_memory; char* write = buffer; @@ -4908,7 +4988,7 @@ PUGI__NS_BEGIN return status_ok; } - template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool) { // get length of remaining data in stream typename std::basic_istream::pos_type pos = stream.tellg(); @@ -4926,7 +5006,7 @@ PUGI__NS_BEGIN size_t max_suffix_size = sizeof(char_t); // read stream data into memory (guard against stream exceptions with buffer holder) - auto_deleter buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); + auto_deleter buffer(pool, read_length * sizeof(T) + max_suffix_size); if (!buffer.data) return status_out_of_memory; stream.read(static_cast(buffer.data), static_cast(read_length)); @@ -4957,10 +5037,10 @@ PUGI__NS_BEGIN if (stream.tellg() < 0) { 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 - 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); @@ -4985,7 +5065,7 @@ PUGI__NS_BEGIN size_t size = as_utf8_begin(str, length); // allocate resulting string - char* result = static_cast(xml_memory::allocate(size + 1)); + char* result = static_cast(get_default_memory_pool()->allocate(size + 1)); if (!result) return 0; // second pass: convert to utf8 @@ -5011,7 +5091,7 @@ PUGI__NS_BEGIN FILE* result = fopen(path_utf8, mode_ascii); // free dummy buffer - xml_memory::deallocate(path_utf8); + get_default_memory_pool()->deallocate(path_utf8); return result; } @@ -6912,18 +6992,24 @@ namespace pugi 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() { - _destroy(); + (void)_destroy(); } #ifdef PUGIXML_HAS_MOVE PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0) { - _create(); + impl::xml_document_struct* other = static_cast(rhs._root); + _create(other->get_memory_pool()); _move(rhs); } @@ -6931,8 +7017,8 @@ namespace pugi { if (this == &rhs) return *this; - _destroy(); - _create(); + xml_memory_pool* pool = _destroy(); + _create(pool); _move(rhs); return *this; @@ -6941,8 +7027,8 @@ namespace pugi PUGI__FN void xml_document::reset() { - _destroy(); - _create(); + xml_memory_pool* pool = _destroy(); + _create(pool); } PUGI__FN void xml_document::reset(const xml_document& proto) @@ -6953,7 +7039,7 @@ namespace pugi append_copy(cur); } - PUGI__FN void xml_document::_create() + PUGI__FN void xml_document::_create(xml_memory_pool* pool) { assert(!_root); @@ -6981,7 +7067,7 @@ namespace pugi #endif // allocate new root - _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page); + _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(pool, page); _root->prev_sibling_c = _root; // setup sentinel page @@ -6996,21 +7082,23 @@ namespace pugi assert(reinterpret_cast(_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); + impl::xml_document_struct* doc = static_cast(_root); + xml_memory_pool* pool = doc->get_memory_pool(); // destroy static storage if (_buffer) { - impl::xml_memory::deallocate(_buffer); + pool->deallocate(_buffer); _buffer = 0; } // 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(_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) @@ -7022,7 +7110,7 @@ namespace pugi { impl::xml_memory_page* next = page->next; - impl::xml_allocator::deallocate_page(page); + doc->deallocate_page(page); page = next; } @@ -7033,6 +7121,8 @@ namespace pugi #endif _root = 0; + + return pool; } #ifdef PUGIXML_HAS_MOVE @@ -7137,7 +7227,7 @@ namespace pugi } // 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; } #endif @@ -7637,7 +7727,7 @@ PUGI__NS_BEGIN size_t block_size = block_capacity + offsetof(xpath_memory_block, data); - xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); + xpath_memory_block* block = static_cast(get_default_memory_pool()->allocate(block_size)); if (!block) { if (_error) *_error = true; @@ -7692,7 +7782,7 @@ PUGI__NS_BEGIN if (next) { - xml_memory::deallocate(_root->next); + get_default_memory_pool()->deallocate(_root->next); _root->next = next; } } @@ -7710,7 +7800,7 @@ PUGI__NS_BEGIN { xpath_memory_block* next = cur->next; - xml_memory::deallocate(cur); + get_default_memory_pool()->deallocate(cur); cur = next; } @@ -7729,7 +7819,7 @@ PUGI__NS_BEGIN { xpath_memory_block* next = cur->next; - xml_memory::deallocate(cur); + get_default_memory_pool()->deallocate(cur); cur = next; } @@ -8397,7 +8487,7 @@ PUGI__NS_BEGIN #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(end - begin); char_t* scratch = buffer; @@ -8405,7 +8495,7 @@ PUGI__NS_BEGIN if (length >= sizeof(buffer) / sizeof(buffer[0])) { // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + scratch = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!scratch) return false; } @@ -8416,7 +8506,7 @@ PUGI__NS_BEGIN *out_result = convert_string_to_number(scratch); // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); + if (scratch != buffer) pool->deallocate(scratch); return true; } @@ -8658,7 +8748,7 @@ PUGI__NS_BEGIN ~xpath_variable_string() { - if (value) xml_memory::deallocate(value); + if (value) get_default_memory_pool()->deallocate(value); } char_t* value; @@ -8702,7 +8792,7 @@ PUGI__NS_BEGIN 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 - 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; T* result = new (memory) T(); @@ -8736,7 +8826,7 @@ PUGI__NS_BEGIN template PUGI__FN void delete_xpath_variable(T* var) { 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) @@ -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(end - begin); char_t* scratch = buffer; @@ -8794,7 +8884,7 @@ PUGI__NS_BEGIN if (length >= sizeof(buffer) / sizeof(buffer[0])) { // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + scratch = static_cast(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t))); if (!scratch) return false; } @@ -8805,7 +8895,7 @@ PUGI__NS_BEGIN *out_result = set->get(scratch); // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); + if (scratch != buffer) pool->deallocate(scratch); return true; } @@ -11444,7 +11534,7 @@ PUGI__NS_BEGIN return error("Unknown variable: variable set is not provided"); 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(); if (!var) @@ -11484,7 +11574,7 @@ PUGI__NS_BEGIN { 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(); _lexer.next(); @@ -12023,7 +12113,7 @@ PUGI__NS_BEGIN { 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; return new (memory) xpath_query_impl(); @@ -12035,7 +12125,7 @@ PUGI__NS_BEGIN impl->alloc.release(); // 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) @@ -12072,6 +12162,18 @@ PUGI__NS_END 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(_buffer) + size; + _capacity -= size; + + return block; + } + #ifndef PUGIXML_NO_EXCEPTIONS PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) { @@ -12159,7 +12261,7 @@ namespace pugi size_t size_ = static_cast(end_ - begin_); // use internal buffer for 0 or 1 elements, heap buffer otherwise - xpath_node* storage = (size_ <= 1) ? _storage : static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); + xpath_node* storage = (size_ <= 1) ? _storage : static_cast(impl::get_default_memory_pool()->allocate(size_ * sizeof(xpath_node))); if (!storage) { @@ -12172,7 +12274,7 @@ namespace pugi // deallocate old buffer 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 if (size_) @@ -12209,7 +12311,7 @@ namespace pugi PUGI__FN xpath_node_set::~xpath_node_set() { 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) @@ -12237,7 +12339,7 @@ namespace pugi if (this == &rhs) return *this; if (_begin != _storage) - impl::xml_memory::deallocate(_begin); + impl::get_default_memory_pool()->deallocate(_begin); _move(rhs); @@ -12377,13 +12479,13 @@ namespace pugi // duplicate string size_t size = (impl::strlength(value) + 1) * sizeof(char_t); - char_t* copy = static_cast(impl::xml_memory::allocate(size)); + char_t* copy = static_cast(impl::get_default_memory_pool()->allocate(size)); if (!copy) return false; memcpy(copy, value, size); // 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; return true; diff --git a/src/pugixml.hpp b/src/pugixml.hpp index f658109..9fb1e69 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -307,6 +307,31 @@ namespace pugi 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) class PUGIXML_CLASS xml_writer { @@ -1021,14 +1046,17 @@ namespace pugi xml_document(const xml_document&); xml_document& operator=(const xml_document&); - void _create(); - void _destroy(); + void _create(xml_memory_pool* pool); + xml_memory_pool* _destroy(); void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; public: // Default constructor, makes empty 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 ~xml_document(); diff --git a/tests/test_memory.cpp b/tests/test_memory.cpp index 3258736..0092eae 100644 --- a/tests/test_memory.cpp +++ b/tests/test_memory.cpp @@ -30,6 +30,47 @@ namespace page_deallocs += is_page(memory_size(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) @@ -76,6 +117,53 @@ TEST(memory_custom_memory_management) 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(""))); + + 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 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) { page_allocs = page_deallocs = 0; @@ -140,6 +228,73 @@ TEST(memory_large_allocations) 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 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 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) { page_allocs = page_deallocs = 0; @@ -197,6 +352,66 @@ TEST(memory_page_management) 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 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) { xml_document doc; @@ -301,3 +516,55 @@ TEST(memory_string_allocate_decreasing_inplace) 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(""))); + + 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 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); +}