php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #43450 Memory leak on some functions with implicit object __toString() call
Submitted: 2007-11-29 14:59 UTC Modified: 2007-12-22 23:09 UTC
Votes:7
Avg. Score:5.0 ± 0.0
Reproduced:7 of 7 (100.0%)
Same Version:6 (85.7%)
Same OS:6 (85.7%)
From: mfischer@php.net Assigned: davidc (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: 5.2.5 OS: Any
Private report: No CVE-ID: None
Anyone can comment on a bug. Have a simpler test case? Does it work for you on a different platform? Let us know!
Just going to say 'Me too!'? Don't clutter the database with that please !
Your email address:
MUST BE VALID
Solve the problem:
29 - 16 = ?
Subscribe to this entry?

 
 [2007-11-29 14:59 UTC] mfischer@php.net
Description:
------------
Under certain circumenstances, the implicit call to __toString() on an object may lead to memory leaks.

In the reproducable example, the following line leaks ($o is a simply object):
 md5($o);
But this line doesn't:
 md5($o->__toString());

This only applies to certain functions, I've identifier md5, sha1 and crc32.

If I try other examples like strstr or strlen, there's no leak at all.

A wild guess is that this maybe has to do whether the function internally uses zend_parse_parameters() or zend_get_parameters_ex().

The function which leak use zend_parse_parameters(), the others don't.

But this may completely accidental.

It seems very related to bug#38591. However I don't see how bug#38604 is related to this issue (mentioned in bug#38591).

This leak was most notable found in an application which is supposed to run for a long time, even hours. So usually within web application this is not an issue.

Reproduce code:
---------------
<?php
    class Foo {
        function __toString() {
            return 'foo';
        }
    }
    for ($i = 0; $i < 1e5; $i++) {

        $o = new Foo;

        # leaks
        md5($o);
        # does not leak
        #md5($o->__toString());

        # does not leak either way
        # strstr($o, 'f');
        #strstr($o->__toString(), 'f');

        if ($i % 1e3 == 0) {
            printf("%u: %1.2f KB\n",
                $i, memory_get_usage(true) / 1024);
        }
    }


Expected result:
----------------
Constant memory usage.

Actual result:
--------------
Memory grows and grows.

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2007-11-30 01:34 UTC] mfischer@php.net
I'm still not sure if this has anything to do with the new Zend parsing API, but I've tested the md5 function with the zend_get_parameters_ex (the old API) and the leak didn't occur. See the two version for a comparison.


-------------------- currently --------------------
PHP_NAMED_FUNCTION(php_if_md5)
{
    char *arg;
    int arg_len;
    zend_bool raw_output = 0;
    char md5str[33];
    PHP_MD5_CTX context;
    unsigned char digest[16];

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
		"s|b", &arg, &arg_len, &raw_output) == FAILURE) {
        return;
    }

    md5str[0] = '\0';
    PHP_MD5Init(&context);
    PHP_MD5Update(&context, arg, arg_len);
    PHP_MD5Final(digest, &context);
    if (raw_output) {
        RETURN_STRINGL(digest, 16, 1);
    } else {
        make_digest_ex(md5str, digest, 16);
        RETVAL_STRING(md5str, 1);
    }

}



----------- hacked rewrite ------------------------
PHP_NAMED_FUNCTION(php_if_md5)
{
    zval **zarg;
    zend_bool raw_output = 0;
    char md5str[33];
    PHP_MD5_CTX context;
    unsigned char digest[16];


    if (ZEND_NUM_ARGS() != 1 ||
		zend_get_parameters_ex(1, &zarg) == FAILURE) {
        WRONG_PARAM_COUNT;
    }
    convert_to_string_ex(zarg);

    md5str[0] = '\0';
    PHP_MD5Init(&context);
    PHP_MD5Update(&context, Z_STRVAL_PP(zarg), Z_STRLEN_PP(zarg));
    PHP_MD5Final(digest, &context);
    if (raw_output) {
        RETURN_STRINGL(digest, 16, 1);
    } else {
        make_digest_ex(md5str, digest, 16);
        RETVAL_STRING(md5str, 1);
    }
}

 [2007-12-20 11:47 UTC] helly@php.net
It appears that zend_std_cast_object_tostring() does not check whether it has to dref readobj prior writing to writeobj in case they are the same zval.

That said the code needs to be refactored to:
- if (readobj==writeobj) { zval_dtor(readobj); }  // not zval_ptr_dtor
- call INIT_PZVAL(writeobj) always
- set Z_TYPE_P(writeobj) = IS_NULL; for the default case
 [2007-12-21 16:37 UTC] davidc@php.net
Patches are added for 5_2, 5_3 and HEAD there:

http://dev.agoraproduction.com/php/Zend/

Thanks,
 [2007-12-22 18:45 UTC] jani@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


 [2012-02-26 14:56 UTC] laruence@php.net
Automatic comment from SVN on behalf of laruence
Revision: http://svn.php.net/viewvc/?view=revision&amp;revision=323563
Log: Improve fix for #61165, the previous one cause #43450 test failed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Dec 03 13:01:28 2024 UTC