Merge branch 'master' into compact

This commit is contained in:
Arseny Kapoulkine 2015-04-12 22:09:45 -07:00
commit 054b0b447e
7 changed files with 149 additions and 86 deletions

View File

@ -497,7 +497,7 @@ allocation_function get_memory_allocation_function();
deallocation_function get_memory_deallocation_function(); deallocation_function get_memory_deallocation_function();
---- ----
Allocation function is called with the size (in bytes) as an argument and should return a pointer to a memory block with alignment that is suitable for storage of primitive types (usually a maximum of `void*` and `double` types alignment is sufficient) and size that is greater than or equal to the requested one. If the allocation fails, the function has to return null pointer (throwing an exception from allocation function results in undefined behavior). Allocation function is called with the size (in bytes) as an argument and should return a pointer to a memory block with alignment that is suitable for storage of primitive types (usually a maximum of `void*` and `double` types alignment is sufficient) and size that is greater than or equal to the requested one. If the allocation fails, the function has to either return null pointer or to throw an exception.
Deallocation function is called with the pointer that was returned by some call to allocation function; it is never called with a null pointer. If memory management functions are not thread-safe, library thread safety is not guaranteed. Deallocation function is called with the pointer that was returned by some call to allocation function; it is never called with a null pointer. If memory management functions are not thread-safe, library thread safety is not guaranteed.

View File

