Skip to content

ext/json: Stop encoding after a key fails to encode - #24123

Closed
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/aph-json-encoder-key-escape-success-ba2c-84-work
Closed

iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/aph-json-encoder-key-escape-success-ba2c-84-work

Conversation

@iliaal

@iliaal iliaal commented Oct 4, 2026

Copy link
Copy Markdown
Member

When an array or object key failed to encode, json_encode() carried on with the remaining values unless JSON_PARTIAL_OUTPUT_ON_ERROR was set, so jsonSerialize() still ran on later values and a later error could replace the one that caused the failure. Both key sites now bail out the same way a failed value already does; partial output is unchanged.

@anun333

anun333 commented Oct 29, 2026

Copy link
Copy Markdown

Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.

Tested at this PR's head (7a0db18326) against today's PHP-8.4 (303bb88f1c5), debug builds. $bad = "k\xff", Probe a JsonSerializable that records its calls:

json_encode() of PHP-8.4 this PR
[$bad => 1, 'b' => new Probe(1)] false, Malformed UTF-8; jsonSerialize() called false, Malformed UTF-8; not called
[$bad => 1, 'b' => new Probe(NAN)] false, Inf and NaN false, Malformed UTF-8
the same, JSON_THROW_ON_ERROR JsonException code 7 code 5
the same, JSON_PARTIAL_OUTPUT_ON_ERROR {"":1,"b":0}, Inf and NaN the same
an object with property $bad, then NAN false, Inf and NaN false, Malformed UTF-8
a bad key inside one jsonSerialize() result, then NAN false, Inf and NaN; both called false, Malformed UTF-8; only the first called
bad key with JSON_INVALID_UTF8_SUBSTITUTE / _IGNORE {"k�":1,"b":1} / {"k":1,"b":1} the same
a bad value, then NAN (control) false, Malformed UTF-8; not called the same

So keys now stop the encoding the way values already did, and the error reported is the one that caused the failure. ext/json/tests passes at the head (90 tests), and the new test fails on PHP-8.4 without the fix.

It no longer applies to today's PHP-8.4, though: ext/json/json_encoder.c conflicts with 3584a98bda0, which changed the same two key sites, so it needs a rebase.

How this was tested

Linux x86_64, gcc 13.3, debug builds of origin/PHP-8.4 and of this PR's head as it stands (it doesn't cherry-pick onto today's branch):

./buildconf --force
./configure --enable-debug --disable-cgi --disable-phpdbg --disable-fileinfo --enable-pcntl --enable-zend-test --enable-fpm --with-curl --enable-mbstring --disable-mbregex --with-mysqli --with-pdo-mysql
make -j4
class Probe implements JsonSerializable {
    static array $calls = [];
    function __construct(public string $name, public mixed $value = 1) {}
    function jsonSerialize(): mixed { self::$calls[] = $this->name; return $this->value; }
}
// e.g. json_encode(["k\xff" => 1, 'b' => new Probe('b', NAN)]), then json_last_error_msg() and Probe::$calls

Suites: TEST_PHP_EXECUTABLE=$PWD/sapi/cli/php sapi/cli/php run-tests.php -n -q -j4 ext/json/tests on both; the new test copied into the PHP-8.4 tree for the check that it fails there.

@bukka

bukka commented Oct 30, 2026

Copy link
Copy Markdown
Member

I think the change makes sense - it needs rebase though...

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.
@iliaal
iliaal force-pushed the fix/aph-json-encoder-key-escape-success-ba2c-84-work branch from 7a0db18 to efa0ccf Compare October 31, 2026 13:26
@iliaal iliaal closed this in de6ffeb Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants