[PHP-CVS] [php-src] PHP-8.4: Free the hooked property value when json_encode() sees an exception

[email protected] (Ilia Alshanetsky) Thu, 30 Jul 2026 12:49:15 +0000
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-30T08:21:16-04:00

Commit: https://github.com/php/php-src/commit/c35253d364c1c1c75ad9dbc4c8acbbdf239ffec1
Raw diff: https://github.com/php/php-src/commit/c35253d364c1c1c75ad9dbc4c8acbbdf239ffec1.diff

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.

Closes GH-22919

Changed paths:
  A  ext/json/tests/json_encode_hooked_property_throwing_getter.phpt
  M  ext/json/json_encoder.c


Diff:

diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c
index 4145bc2b6154..cefee25de90a 100644
--- a/ext/json/json_encoder.c
+++ b/ext/json/json_encoder.c
@@ -287,6 +287,7 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options,
 						if (EG(exception)) {
 							PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc);
 							zend_release_properties(prop_ht);
+							zval_ptr_dtor(&tmp);
 							return FAILURE;
 						}
 					}
diff --git a/ext/json/tests/json_encode_hooked_property_throwing_getter.phpt b/ext/json/tests/json_encode_hooked_property_throwing_getter.phpt
new file mode 100644
index 000000000000..63fc8cf42c0e
--- /dev/null
+++ b/ext/json/tests/json_encode_hooked_property_throwing_getter.phpt
@@ -0,0 +1,44 @@
+--TEST--
+json_encode() releases the hooked property value when the get hook throws
+--FILE--
+<?php
+
+class Value
+{
+    public function __destruct()
+    {
+        echo "Value::__destruct\n";
+    }
+}
+
+class ThrowOnFree
+{
+    public function __destruct()
+    {
+        throw new Exception('thrown while freeing the get hook frame');
+    }
+}
+
+class Container
+{
+    public $hooked {
+        get {
+            $local = new ThrowOnFree();
+            return new Value();
+        }
+    }
+}
+
+try {
+    json_encode(new Container());
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+echo "done\n";
+
+?>
+--EXPECT--
+Value::__destruct
+Exception: thrown while freeing the get hook frame
+done