@ -233,25 +233,25 @@ PUGI__NS_BEGIN
PUGI__NS_END PUGI__NS_END
#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) #if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH)
// auto_ptr-like buffer holder for exception recovery // auto_ptr-like object for exception recovery
PUGI__NS_BEGIN PUGI__NS_BEGIN
struct buffer_holder template <typename T, typename D = void(*)(T*)> struct auto_deleter
{ {
void* data; T* data;
void (*deleter)(void*); D deleter;
buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) auto_deleter(T* data_, D deleter_): data(data_), deleter(deleter_)
{ {
} }
~buffer_holder() ~auto_deleter()
{ {
if (data) deleter(data); if (data) deleter(data);
} }
void* release() T* release()
{ {
void* result = data; T* result = data;
data = 0; data = 0;
return result; return result;
} }
@ -2856,13 +2856,19 @@ PUGI__NS_BEGIN
struct xml_parser struct xml_parser
{ {
xml_allocator alloc; xml_allocator alloc;
xml_allocator* alloc_state;
char_t* error_offset; char_t* error_offset;
xml_parse_status error_status; xml_parse_status error_status;
xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) xml_parser(xml_allocator* alloc_): alloc(*alloc_), alloc_state(alloc_), error_offset(0), error_status(status_ok)
{ {
} }
~xml_parser()
{
*alloc_state = alloc;
}
// DOCTYPE consists of nested sections of the following possible types: // DOCTYPE consists of nested sections of the following possible types:
// <!-- ... -->, <? ... ?>, "...", '...' // <!-- ... -->, <? ... ?>, "...", '...'
// <![...]]> // <![...]]>
@ -3438,9 +3444,6 @@ PUGI__NS_BEGIN
static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk)
{ {
// allocator object is a part of document object
xml_allocator& alloc_ = *static_cast<xml_allocator*>(xmldoc);
// early-out for empty documents // early-out for empty documents
if (length == 0) if (length == 0)
return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element);
@ -3449,7 +3452,7 @@ PUGI__NS_BEGIN
xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0; xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0;
// create parser on stack // create parser on stack
xml_parser parser(alloc_); xml_parser parser(static_cast<xml_allocator*>(xmldoc));
// save last character and make buffer zero-terminated (speeds up parsing) // save last character and make buffer zero-terminated (speeds up parsing)
char_t endch = buffer[length - 1]; char_t endch = buffer[length - 1];
@ -3461,9 +3464,6 @@ PUGI__NS_BEGIN
// perform actual parsing // perform actual parsing
parser.parse_tree(buffer_data, root, optmsk, endch); parser.parse_tree(buffer_data, root, optmsk, endch);
// update allocator state
alloc_ = parser.alloc;
xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0);
assert(result.offset >= 0 && static_cast<size_t>(result.offset) <= length); assert(result.offset >= 0 && static_cast<size_t>(result.offset) <= length);
@ -4661,27 +4661,16 @@ PUGI__NS_BEGIN
// get file size (can result in I/O errors) // get file size (can result in I/O errors)
size_t size = 0; size_t size = 0;
xml_parse_status size_status = get_file_size(file, size); xml_parse_status size_status = get_file_size(file, size);
if (size_status != status_ok) return make_parse_result(size_status);
if (size_status != status_ok)
{
fclose(file);
return make_parse_result(size_status);
}
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)); char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size));
if (!contents) return make_parse_result(status_out_of_memory);
if (!contents)
{
fclose(file);
return make_parse_result(status_out_of_memory);
}
// read file in memory // read file in memory
size_t read_size = fread(contents, 1, size, file); size_t read_size = fread(contents, 1, size, file);
fclose(file);
if (read_size != size) if (read_size != size)
{ {
@ -4704,10 +4693,8 @@ PUGI__NS_BEGIN
return new (memory) xml_stream_chunk(); return new (memory) xml_stream_chunk();
} }
static void destroy(void* ptr) static void destroy(xml_stream_chunk* chunk)
{ {
xml_stream_chunk* chunk = static_cast<xml_stream_chunk*>(ptr);
// free chunk chain // free chunk chain
while (chunk) while (chunk)
{ {
@ -4731,7 +4718,7 @@ PUGI__NS_BEGIN
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)
{ {
buffer_holder chunks(0, xml_stream_chunk<T>::destroy); auto_deleter<xml_stream_chunk<T> > chunks(0, xml_stream_chunk<T>::destroy);
// read file to a chunk list // read file to a chunk list
size_t total = 0; size_t total = 0;
@ -4767,7 +4754,7 @@ PUGI__NS_BEGIN
char* write = buffer; char* write = buffer;
for (xml_stream_chunk<T>* chunk = static_cast<xml_stream_chunk<T>*>(chunks.data); chunk; chunk = chunk->next) for (xml_stream_chunk<T>* chunk = chunks.data; chunk; chunk = chunk->next)
{ {
assert(write + chunk->size <= buffer + total); assert(write + chunk->size <= buffer + total);
memcpy(write, chunk->data, chunk->size); memcpy(write, chunk->data, chunk->size);
@ -4801,7 +4788,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)
buffer_holder buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); auto_deleter<void> buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate);
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));
@ -4899,11 +4886,7 @@ PUGI__NS_BEGIN
xml_writer_file writer(file); xml_writer_file writer(file);
doc.save(writer, indent, flags, encoding); doc.save(writer, indent, flags, encoding);
int result = ferror(file); return ferror(file) == 0;
fclose(file);
return result == 0;
} }
PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer)
@ -4923,6 +4906,9 @@ PUGI__NS_BEGIN
// 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) impl::xml_memory::deallocate(contents);
// grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
if (own || buffer != contents) *out_buffer = buffer;
// store buffer for offset_debug // store buffer for offset_debug
doc->buffer = buffer; doc->buffer = buffer;
@ -4932,9 +4918,6 @@ PUGI__NS_BEGIN
// remember encoding // remember encoding
res.encoding = buffer_encoding; res.encoding = buffer_encoding;
// grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
if (own || buffer != contents) *out_buffer = buffer;
return res; return res;
} }
@ -5966,23 +5949,25 @@ namespace pugi
if (!extra) return impl::make_parse_result(status_out_of_memory); if (!extra) return impl::make_parse_result(status_out_of_memory);
// save name; name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level
char_t* rootname = _root->contents;
_root->contents = 0;
// parse
char_t* buffer = 0;
xml_parse_result res = impl::load_buffer_impl(doc, _root, const_cast<void*>(contents), size, options, encoding, false, false, &buffer);
// restore name
_root->contents = rootname;
// add extra buffer to the list // add extra buffer to the list
extra->buffer = buffer; extra->buffer = 0;
extra->next = doc->extra_buffers; extra->next = doc->extra_buffers;
doc->extra_buffers = extra; doc->extra_buffers = extra;
return res; // name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level
struct name_sentry
{
xml_node_struct* node;
char_t* name;
~name_sentry() { node->contents = name; }
};
name_sentry sentry = { _root, _root->contents };
sentry.node->contents = 0;
return impl::load_buffer_impl(doc, _root, const_cast<void*>(contents), size, options, encoding, false, false, &extra->buffer);
} }
PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const
@ -6800,18 +6785,18 @@ namespace pugi
{ {
reset(); reset();
FILE* file = fopen(path_, "rb"); impl::auto_deleter<FILE, int(*)(FILE*)> file(fopen(path_, "rb"), fclose);
return impl::load_file_impl(*this, file, options, encoding); return impl::load_file_impl(*this, file.data, options, encoding);
} }
PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding)
{ {
reset(); reset();
FILE* file = impl::open_file_wide(path_, L"rb"); impl::auto_deleter<FILE, int(*)(FILE*)> file(impl::open_file_wide(path_, L"rb"), fclose);
return impl::load_file_impl(*this, file, options, encoding); return impl::load_file_impl(*this, file.data, options, encoding);
} }
PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding)
@ -6879,16 +6864,16 @@ namespace pugi
PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
{ {
FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); impl::auto_deleter<FILE, int(*)(FILE*)> file(fopen(path_, (flags & format_save_file_text) ? "w" : "wb"), fclose);
return impl::save_file_impl(*this, file, indent, flags, encoding); return impl::save_file_impl(*this, file.data, indent, flags, encoding);
} }
PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
{ {
FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); impl::auto_deleter<FILE, int(*)(FILE*)> file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), fclose);
return impl::save_file_impl(*this, file, indent, flags, encoding); return impl::save_file_impl(*this, file.data, indent, flags, encoding);
} }
PUGI__FN xml_node xml_document::document_element() const PUGI__FN xml_node xml_document::document_element() const
@ -11544,15 +11529,13 @@ PUGI__NS_BEGIN
return new (memory) xpath_query_impl(); return new (memory) xpath_query_impl();
} }
static void destroy(void* ptr) static void destroy(xpath_query_impl* impl)
{ {
if (!ptr) return;
// free all allocated pages // free all allocated pages
static_cast<xpath_query_impl*>(ptr)->alloc.release(); impl->alloc.release();
// free allocator memory (with the first page) // free allocator memory (with the first page)
xml_memory::deallocate(ptr); xml_memory::deallocate(impl);
} }
xpath_query_impl(): root(0), alloc(&block) xpath_query_impl(): root(0), alloc(&block)
@ -12011,7 +11994,7 @@ namespace pugi
} }
else else
{ {
impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); impl::auto_deleter<impl::xpath_query_impl> impl(qimpl, impl::xpath_query_impl::destroy);
qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result);
@ -12019,7 +12002,7 @@ namespace pugi
{ {
qimpl->root->optimize(&qimpl->alloc); qimpl->root->optimize(&qimpl->alloc);
_impl = static_cast<impl::xpath_query_impl*>(impl_holder.release()); _impl = impl.release();
_result.error = 0; _result.error = 0;
} }
} }
@ -12027,7 +12010,8 @@ namespace pugi
PUGI__FN xpath_query::~xpath_query() PUGI__FN xpath_query::~xpath_query()
{ {
impl::xpath_query_impl::destroy(_impl); if (_impl)
impl::xpath_query_impl::destroy(static_cast<impl::xpath_query_impl*>(_impl));
} }
PUGI__FN xpath_value_type xpath_query::return_type() const PUGI__FN xpath_value_type xpath_query::return_type() const

