diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 078a39c..ce47fce 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -7758,6 +7758,28 @@ PUGI__NS_BEGIN } } + PUGI__FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) + { + switch (rhs->type()) + { + case xpath_type_node_set: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_number: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_string: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_boolean: + return lhs->set(static_cast(rhs)->value); + + default: + assert(!"Invalid variable type"); + return false; + } + } + 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) { size_t length = static_cast(end - begin); @@ -11277,7 +11299,46 @@ namespace pugi } } - PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const + PUGI__FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + _data[i] = 0; + + _assign(rhs); + } + + PUGI__FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) + { + if (this == &rhs) return *this; + + _assign(rhs); + + return *this; + } + + PUGI__FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) + { + xpath_variable_set temp; + + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + if (rhs._data[i] && !_clone(rhs._data[i], &temp._data[i])) + return; + + _swap(temp); + } + + PUGI__FN void xpath_variable_set::_swap(xpath_variable_set& rhs) + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + xpath_variable* chain = _data[i]; + + _data[i] = rhs._data[i]; + rhs._data[i] = chain; + } + } + + PUGI__FN xpath_variable* xpath_variable_set::_find(const char_t* name) const { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; @@ -11290,6 +11351,33 @@ namespace pugi return 0; } + PUGI__FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) + { + xpath_variable* last = 0; + + while (var) + { + // allocate storage for new variable + xpath_variable* nvar = impl::new_xpath_variable(var->_type, var->name()); + if (!nvar) return false; + + // link the variable to the result immediately to handle failures gracefully + if (last) + last->_next = nvar; + else + *out_result = nvar; + + last = nvar; + + // copy the value; this can fail due to out-of-memory conditions + if (!impl::copy_xpath_variable(nvar, var)) return false; + + var = var->_next; + } + + return true; + } + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); @@ -11339,12 +11427,12 @@ namespace pugi PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) { - return find(name); + return _find(name); } PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const { - return find(name); + return _find(name); } PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) diff --git a/src/pugixml.hpp b/src/pugixml.hpp index a7154f1..b8b8946 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -1075,17 +1075,22 @@ namespace pugi private: xpath_variable* _data[64]; - // Non-copyable semantics - xpath_variable_set(const xpath_variable_set&); - xpath_variable_set& operator=(const xpath_variable_set&); + void _assign(const xpath_variable_set& rhs); + void _swap(xpath_variable_set& rhs); - xpath_variable* find(const char_t* name) const; + xpath_variable* _find(const char_t* name) const; + + static bool _clone(xpath_variable* var, xpath_variable** out_result); public: // Default constructor/destructor xpath_variable_set(); ~xpath_variable_set(); + // Copy constructor/assignment operator + xpath_variable_set(const xpath_variable_set& rhs); + xpath_variable_set& operator=(const xpath_variable_set& rhs); + // Add a new variable or get the existing one, if the types match xpath_variable* add(const char_t* name, xpath_value_type type); diff --git a/tests/test_xpath_variables.cpp b/tests/test_xpath_variables.cpp index 0d33312..b2ebc47 100644 --- a/tests/test_xpath_variables.cpp +++ b/tests/test_xpath_variables.cpp @@ -413,4 +413,64 @@ TEST_XML(xpath_variables_count_sum, "122334") +{ + xpath_variable_set set1; + set1.set(STR("a"), true); + set1.set(STR("b"), 2.0); + set1.set(STR("c"), STR("string")); + set1.set(STR("d"), doc.select_nodes(STR("//*"))); + + CHECK_XPATH_STRING_VAR(xml_node(), STR("substring($c, count($d[$a]) + $b)"), &set1, STR("ring")); + + xpath_variable_set set2 = set1; + + CHECK_XPATH_STRING_VAR(xml_node(), STR("substring($c, count($d[$a]) + $b)"), &set2, STR("ring")); + + xpath_variable_set set3; + + CHECK(!set3.get(STR("a"))); + + set3 = set1; + + CHECK_XPATH_STRING_VAR(xml_node(), STR("substring($c, count($d[$a]) + $b)"), &set2, STR("ring")); + + set3 = set3; + + CHECK_XPATH_STRING_VAR(xml_node(), STR("substring($c, count($d[$a]) + $b)"), &set2, STR("ring")); + + set3 = xpath_variable_set(); + + CHECK(!set3.get(STR("a"))); +} + +TEST_XML(xpath_variables_copy_out_of_memory, "") +{ + xpath_variable_set set1; + set1.set(STR("a"), true); + set1.set(STR("b"), 2.0); + set1.set(STR("c"), STR("string")); + set1.set(STR("d"), doc.select_nodes(STR("//*"))); + + xpath_variable_set set2 = set1; + + test_runner::_memory_fail_threshold = 32768 + 75 * sizeof(void*); + + CHECK_ALLOC_FAIL(xpath_variable_set set3 = set1); + + xpath_variable_set set4; + + CHECK_ALLOC_FAIL(set4 = set1); + CHECK(!set4.get(STR("a")) && !set4.get(STR("b")) && !set4.get(STR("c")) && !set4.get(STR("d"))); + + CHECK_ALLOC_FAIL(set2 = set1); + + CHECK(set2.get(STR("a")) && set2.get(STR("b")) && set2.get(STR("c")) && set2.get(STR("d"))); + + CHECK(set2.get(STR("a"))->get_boolean() == true); + CHECK(set2.get(STR("b"))->get_number() == 2.0); + CHECK_STRING(set2.get(STR("c"))->get_string(), STR("string")); + CHECK(set2.get(STR("d"))->get_node_set().size() == 1); +} #endif