Commit a402e3645cd for php
commit a402e3645cd9f9c9b3e4ddf4e41cc156dc9f3382
Author: lazerg <lazerg2@gmail.com>
Date: Sat Nov 14 19:11:27 2026 +0800
Fix GH-24743: var_export() ignores exception from lazy object init (#24746)
Stop exporting when zend_get_properties_for() raises an exception while
initializing a lazy object. Release the recursion guard and properties
before returning FAILURE so var_export() does not emit partial output.
Add regression coverage for lazy initialization failures and retain coverage
for nested property hook exceptions, whose failure handling is already fixed.
Closes #24746
diff --git a/NEWS b/NEWS
index 004580cf218..7b2fd52737b 100644
--- a/NEWS
+++ b/NEWS
@@ -105,6 +105,8 @@ PHP NEWS
DNS responses. (Marti2203)
. Fixed bug GH-25387 (Uploaded files leak in request_parse_body()).
(Kévin Dunglas)
+ . Fixed bug GH-24743 (var_export() ignores exceptions from lazy object
+ initialization). (lazerg)
- Streams:
. Fixed bug GH-22063 (Stream filter chain UAF on self-removal during
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 00000000000..3cf841cb17f
--- /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--
+<?php
+
+class Inner {
+ public static bool $fail = true;
+
+ public int $x = 1 {
+ get {
+ if (self::$fail) {
+ throw new Exception("hook failed");
+ }
+ return $this->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 00000000000..fd57fe5f114
--- /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--
+<?php
+
+class C {
+ public int $a = 1;
+}
+
+class O {
+ public C $c;
+ public int $b = 2;
+}
+
+$o = new O;
+$o->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 6ce623bc2a4..dc398c5a598 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);