View File

@ -50,6 +50,18 @@ static void* custom_allocate(size_t size)
} }
} }
#ifndef PUGIXML_NO_EXCEPTIONS
static void* custom_allocate_throw(size_t size)
{
void* result = custom_allocate(size);
if (!result)
throw std::bad_alloc();
return result;
}
#endif
static void custom_deallocate(void* ptr) static void custom_deallocate(void* ptr)
{ {
assert(ptr); assert(ptr);
@ -82,7 +94,7 @@ namespace std
} }
#endif #endif
static bool run_test(test_runner* test) static bool run_test(test_runner* test, const char* test_name, pugi::allocation_function allocate)
{ {
#ifndef PUGIXML_NO_EXCEPTIONS #ifndef PUGIXML_NO_EXCEPTIONS
try try
@ -94,7 +106,7 @@ static bool run_test(test_runner* test)
test_runner::_memory_fail_threshold = 0; test_runner::_memory_fail_threshold = 0;
test_runner::_memory_fail_triggered = false; test_runner::_memory_fail_triggered = false;
pugi::set_memory_management_functions(custom_allocate, custom_deallocate); pugi::set_memory_management_functions(allocate, custom_deallocate);
#ifdef _MSC_VER #ifdef _MSC_VER
# pragma warning(push) # pragma warning(push)
@ -110,7 +122,7 @@ static bool run_test(test_runner* test)
if (result) if (result)
{ {
printf("Test %s failed: %s\n", test->_name, test_runner::_failure_message); printf("Test %s failed: %s\n", test_name, test_runner::_failure_message);
return false; return false;
} }
@ -118,13 +130,13 @@ static bool run_test(test_runner* test)
if (test_runner::_memory_fail_triggered) if (test_runner::_memory_fail_triggered)
{ {
printf("Test %s failed: unguarded memory fail triggered\n", test->_name); printf("Test %s failed: unguarded memory fail triggered\n", test_name);
return false; return false;
} }
if (g_memory_total_size != 0 || g_memory_total_count != 0) if (g_memory_total_size != 0 || g_memory_total_count != 0)
{ {
printf("Test %s failed: memory leaks found (%u bytes in %u allocations)\n", test->_name, static_cast<unsigned int>(g_memory_total_size), static_cast<unsigned int>(g_memory_total_count)); printf("Test %s failed: memory leaks found (%u bytes in %u allocations)\n", test_name, static_cast<unsigned int>(g_memory_total_size), static_cast<unsigned int>(g_memory_total_count));
return false; return false;
} }
@ -133,12 +145,12 @@ static bool run_test(test_runner* test)
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
printf("Test %s failed: exception %s\n", test->_name, e.what()); printf("Test %s failed: exception %s\n", test_name, e.what());
return false; return false;
} }
catch (...) catch (...)
{ {
printf("Test %s failed for unknown reason\n", test->_name); printf("Test %s failed for unknown reason\n", test_name);
return false; return false;
} }
#endif #endif
@ -176,7 +188,15 @@ int main(int, char** argv)
for (test = test_runner::_tests; test; test = test->_next) for (test = test_runner::_tests; test; test = test->_next)
{ {
total++; total++;
passed += run_test(test); passed += run_test(test, test->_name, custom_allocate);
#ifndef PUGIXML_NO_EXCEPTIONS
if (g_memory_fail_triggered)
{
total++;
passed += run_test(test, (test->_name + std::string(" (throw)")).c_str(), custom_allocate_throw);
}
#endif
} }
unsigned int failed = total - passed; unsigned int failed = total - passed;

