From 75217c16c89639fe3b01e43d1cdc4a701063347d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:05:45 +0200 Subject: [PATCH] Fix GH-19801: address leak when calling var_dump() with recursion in __debugInfo() (#19837) --- NEWS | 2 ++ .../debug_zval_dump_gh19801_memory_leak.phpt | 32 +++++++++++++++++++ .../var_dump_gh19801_memory_leak.phpt | 30 +++++++++++++++++ ext/standard/var.c | 4 +-- 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/general_functions/debug_zval_dump_gh19801_memory_leak.phpt create mode 100644 ext/standard/tests/general_functions/var_dump_gh19801_memory_leak.phpt diff --git a/NEWS b/NEWS index 0891acce266bf..b3cdd047e509f 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,8 @@ PHP NEWS . Fixed bug GH-12265 (Cloning an object breaks serialization recursion). (nielsdos) . Fixed bug GH-19701 (Serialize/deserialize loses some data). (nielsdos) + . Fixed bug GH-19801 (leaks in var_dump() and debug_zval_dump()). + (alexandre-daubois) - Zip: . Fixed bug GH-19688 (Remove pattern overflow in zip addGlob()). (nielsdos) diff --git a/ext/standard/tests/general_functions/debug_zval_dump_gh19801_memory_leak.phpt b/ext/standard/tests/general_functions/debug_zval_dump_gh19801_memory_leak.phpt new file mode 100644 index 0000000000000..8d65cd6d18587 --- /dev/null +++ b/ext/standard/tests/general_functions/debug_zval_dump_gh19801_memory_leak.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-19801 (debug_zval_dump() leak with __debugInfo() that modifies circular references) +--FILE-- +a = null; + gc_collect_cycles(); + return []; + } + }, +]; + +$b = new stdClass; +$b->a = &$a; + +debug_zval_dump($b); +?> +--EXPECTF-- +object(stdClass)#2 (1) refcount(%d){ + ["a"]=> + reference refcount(%d) { + array(1) refcount(%d){ + [0]=> + object(class@anonymous)#1 (0) refcount(%d){ + } + } + } +} diff --git a/ext/standard/tests/general_functions/var_dump_gh19801_memory_leak.phpt b/ext/standard/tests/general_functions/var_dump_gh19801_memory_leak.phpt new file mode 100644 index 0000000000000..f0522916de4e6 --- /dev/null +++ b/ext/standard/tests/general_functions/var_dump_gh19801_memory_leak.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-19801 (var_dump() memory leak with __debugInfo() that modifies circular references) +--FILE-- +a = null; + gc_collect_cycles(); + return []; + } + }, +]; + +$b = new stdClass; +$b->a = &$a; + +var_dump($b); +?> +--EXPECTF-- +object(stdClass)#2 (1) { + ["a"]=> + &array(1) { + [0]=> + object(class@anonymous)#1 (0) { + } + } +} diff --git a/ext/standard/var.c b/ext/standard/var.c index c6e280d15ab93..795fb366c6013 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -139,7 +139,7 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ } ZEND_HASH_FOREACH_END(); if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { GC_UNPROTECT_RECURSION(myht); - GC_DELREF(myht); + GC_DTOR_NO_REF(myht); } if (level > 1) { php_printf("%*c", level-1, ' '); @@ -336,7 +336,7 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */ } ZEND_HASH_FOREACH_END(); if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { GC_UNPROTECT_RECURSION(myht); - GC_DELREF(myht); + GC_DTOR_NO_REF(myht); } if (level > 1) { php_printf("%*c", level - 1, ' ');