php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #70275 On recursion error, json_encode can eat up all system memory
Submitted: 2015-08-14 23:14 UTC Modified: 2016-08-29 14:06 UTC
From: royanee at gmail dot com Assigned: bukka (profile)
Status: Closed Package: JSON related
PHP Version: 5.6.12 OS:
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: royanee at gmail dot com
New email:
PHP Version: OS:

 

 [2015-08-14 23:14 UTC] royanee at gmail dot com
Description:
------------
When json_encode encounters recursion or other errors it will return false (as long as you are not using the largely undocumented constant JSON_PARTIAL_OUTPUT_ON_ERROR).

Unfortunately, it waits until after it has processed all of the data to return the error. This was a problem for me when I was attempting to export an application state in JSON. As I was using the CLI, it ate up all of the system memory and at best it would have returned false if I could have waited an eternity for it to finish.

I've included a test script with a nice handful of circular object references. 

Test script:
---------------
$max_num_objects = 20;
for ($num_objects = 5; $num_objects <= $max_num_objects; $num_objects++) {
        echo PHP_EOL . 'Number of Objects: ' . $num_objects . PHP_EOL;

        // Create the objects and link them to each other.
        $objects = new stdClass;
        for ($i = 0; $i < $num_objects; $i++) {
                $objects->$i = new stdClass;
        }
        for ($i = 0; $i < $num_objects; $i++) {
                for ($j = 0; $j < $num_objects; $j++) {
                        if ($i != $j) {
                                $objects->$i->$j = $objects->$j;
                        }
                }
        }

        $starttime = microtime(true);
        serialize($objects);
        echo '$objects serialize: ' . number_format(microtime(true) - $starttime, 3) . ' seconds' . PHP_EOL;
        
        $starttime = microtime(true); 
        if (json_encode($objects) === FALSE) {
                echo 'JSON Error #' . json_last_error() . ': ' . json_last_error_msg() . PHP_EOL;
        }
        echo '$objects json_encode: ' . number_format(microtime(true) - $starttime, 3) . ' seconds' . PHP_EOL;
}

Expected result:
----------------
Finishes in less than a second, ideally in a negligible amount of time. Example:
...
Number of Objects: 10
$objects serialize: 0.000 seconds
JSON Error #6: Recursion detected
$objects json_encode: 0.000 seconds

Actual result:
--------------
...
Number of Objects: 10
$objects serialize: 0.000 seconds
JSON Error #6: Recursion detected
$objects json_encode: 7.227 seconds

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-11-09 19:28 UTC] bukka@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: bukka
 [2016-05-16 19:10 UTC] bukka@php.net
This is caused by the fact that parsing is not stopped internally when the first error (recursion in this case) is found. To fix this we will have to add a return check to all calls in encoder and allow php_json_encode to fill buffer with incomplete json. Ideally the php_json_encode will be changed to return value indicating that there is an error or all is ok (FAILURE or SUCCESS) instead of void. This can't go to bugfixing release and will have to wait for the next minor.
 [2016-08-29 14:06 UTC] bukka@php.net
-Status: Assigned +Status: Closed
 [2016-08-29 14:06 UTC] bukka@php.net
Fixed and will be part of PHP 7.1
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Sat Nov 28 14:01:23 2020 UTC