From 37f30448e4d755d20be4333a41f5348c3ee51134 Mon Sep 17 00:00:00 2001 From: JoaoMarcos44 <87440198+JoaoMarcos44@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:45:57 -0300 Subject: [PATCH] Fix GH-25108: Skip uninitialized hooked properties in json_encode() --- NEWS | 2 ++ ext/json/json_encoder.c | 4 +++ ext/json/tests/gh25108.phpt | 66 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 ext/json/tests/gh25108.phpt diff --git a/NEWS b/NEWS index 529f07daccb8..165aea1646bb 100644 --- a/NEWS +++ b/NEWS @@ -115,6 +115,8 @@ PHP NEWS INT_MIN. (Marc Bennewitz) - JSON: + . Fixed bug GH-25108 (json_encode() serializes uninitialized properties + with setter hooks as null). (JoaoMarcos44) . Fixed json_encode() leaving the recursion guard set on objects with hooked properties after running out of memory while escaping a property name. (adapik) diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index a6814c006958..100678260862 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -295,6 +295,10 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, zval_ptr_dtor(&tmp); return FAILURE; } + if (UNEXPECTED(data == &EG(uninitialized_zval))) { + zval_ptr_dtor(&tmp); + continue; + } } if (need_comma) { diff --git a/ext/json/tests/gh25108.phpt b/ext/json/tests/gh25108.phpt new file mode 100644 index 000000000000..17c1ceb167e1 --- /dev/null +++ b/ext/json/tests/gh25108.phpt @@ -0,0 +1,66 @@ +--TEST-- +GH-25108 (json_encode() skips uninitialized hooked properties) +--FILE-- + $value; + } + public ?int $hookedNullable { + set => $value; + } +} + +class MixedProperties +{ + public int $firstMissing { + set => $value; + } + public string $first = 'first'; + public ?string $middleMissing { + set => $value; + } + public ?string $getterNull { + get => null; + } + public ?string $backedNull = null { + set => $value; + } + public int $backedValue = 42 { + set => $value; + } + public string $last = 'last'; + public int $lastMissing { + set => $value; + } + public ?string $virtualSetOnly { + set {} + } +} + +$empty = new UninitializedProperties(); +echo json_encode($empty), "\n"; + +$empty->hooked = 7; +$empty->hookedNullable = null; +echo json_encode($empty), "\n"; + +$mixed = new MixedProperties(); +echo json_encode($mixed), "\n"; +echo json_encode($mixed, JSON_PRETTY_PRINT), "\n"; + +?> +--EXPECT-- +{} +{"hooked":7,"hookedNullable":null} +{"first":"first","getterNull":null,"backedNull":null,"backedValue":42,"last":"last"} +{ + "first": "first", + "getterNull": null, + "backedNull": null, + "backedValue": 42, + "last": "last" +}