From 41ff2db76d4e00638d184253c654aacd9459d878 Mon Sep 17 00:00:00 2001 From: lazerg Date: Tue, 27 Oct 2026 17:27:01 +0500 Subject: [PATCH] Fix GH-24743: var_export() ignores exception from lazy object init --- .../tests/general_functions/gh24743_1.phpt | 79 +++++++++++++++++++ .../tests/general_functions/gh24743_2.phpt | 35 ++++++++ ext/standard/var.c | 5 ++ 3 files changed, 119 insertions(+) create mode 100644 ext/standard/tests/general_functions/gh24743_1.phpt create mode 100644 ext/standard/tests/general_functions/gh24743_2.phpt diff --git a/ext/standard/tests/general_functions/gh24743_1.phpt b/ext/standard/tests/general_functions/gh24743_1.phpt new file mode 100644 index 000000000000..3cf841cb17f4 --- /dev/null +++ b/ext/standard/tests/general_functions/gh24743_1.phpt @@ -0,0 +1,79 @@ +--TEST-- +GH-24743 (var_export() does not handle exception correctly when retrieving object) +--FILE-- +x; + } + } +} + +class Outer { + public Inner $a; + public int $b = 2; + + public function __construct() { + $this->a = new Inner; + } +} + +$o = new Outer; + +function show_export(string $label, callable $export): void +{ + ob_start(); + $caught = null; + try { + $export(); + } catch (Throwable $e) { + $caught = $e; + } + $output = ob_get_clean(); + + echo "--- ", $label, " ---\n"; + if ($caught === null) { + echo "No exception\n"; + } else { + echo "Caught: ", $caught->getMessage(), "\n"; + } + + if ($output !== '') { + echo "BUG: var_export() emitted partial output before the exception:\n"; + echo $output, "\n"; + echo "--- end of partial output ---\n"; + } +} + +show_export("print mode, nested object", fn() => var_export($o)); +show_export("return mode, nested object", fn() => var_export($o, true)); +show_export("print mode, object inside array", fn() => var_export([1, $o])); + +echo "--- recovery: recursion guards were released ---\n"; +Inner::$fail = false; +var_export($o); +echo "\n"; + +?> +--EXPECT-- +--- print mode, nested object --- +Caught: hook failed +--- return mode, nested object --- +Caught: hook failed +--- print mode, object inside array --- +Caught: hook failed +--- recovery: recursion guards were released --- +\Outer::__set_state(array( + 'a' => + \Inner::__set_state(array( + 'x' => 1, + )), + 'b' => 2, +)) diff --git a/ext/standard/tests/general_functions/gh24743_2.phpt b/ext/standard/tests/general_functions/gh24743_2.phpt new file mode 100644 index 000000000000..fd57fe5f114e --- /dev/null +++ b/ext/standard/tests/general_functions/gh24743_2.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-24743 (var_export() does not handle exception correctly when retrieving object): lazy object initializer +--FILE-- +c = (new ReflectionClass(C::class))->newLazyGhost(function ($obj) { + throw new Exception("init failed"); +}); + +try { + var_export($o); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_export($o->c); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +init failed +init failed diff --git a/ext/standard/var.c b/ext/standard/var.c index 6ce623bc2a4a..dc398c5a598b 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -635,6 +635,11 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* } ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj); myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT); + if (UNEXPECTED(EG(exception))) { + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); + zend_release_properties(myht); + return FAILURE; + } if (level > 1) { smart_str_appendc(buf, '\n'); buffer_append_spaces(buf, level - 1);