View File

@ -143,9 +143,9 @@ struct dummy_fixture {};
#endif #endif
#ifdef PUGIXML_NO_EXCEPTIONS #ifdef PUGIXML_NO_EXCEPTIONS
#define CHECK_ALLOC_FAIL(code) CHECK(!test_runner::_memory_fail_triggered); code; CHECK(test_runner::_memory_fail_triggered); test_runner::_memory_fail_triggered = false #define CHECK_ALLOC_FAIL(code) do { CHECK(!test_runner::_memory_fail_triggered); code; CHECK(test_runner::_memory_fail_triggered); test_runner::_memory_fail_triggered = false; } while (test_runner::_memory_fail_triggered)
#else #else
#define CHECK_ALLOC_FAIL(code) CHECK(!test_runner::_memory_fail_triggered); try { code; } catch (std::bad_alloc&) {} CHECK(test_runner::_memory_fail_triggered); test_runner::_memory_fail_triggered = false #define CHECK_ALLOC_FAIL(code) do { CHECK(!test_runner::_memory_fail_triggered); try { code; } catch (std::bad_alloc&) {} CHECK(test_runner::_memory_fail_triggered); test_runner::_memory_fail_triggered = false; } while (test_runner::_memory_fail_triggered)
#endif #endif
#define STR(text) PUGIXML_TEXT(text) #define STR(text) PUGIXML_TEXT(text)

View File

