From efa0ccfea5942ea8a9d3737f9ef61c8cfef50bb7 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 24 Sep 2026 19:10:44 -0400 Subject: [PATCH] ext/json: Stop encoding after a key fails to encode A failure to escape an array or object key was ignored unless JSON_PARTIAL_OUTPUT_ON_ERROR was set, so encoding went on: later values still ran user code such as jsonSerialize(), and a later error replaced the one that was reported, e.g. a NAN value after an invalid UTF-8 key reported "Inf and NaN cannot be JSON encoded". Both key sites, the declared-property loop and the property table loop, now return FAILURE the same way a failed value does. --- NEWS | 2 + ext/json/json_encoder.c | 8 +-- .../tests/json_encode_invalid_key_aborts.phpt | 55 +++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 ext/json/tests/json_encode_invalid_key_aborts.phpt diff --git a/NEWS b/NEWS index 328c133c640e..ebafe2f65724 100644 --- a/NEWS +++ b/NEWS @@ -114,6 +114,8 @@ PHP NEWS (adapik) . Fixed json_encode() silently narrowing $depth values outside the int range, which wrapped to an unrelated depth. (cuishuang) + . Fixed json_encode() continuing to encode after an array or object key + failed to encode. (Ilia Alshanetsky) - LDAP: . Fix crash in ldap_exop_sync() when $response_data is omitted. (lacatoire) diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index a6814c006958..cfef043ddf66 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -187,11 +187,11 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, if (php_json_escape_string(buf, ZSTR_VAL(prop_info->name), ZSTR_LEN(prop_info->name), options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE) { - if (ZEND_HAS_OOM()) { + if (ZEND_HAS_OOM() || !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { PHP_JSON_HASH_UNPROTECT_RECURSION(obj); return FAILURE; } - if ((options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) && buf->s) { + if (buf->s) { ZEND_ASSERT(ZSTR_LEN(buf->s) >= 4); ZSTR_LEN(buf->s) -= 4; smart_str_appendl(buf, "\"\"", 2); @@ -308,13 +308,13 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE) { - if (ZEND_HAS_OOM()) { + if (ZEND_HAS_OOM() || !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { PHP_JSON_HASH_UNPROTECT_RECURSION(recursion_rc); zend_release_properties(prop_ht); zval_ptr_dtor(&tmp); return FAILURE; } - if ((options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) && buf->s) { + if (buf->s) { ZEND_ASSERT(ZSTR_LEN(buf->s) >= 4); ZSTR_LEN(buf->s) -= 4; smart_str_appendl(buf, "\"\"", 2); diff --git a/ext/json/tests/json_encode_invalid_key_aborts.phpt b/ext/json/tests/json_encode_invalid_key_aborts.phpt new file mode 100644 index 000000000000..76afc5be43f3 --- /dev/null +++ b/ext/json/tests/json_encode_invalid_key_aborts.phpt @@ -0,0 +1,55 @@ +--TEST-- +json_encode() stops encoding after an object or array key fails to encode +--FILE-- +{"\xff"} = 1; +$dynamic->next = new Value; + +$declared = new Declared; +$declared->next = new Value; + +$cases = [ + 'array' => ["\xff" => 1, 'next' => new Value], + 'array, later NAN' => ["\xff" => 1, 'next' => NAN], + 'dynamic property' => $dynamic, + 'declared property' => $declared, +]; + +foreach ($cases as $name => $value) { + echo "$name:\n"; + var_dump(json_encode($value), json_last_error_msg()); +} + +echo "partial output:\n"; +var_dump(json_encode(["\xff" => 1, 'next' => 2], JSON_PARTIAL_OUTPUT_ON_ERROR), json_last_error_msg()); + +?> +--EXPECT-- +array: +bool(false) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +array, later NAN: +bool(false) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +dynamic property: +bool(false) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +declared property: +bool(false) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +partial output: +string(15) "{"":1,"next":2}" +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"