diff --git a/ext/xml/tests/gh12254.phpt b/ext/xml/tests/gh12254.phpt
new file mode 100644
index 00000000000..0ecc6a4ecc3
--- /dev/null
+++ b/ext/xml/tests/gh12254.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-12254: xml_parse_into_struct() memory leak when called twice
+--EXTENSIONS--
+xml
+--FILE--
+", $values, $tags));
+}, function ($parser, $name) {
+ echo "close\n";
+ var_dump($name);
+});
+xml_parse_into_struct($parser, "", $values, $tags);
+// Yes, this doesn't do anything but it at least shouldn't leak...
+xml_parse_into_struct($parser, "", $values, $tags);
+
+?>
+--EXPECTF--
+open
+string(9) "CONTAINER"
+array(0) {
+}
+
+Warning: xml_parse_into_struct(): Parser must not be called recursively in %s on line %d
+bool(false)
+close
+string(9) "CONTAINER"
diff --git a/ext/xml/xml.c b/ext/xml/xml.c
index 53e959b67b0..a7c6757d7a2 100644
--- a/ext/xml/xml.c
+++ b/ext/xml/xml.c
@@ -312,6 +312,16 @@ static zend_object *xml_parser_create_object(zend_class_entry *class_type) {
return &intern->std;
}
+static void xml_parser_free_ltags(xml_parser *parser)
+{
+ if (parser->ltags) {
+ int inx;
+ for (inx = 0; ((inx < parser->level) && (inx < XML_MAXLEVEL)); inx++)
+ efree(parser->ltags[ inx ]);
+ efree(parser->ltags);
+ }
+}
+
static void xml_parser_free_obj(zend_object *object)
{
xml_parser *parser = xml_parser_from_obj(object);
@@ -319,12 +329,7 @@ static void xml_parser_free_obj(zend_object *object)
if (parser->parser) {
XML_ParserFree(parser->parser);
}
- if (parser->ltags) {
- int inx;
- for (inx = 0; ((inx < parser->level) && (inx < XML_MAXLEVEL)); inx++)
- efree(parser->ltags[ inx ]);
- efree(parser->ltags);
- }
+ xml_parser_free_ltags(parser);
if (!Z_ISUNDEF(parser->startElementHandler)) {
zval_ptr_dtor(&parser->startElementHandler);
}
@@ -1255,6 +1260,11 @@ PHP_FUNCTION(xml_parse_into_struct)
parser = Z_XMLPARSER_P(pind);
+ if (parser->isparsing) {
+ php_error_docref(NULL, E_WARNING, "Parser must not be called recursively");
+ RETURN_FALSE;
+ }
+
if (info) {
info = zend_try_array_init(info);
if (!info) {
@@ -1274,15 +1284,12 @@ PHP_FUNCTION(xml_parse_into_struct)
}
parser->level = 0;
+ xml_parser_free_ltags(parser);
parser->ltags = safe_emalloc(XML_MAXLEVEL, sizeof(char *), 0);
XML_SetElementHandler(parser->parser, _xml_startElementHandler, _xml_endElementHandler);
XML_SetCharacterDataHandler(parser->parser, _xml_characterDataHandler);
- if (parser->isparsing) {
- php_error_docref(NULL, E_WARNING, "Parser must not be called recursively");
- RETURN_FALSE;
- }
parser->isparsing = 1;
ret = XML_Parse(parser->parser, (XML_Char*)data, data_len, 1);
parser->isparsing = 0;
diff --git a/ext/xml/xml.stub.php b/ext/xml/xml.stub.php
index 89385675f4b..7047814bd44 100644
--- a/ext/xml/xml.stub.php
+++ b/ext/xml/xml.stub.php
@@ -182,7 +182,7 @@ function xml_parse(XMLParser $parser, string $data, bool $is_final = false): int
* @param array $values
* @param array $index
*/
-function xml_parse_into_struct(XMLParser $parser, string $data, &$values, &$index = null): int {}
+function xml_parse_into_struct(XMLParser $parser, string $data, &$values, &$index = null): int|false {}
function xml_get_error_code(XMLParser $parser): int {}
diff --git a/ext/xml/xml_arginfo.h b/ext/xml/xml_arginfo.h
index abf34ac2501..48cd269fd24 100644
--- a/ext/xml/xml_arginfo.h
+++ b/ext/xml/xml_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: cd199a8733c51c8bb5970f86b7797ca91e6e59c6 */
+ * Stub hash: f87e295b35cd43db72a936ee5745297a45730090 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_xml_parser_create, 0, 0, XMLParser, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, encoding, IS_STRING, 1, "null")
@@ -46,7 +46,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_xml_parse, 0, 2, IS_LONG, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, is_final, _IS_BOOL, 0, "false")
ZEND_END_ARG_INFO()
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_xml_parse_into_struct, 0, 3, IS_LONG, 0)
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_xml_parse_into_struct, 0, 3, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_OBJ_INFO(0, parser, XMLParser, 0)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_ARG_INFO(1, values)