From 32774987aa6e32389f76555fe967cad7f5c1a2e2 Mon Sep 17 00:00:00 2001 From: "Alexey (Holyfoot) Botchkov" Date: Wed, 22 Jul 2026 12:48:27 +0300 Subject: [PATCH] MDEV-40394 XML schema fails on self-referencing type. Copy XMLSchema_user_type information when the type is used recursively. Copies that were once created are stored in m_c_free and then reused later to avoid memory issues. --- plugin/type_xmltype/item_func_xml_isvalid.cc | 314 ++++++++++++++---- .../type_xmltype_validation.result | 193 +++++++++++ .../type_xmltype/type_xmltype_validation.test | 188 +++++++++++ 3 files changed, 633 insertions(+), 62 deletions(-) diff --git a/plugin/type_xmltype/item_func_xml_isvalid.cc b/plugin/type_xmltype/item_func_xml_isvalid.cc index 48be37d84358b..abb449ec7979a 100644 --- a/plugin/type_xmltype/item_func_xml_isvalid.cc +++ b/plugin/type_xmltype/item_func_xml_isvalid.cc @@ -127,6 +127,7 @@ class XMLSchema_item: public Sql_alloc { public: XMLSchema_item(const XMLSchema_item &)= delete; + void operator=(const XMLSchema_item &) = delete; XMLSchema_item() {} @@ -182,7 +183,7 @@ class XMLSchema_item: public Sql_alloc return VTN_CONTINUE; } - virtual bool is_validate_done() { return true; } + virtual bool end_validation() { return true; } virtual bool validate_value(MY_XML_VALIDATION_DATA *st, @@ -206,7 +207,6 @@ class XMLSchema_item: public Sql_alloc { return MY_XML_ERROR; } - int validate_failed(MY_XML_VALIDATION_DATA *st); class XMLSchema_item *m_next; @@ -303,6 +303,11 @@ class XMLSchema_tag_attribute: public XMLSchema_item { return len == m_val_len && memcmp(m_val, name, len) == 0; } + void copy_attribute(XMLSchema_tag_attribute *c) const + { + c->m_val= m_val; + c->m_val_len= m_val_len; + } }; @@ -332,6 +337,10 @@ class XMLSchema_tag_integer_attribute: public XMLSchema_tag_attribute return m_error ? MY_XML_ERROR : MY_XML_OK; } + void copy_int_attribute(XMLSchema_tag_integer_attribute *c) const + { + c->m_value_int= m_value_int; + } }; @@ -494,7 +503,6 @@ class XMLSchema_tag: public XMLSchema_item public: XMLSchema_tag_attribute *m_tag_attributes; XMLSchema_tag_attribute m_id; /* eveny tag in schema has the "id" attr */ - XMLSchema_tag *m_next_tag; XMLSchema_annotation *m_annotation; bool declare_attribute(XMLSchema_tag_attribute *attr) @@ -518,6 +526,17 @@ class XMLSchema_tag: public XMLSchema_item { st->push(this); } + /* + If the tag has any runtime data, it should be able to + create it's copy. + Otherwise we return NULL and the same instance + will be recycled. + */ + virtual XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) + { + return NULL; + } + }; @@ -584,7 +603,6 @@ class XMLSchema_attribute: public XMLSchema_tag bool resolve_type(MY_XML_VALIDATION_DATA *st, LEX_CSTRING *bad_type) override; - bool check_type() override; void validate_prepare() override; bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; @@ -1272,7 +1290,7 @@ class XMLSchema_any_type: public XMLSchema_type st->push(&st->annotation); return VTN_ACCEPTED; } - bool is_validate_done() override + bool end_validation() override { return true; } @@ -1307,17 +1325,30 @@ class XMLSchema_simple_builtin_type: public XMLSchema_type class XMLSchema_user_type: public XMLSchema_type { protected: + class Compositor_stack_node : public Sql_alloc + { + public: + XMLSchema_tag *m_compositor; + Compositor_stack_node *m_next; + }; + Compositor_stack_node *m_c_stack; + Compositor_stack_node *m_c_free; + XMLSchema_schema *m_schema; XMLSchema_tag_attribute m_type_name; XMLSchema_tag_attribute m_final; + int m_in_validate; + MY_XML_VALIDATION_DATA *m_st; public: XMLSchema_tag *m_compositor; XMLSchema_user_type *m_next_type; XMLSchema_user_type(XMLSchema_schema *schema): XMLSchema_type(), + m_c_stack(NULL), m_c_free(NULL), m_schema(schema), m_type_name(&xs_name), m_final(&xs_final), + m_in_validate(0), m_compositor(NULL) { declare_attribute(&m_type_name); @@ -1329,6 +1360,22 @@ class XMLSchema_user_type: public XMLSchema_type { return m_compositor->check_type(); } + void reset_type() + { + /* + If validation fails and returns before the parsing ends, + that data is in 'hot' state. + So we have to free the m_c_stack and reset the m_in_validate. + */ + m_in_validate= false; + while (m_c_stack) + { + Compositor_stack_node *tmp= m_c_stack; + m_c_stack= m_c_stack->m_next; + tmp->m_next= m_c_free; + m_c_free= tmp; + } + } void get_type_name(LEX_CSTRING *name) const { name->str= m_type_name.m_val; @@ -1340,11 +1387,56 @@ class XMLSchema_user_type: public XMLSchema_type } void validate_prepare() override { + if (m_in_validate) + { + Compositor_stack_node *csn; + XMLSchema_tag *comp; + + /* try to recycle already alloced compositor first. */ + if (m_c_free) + { + csn= m_c_free; + m_c_free= csn->m_next; + comp= csn->m_compositor; + } + else + { + csn= new(m_st->mem_root) Compositor_stack_node; + if (!(comp= m_compositor->get_copy(m_st))) + comp= m_compositor; + + if (!csn || !comp) + goto do_validate; + } + + csn->m_next= m_c_stack; + m_c_stack= csn; + csn->m_compositor= m_compositor; + m_compositor= comp; + } + +do_validate: + m_in_validate++; m_compositor->validate_prepare(); } - bool is_validate_done() override + bool end_validation() override { - return m_compositor->is_validate_done(); + bool res; + + m_in_validate--; + res= m_compositor->end_validation(); + if (m_in_validate) + { + /* pop the compositor from the stack. */ + Compositor_stack_node *c_node= m_c_stack; + m_c_stack= c_node->m_next; + XMLSchema_tag *cur= m_compositor; + m_compositor= c_node->m_compositor; + c_node->m_compositor= cur; + c_node->m_next= m_c_free; + m_c_free= c_node; + } + return res; } bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1678,34 +1770,28 @@ class XMLSchema_all: public XMLSchema_tag protected: XMLSchema_tag_integer_attribute m_minOccurs; XMLSchema_tag_unbounded_integer_attribute m_maxOccurs; - XMLSchema_tag **m_tags_hook; public: int m_counter; - XMLSchema_tag *m_tags; + List m_tags_list; XMLSchema_all(): XMLSchema_tag(), m_minOccurs(&xs_minOccurs), - m_maxOccurs(&xs_maxOccurs), - m_tags_hook(&m_tags), m_tags(NULL) + m_maxOccurs(&xs_maxOccurs) { declare_attribute(&m_minOccurs); declare_attribute(&m_maxOccurs); } - void append_tag(XMLSchema_tag *tag); + void append_tag(XMLSchema_tag *tag, MEM_ROOT *r); bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; - bool leave(MY_XML_VALIDATION_DATA *st, - const char *attr, size_t len) override - { - *m_tags_hook= NULL; - return XMLSchema_tag::leave(st, attr, len); - } bool check_type() override { - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + while ((cur= it++)) { if(cur->check_type()) return TRUE; @@ -1714,16 +1800,22 @@ class XMLSchema_all: public XMLSchema_tag } void validate_prepare() override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + m_counter= 0; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + + while ((cur= it++)) cur->validate_prepare(); } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; enum vtn_result tn_result; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + while ((cur= it++)) { if ((tn_result= cur->validate_tag_name(st, attr, len)) != VTN_CONTINUE) { @@ -1732,36 +1824,50 @@ class XMLSchema_all: public XMLSchema_tag return tn_result; } } - return is_validate_done() ? VTN_CONTINUE : VTN_ERROR; + return end_validation() ? VTN_CONTINUE : VTN_ERROR; } - bool is_validate_done() override + bool end_validation() override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + if (m_counter == 0) { return m_minOccurs.m_value_int == 0; } - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + while ((cur= it++)) { - if (!cur->is_validate_done()) + if (!cur->end_validation()) { return false; } } return true; } + void copy_tags(MY_XML_VALIDATION_DATA *st, XMLSchema_all *c); + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_all *c= new(st->mem_root) XMLSchema_all(); + if (!c) + return NULL; + copy_tags(st, c); + return c; + } }; class XMLSchema_sequence: public XMLSchema_all { XMLSchema_tag *m_cur_tag; + List_iterator_fast m_cur_list; public: bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; void validate_prepare() override { m_cur_tag= NULL; + m_cur_list.init(m_tags_list); m_counter= 0; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, @@ -1773,13 +1879,13 @@ class XMLSchema_sequence: public XMLSchema_all if (!m_cur_tag) { - if (!m_tags) + if (m_tags_list.is_empty()) return VTN_CONTINUE; turn_over: if (m_counter >= m_maxOccurs.m_value_int) return VTN_CONTINUE; - m_cur_tag= m_tags; + m_cur_tag= m_cur_list++; m_counter++; beginning= true; m_cur_tag->validate_prepare(); @@ -1788,7 +1894,7 @@ class XMLSchema_sequence: public XMLSchema_all while ((tn_result= m_cur_tag->validate_tag_name(st, attr, len)) == VTN_CONTINUE) { - if (!m_cur_tag->is_validate_done()) + if (!m_cur_tag->end_validation()) { /* If we already applied some elements of the sequence, @@ -1798,7 +1904,7 @@ class XMLSchema_sequence: public XMLSchema_all return beginning ? VTN_CONTINUE : VTN_ERROR; } - m_cur_tag= m_cur_tag->m_next_tag; + m_cur_tag= m_cur_list++; if (!m_cur_tag) { if (full_loop) @@ -1817,15 +1923,16 @@ class XMLSchema_sequence: public XMLSchema_all return tn_result; } - bool is_validate_done() override + bool end_validation() override { if (m_counter < m_minOccurs.m_value_int) return false; + while (m_cur_tag) { - if (!m_cur_tag->is_validate_done()) + if (!m_cur_tag->end_validation()) return false; - m_cur_tag= m_cur_tag->m_next_tag; + m_cur_tag= m_cur_list++; if (m_cur_tag) m_cur_tag->validate_prepare(); @@ -1833,6 +1940,14 @@ class XMLSchema_sequence: public XMLSchema_all return true; } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_sequence *c= new(st->mem_root) XMLSchema_sequence(); + if (!c) + return NULL; + copy_tags(st, c); + return c; + } }; @@ -1842,12 +1957,13 @@ class XMLSchema_choice: public XMLSchema_sequence public: void validate_prepare() override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + m_counter= 0; m_found= NULL; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) - { + while ((cur= it++)) cur->validate_prepare(); - } } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1859,7 +1975,7 @@ class XMLSchema_choice: public XMLSchema_sequence if ((tn_result= m_found->validate_tag_name(st, attr, len)) == VTN_CONTINUE) { - if (!m_found->is_validate_done()) + if (!m_found->end_validation()) return VTN_ERROR; m_found= NULL; } @@ -1871,42 +1987,57 @@ class XMLSchema_choice: public XMLSchema_sequence return VTN_CONTINUE; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) { - if ((tn_result= cur->validate_tag_name(st, attr, len)) == VTN_ACCEPTED) + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + + while ((cur= it++)) { - m_found= cur; - m_counter++; - return VTN_ACCEPTED; - } + if ((tn_result= cur->validate_tag_name(st, attr, len)) == VTN_ACCEPTED) + { + m_found= cur; + m_counter++; + return VTN_ACCEPTED; + } - if (tn_result == VTN_ERROR) - return VTN_ERROR; + if (tn_result == VTN_ERROR) + return VTN_ERROR; + } } return VTN_CONTINUE; } - bool is_validate_done() override + bool end_validation() override { if (m_found) { - if (!m_found->is_validate_done()) + if (!m_found->end_validation()) return false; } return m_counter >= m_minOccurs.m_value_int; } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_choice *c= new(st->mem_root) XMLSchema_choice(); + if (!c) + return NULL; + copy_tags(st, c); + return c; + } }; class XMLSchema_group_def: public XMLSchema_tag { public: + bool m_in_check_type; XMLSchema_tag *m_compositor; XMLSchema_tag_attribute m_atr_name; XMLSchema_group_def *m_next_group; XMLSchema_type *m_type; XMLSchema_group_def(): XMLSchema_tag(), + m_in_check_type(false), m_compositor(NULL), m_atr_name(&xs_name) { @@ -1916,6 +2047,18 @@ class XMLSchema_group_def: public XMLSchema_tag bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; bool leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; + bool check_type() override + { + bool res; + + if (m_in_check_type) + return TRUE; /* circular groups are not allowed. */ + + m_in_check_type= true; + res= m_compositor->check_type(); + m_in_check_type= false; + return res; + } }; @@ -1948,7 +2091,7 @@ class XMLSchema_group_reference: public XMLSchema_tag bool check_type() override { - return m_group->m_compositor->check_type(); + return m_group->check_type(); } void validate_prepare() override { @@ -1959,9 +2102,13 @@ class XMLSchema_group_reference: public XMLSchema_tag { return m_group->m_compositor->validate_tag_name(st, attr, len); } - bool is_validate_done() override + bool end_validation() override + { + return m_group->m_compositor->end_validation(); + } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override { - return m_group->m_compositor->is_validate_done(); + return m_group->m_compositor->get_copy(st); } }; @@ -2045,10 +2192,22 @@ class XMLSchema_element_local: public XMLSchema_element_global return VTN_CONTINUE; } - bool is_validate_done() override + bool end_validation() override { return m_counter >= m_atr_minOccurs.m_value_int; } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_element_local *c= new(st->mem_root) XMLSchema_element_local(); + if (!c) + return NULL; + + c->m_type= m_type; + m_atr_minOccurs.copy_int_attribute(&c->m_atr_minOccurs); + m_atr_maxOccurs.copy_int_attribute(&c->m_atr_maxOccurs); + m_atr_name.copy_attribute(&c->m_atr_name); + return c; + } }; @@ -2165,6 +2324,7 @@ class XMLSchema_schema: public XMLSchema_tag XMLSchema_type *find_simple_type(const char *name, size_t len) const; XMLSchema_type *find_complex_type(const char *name, size_t len) const; bool check_types(LEX_CSTRING *bad_type); + bool reset_types(); }; @@ -2401,6 +2561,7 @@ bool XMLSchema_tag_xmlns_attribute::value( bool XMLSchema_user_type::leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { + m_st= st; if (!m_compositor) m_compositor= &empty_compositor; return XMLSchema_type::leave(st, attr, len); @@ -2976,10 +3137,9 @@ bool XMLSchema_complexContent::enter_tag(MY_XML_VALIDATION_DATA *st, */ -void XMLSchema_all::append_tag(XMLSchema_tag *tag) +void XMLSchema_all::append_tag(XMLSchema_tag *tag, MEM_ROOT *r) { - *m_tags_hook= tag; - m_tags_hook= &tag->m_next_tag; + m_tags_list.push_back(tag, r); } @@ -2998,13 +3158,31 @@ bool XMLSchema_all::enter_tag(MY_XML_VALIDATION_DATA *st, if (def == NULL) return MY_XML_ERROR; /* OOM */ - append_tag(def); + append_tag(def, st->mem_root); st->push(def); return MY_XML_OK; } + +void XMLSchema_all::copy_tags(MY_XML_VALIDATION_DATA *st, XMLSchema_all *c) +{ + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + + while ((cur= it++)) + { + XMLSchema_tag *t; + if (!(t= cur->get_copy(st))) + t= cur; + c->append_tag(t, st->mem_root); + } + m_minOccurs.copy_int_attribute(&c->m_minOccurs); + m_maxOccurs.copy_int_attribute(&c->m_maxOccurs); +} + + bool XMLSchema_sequence::enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { @@ -3032,7 +3210,7 @@ bool XMLSchema_sequence::enter_tag(MY_XML_VALIDATION_DATA *st, if (def == NULL) return MY_XML_ERROR; /* OOM */ - append_tag(def); + append_tag(def, st->mem_root); st->push(def); return MY_XML_OK; @@ -3175,7 +3353,7 @@ bool XMLSchema_element_global::validate_leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { st->pop(); - return m_type->is_validate_done() ? MY_XML_OK : MY_XML_ERROR; + return m_type->end_validation() ? MY_XML_OK : MY_XML_ERROR; } @@ -3290,6 +3468,7 @@ XMLSchema_type *XMLSchema_schema::find_complex_type( return NULL; } + bool XMLSchema_schema::check_types(LEX_CSTRING *bad_type) { XMLSchema_user_type *t; @@ -3315,6 +3494,21 @@ bool XMLSchema_schema::check_types(LEX_CSTRING *bad_type) return FALSE; } + +bool XMLSchema_schema::reset_types() +{ + XMLSchema_user_type *t; + + for(t= m_global_complexTypes; t; t= t->m_next_type) + t->reset_type(); + + for(t= m_global_simpleTypes; t; t= t->m_next_type) + t->reset_type(); + + return FALSE; +} + + XMLSchema_type *XMLSchema_schema::find_simple_type_by_name( MY_XML_VALIDATION_DATA *st, const char *name, size_t len) const { @@ -3399,12 +3593,6 @@ bool XMLSchema_schema::validate_element(MY_XML_VALIDATION_DATA *st, } -bool XMLSchema_attribute::check_type() -{ - return m_type->check_type(); -} - - void XMLSchema_attribute::validate_prepare() { m_type->validate_prepare(); @@ -3675,6 +3863,8 @@ static int validate_schema(const String *xml, rc= my_xml_parse(&p, xml->ptr(), xml->length()) == MY_XML_OK; my_xml_parser_free(&p); + user_data->schema->reset_types(); + return rc; } diff --git a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result index c219da98a9ab2..baa3690231a69 100644 --- a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result +++ b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result @@ -2119,3 +2119,196 @@ SELECT XMLISVALID( 'x', ' ) AS r; ERROR HY000: Invalid XML schema, type A has bad circular references. + +MDEV-40394 XML schema fails on self-referencing type. + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as V; +V +1 +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_1; +Expect_1 +1 +SELECT XMLISVALID('', +' + + + + +') as V; +V +0 +SELECT XMLISVALID(' ', +' + + + + +') as V; +V +0 +SELECT XMLISVALID(' ', +' + + + + + + +') as V; +V +1 +SELECT XMLISVALID(' a ', +' + + + + + + + + + + + + + + ') AS v; +ERROR HY000: Invalid XML schema, type T has bad circular references. +SELECT XMLISVALID('io', +' + + + + + ') as A_expect_1; +A_expect_1 +1 +SELECT XMLISVALID('i1i2o1o2', +' + + + + + ') as B_expect_1; +B_expect_1 +1 +SELECT XMLISVALID('ab', +' + + + + + ') as C_expect_0; +C_expect_0 +0 +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_0; +Expect_0 +0 diff --git a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test index fea3f9dd59b76..95af685ac0b5c 100644 --- a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test +++ b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test @@ -2236,3 +2236,191 @@ SELECT XMLISVALID( 'x', ' ) AS r; +--echo +--echo MDEV-40394 XML schema fails on self-referencing type. +--echo + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as V; + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_1; + +SELECT XMLISVALID('', +' + + + + +') as V; + +SELECT XMLISVALID(' ', +' + + + + +') as V; + +SELECT XMLISVALID(' ', +' + + + + + + +') as V; + +--error ER_UNKNOWN_ERROR +SELECT XMLISVALID(' a ', + ' + + + + + + + + + + + + + + ') AS v; + +# Case A: recursive xs:all, valid doc, inner element name reused at parent. +SELECT XMLISVALID('io', +' + + + + + ') as A_expect_1; + +# Case B: recursive xs:sequence, element maxOccurs=2, inner uses budget. +SELECT XMLISVALID('i1i2o1o2', +' + + + + + ') as B_expect_1; + +# Case C: recursive xs:all, does it FALSE-ACCEPT a duplicate? +SELECT XMLISVALID('ab', +' + + + + + ') as C_expect_0; + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_0;