Skip to content

Commit 41ff2db

Browse files
committed
Fix GH-24743: var_export() ignores exception from lazy object init
1 parent 47d5c0c commit 41ff2db

3 files changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
--TEST--
2+
GH-24743 (var_export() does not handle exception correctly when retrieving object)
3+
--FILE--
4+
<?php
5+
6+
class Inner {
7+
public static bool $fail = true;
8+
9+
public int $x = 1 {
10+
get {
11+
if (self::$fail) {
12+
throw new Exception("hook failed");
13+
}
14+
return $this->x;
15+
}
16+
}
17+
}
18+
19+
class Outer {
20+
public Inner $a;
21+
public int $b = 2;
22+
23+
public function __construct() {
24+
$this->a = new Inner;
25+
}
26+
}
27+
28+
$o = new Outer;
29+
30+
function show_export(string $label, callable $export): void
31+
{
32+
ob_start();
33+
$caught = null;
34+
try {
35+
$export();
36+
} catch (Throwable $e) {
37+
$caught = $e;
38+
}
39+
$output = ob_get_clean();
40+
41+
echo "--- ", $label, " ---\n";
42+
if ($caught === null) {
43+
echo "No exception\n";
44+
} else {
45+
echo "Caught: ", $caught->getMessage(), "\n";
46+
}
47+
48+
if ($output !== '') {
49+
echo "BUG: var_export() emitted partial output before the exception:\n";
50+
echo $output, "\n";
51+
echo "--- end of partial output ---\n";
52+
}
53+
}
54+
55+
show_export("print mode, nested object", fn() => var_export($o));
56+
show_export("return mode, nested object", fn() => var_export($o, true));
57+
show_export("print mode, object inside array", fn() => var_export([1, $o]));
58+
59+
echo "--- recovery: recursion guards were released ---\n";
60+
Inner::$fail = false;
61+
var_export($o);
62+
echo "\n";
63+
64+
?>
65+
--EXPECT--
66+
--- print mode, nested object ---
67+
Caught: hook failed
68+
--- return mode, nested object ---
69+
Caught: hook failed
70+
--- print mode, object inside array ---
71+
Caught: hook failed
72+
--- recovery: recursion guards were released ---
73+
\Outer::__set_state(array(
74+
'a' =>
75+
\Inner::__set_state(array(
76+
'x' => 1,
77+
)),
78+
'b' => 2,
79+
))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-24743 (var_export() does not handle exception correctly when retrieving object): lazy object initializer
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public int $a = 1;
8+
}
9+
10+
class O {
11+
public C $c;
12+
public int $b = 2;
13+
}
14+
15+
$o = new O;
16+
$o->c = (new ReflectionClass(C::class))->newLazyGhost(function ($obj) {
17+
throw new Exception("init failed");
18+
});
19+
20+
try {
21+
var_export($o);
22+
} catch (Exception $e) {
23+
echo $e->getMessage(), "\n";
24+
}
25+
26+
try {
27+
var_export($o->c);
28+
} catch (Exception $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
32+
?>
33+
--EXPECT--
34+
init failed
35+
init failed

‎ext/standard/var.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,11 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /*
635635
}
636636
ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj);
637637
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT);
638+
if (UNEXPECTED(EG(exception))) {
639+
ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj);
640+
zend_release_properties(myht);
641+
return FAILURE;
642+
}
638643
if (level > 1) {
639644
smart_str_appendc(buf, '\n');
640645
buffer_append_spaces(buf, level - 1);

0 commit comments

Comments
 (0)