@ -319,9 +319,7 @@ TEST(document_load_file_out_of_memory_file_leak)
pugi::xml_document doc; pugi::xml_document doc;
for (int i = 0; i < 256; ++i) for (int i = 0; i < 256; ++i)
{
CHECK_ALLOC_FAIL(CHECK(doc.load_file("tests/data/small.xml").status == status_out_of_memory)); CHECK_ALLOC_FAIL(CHECK(doc.load_file("tests/data/small.xml").status == status_out_of_memory));
}
test_runner::_memory_fail_threshold = 0; test_runner::_memory_fail_threshold = 0;
@ -329,6 +327,21 @@ TEST(document_load_file_out_of_memory_file_leak)
CHECK_NODE(doc, STR("<node />")); CHECK_NODE(doc, STR("<node />"));
} }
TEST(document_load_file_wide_out_of_memory_file_leak)
{
test_runner::_memory_fail_threshold = 256;
pugi::xml_document doc;
for (int i = 0; i < 256; ++i)
CHECK_ALLOC_FAIL(CHECK(doc.load_file(L"tests/data/small.xml").status == status_out_of_memory));
test_runner::_memory_fail_threshold = 0;
CHECK(doc.load_file(L"tests/data/small.xml"));
CHECK_NODE(doc, STR("<node />"));
}
TEST(document_load_file_error_previous) TEST(document_load_file_error_previous)
{ {
pugi::xml_document doc; pugi::xml_document doc;
@ -556,6 +569,26 @@ TEST_XML(document_save_file_wide_text, "<node/>")
CHECK(test_file_contents(f.path, "<node />\n", 9)); CHECK(test_file_contents(f.path, "<node />\n", 9));
} }
TEST_XML(document_save_file_leak, "<node/>")
{
temp_file f;
for (int i = 0; i < 256; ++i)
CHECK(doc.save_file(f.path));
}
TEST_XML(document_save_file_wide_leak, "<node/>")
{
temp_file f;
// widen the path
wchar_t wpath[sizeof(f.path)];
std::copy(f.path, f.path + strlen(f.path) + 1, wpath + 0);
for (int i = 0; i < 256; ++i)
CHECK(doc.save_file(wpath));
}
TEST(document_load_buffer) TEST(document_load_buffer)
{ {
const pugi::char_t text[] = STR("<?xml?><node/>"); const pugi::char_t text[] = STR("<?xml?><node/>");

View File

@ -905,7 +905,8 @@ TEST(dom_node_out_of_memory)
xml_attribute a = n.append_attribute(STR("a")); xml_attribute a = n.append_attribute(STR("a"));
CHECK(a); CHECK(a);
CHECK_ALLOC_FAIL(while (n.append_child(node_comment) || n.append_attribute(STR("b"))) { /* nop */ }); CHECK_ALLOC_FAIL(while (n.append_child(node_comment)) { /* nop */ });
CHECK_ALLOC_FAIL(while (n.append_attribute(STR("b"))) { /* nop */ });
// verify all node modification operations // verify all node modification operations
CHECK_ALLOC_FAIL(CHECK(!n.append_child())); CHECK_ALLOC_FAIL(CHECK(!n.append_child()));

View File

@ -935,6 +935,31 @@ TEST(parse_out_of_memory_conversion)
CHECK(!doc.first_child()); CHECK(!doc.first_child());
} }
TEST(parse_out_of_memory_allocator_state_sync)
{
const unsigned int count = 10000;
static char_t text[count * 4];
for (unsigned int i = 0; i < count; ++i)
{
text[4*i + 0] = '<';
text[4*i + 1] = 'n';
text[4*i + 2] = '/';
text[4*i + 3] = '>';
}
test_runner::_memory_fail_threshold = 65536;
xml_document doc;
CHECK_ALLOC_FAIL(CHECK(doc.load_buffer_inplace(text, count * 4).status == status_out_of_memory));
CHECK_NODE(doc.first_child(), STR("<n />"));
test_runner::_memory_fail_threshold = 0;
for (unsigned int i = 0; i < count; ++i)
CHECK(doc.append_child(STR("n")));
}
static bool test_offset(const char_t* contents, unsigned int options, pugi::xml_parse_status status, ptrdiff_t offset) static bool test_offset(const char_t* contents, unsigned int options, pugi::xml_parse_status status, ptrdiff_t offset)
{ {
xml_document doc; xml_document doc;