Skip to content

Commit 56e50e4

Browse files
committed
Free the hooked property value when json_encode() sees an exception
php_json_encode_array() reads a hooked property into a local zval and bails out when an exception is pending. The get hook can publish its return value into that zval and then throw while its frame is freed, for example from the destructor of one of its locals, so the bailout leaks the value until request shutdown. Destroy it on the way out, the way the other failure exit already does.
1 parent 2804d09 commit 56e50e4

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

ext/json/json_encoder.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options,
287287
if (EG(exception)) {
288288
PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc);
289289
zend_release_properties(prop_ht);
290+
zval_ptr_dtor(&tmp);
290291
return FAILURE;
291292
}
292293
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
json_encode() releases the hooked property value when the get hook throws
3+
--FILE--
4+
<?php
5+
6+
class Value
7+
{
8+
public function __destruct()
9+
{
10+
echo "Value::__destruct\n";
11+
}
12+
}
13+
14+
class ThrowOnFree
15+
{
16+
public function __destruct()
17+
{
18+
throw new Exception('thrown while freeing the get hook frame');
19+
}
20+
}
21+
22+
class Container
23+
{
24+
public $hooked {
25+
get {
26+
$local = new ThrowOnFree();
27+
return new Value();
28+
}
29+
}
30+
}
31+
32+
try {
33+
json_encode(new Container());
34+
} catch (Throwable $e) {
35+
echo $e::class, ': ', $e->getMessage(), "\n";
36+
}
37+
38+
echo "done\n";
39+
40+
?>
41+
--EXPECT--
42+
Value::__destruct
43+
Exception: thrown while freeing the get hook frame
44+
done

0 commit comments

Comments
 (0)