php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #61792 preg_replace_callback memory leak
Submitted: 2012-04-20 21:40 UTC Modified: 2012-04-20 22:45 UTC
From: tshaw at oitc dot com Assigned:
Status: Not a bug Package: PCRE related
PHP Version: 5.4.0 OS: OSX 10.7.3
Private report: No CVE-ID: None
 [2012-04-20 21:40 UTC] tshaw at oitc dot com
Description:
------------
$ ./ptest.php
Test preg_replace_callback
Iteration number 0
Iteration number 1
....
Iteration number 180951
PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to 
allocate 3072 bytes) in /Users/tshaw/Sites/surbl/ptest.php(11) : runtime-created 
function on line 1

Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 
3072 bytes) in /Users/tshaw/Sites/surbl/ptest.php(11) : runtime-created function 
on line 1


Test script:
---------------
#!/usr/local/php5/bin/php
<?PHP
// #!/usr/bin/php
error_reporting(E_ALL);
function urlDecodeUnreservedChars( $string ) {
        $unreserved = array();
        $unreserved[] = dechex( ord( '-' ) );
        $unreserved[] = dechex( ord( '.' ) );
        $unreserved[] = dechex( ord( '_' ) );
        $unreserved[] = dechex( ord( '~' ) );
        return preg_replace_callback( array_map( create_function( '$str', 'return "/%" . strtoupper( $str ) . "/x";' ), $unreserved ), create_function( '$matches', 'return chr( hexdec( $matches[0] ));' ), $string );
    }
for ($i=0; $i <5000000; $i++) {
		echo "Iteration number $i\n";
	urlDecodeUnreservedChars( "12345" );
}
?>


Expected result:
----------------
Expected it to run to completion

Actual result:
--------------
PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to 
allocate 3072 bytes) in /Users/tshaw/Sites/surbl/ptest.php(11) : runtime-created 
function on line 1

Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 
3072 bytes) in /Users/tshaw/Sites/surbl/ptest.php(11) : runtime-created function 
on line 1


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-04-20 22:24 UTC] felipe@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

There is no memory leak, what happens is that the memory associated to the lambda functions (via create_function()) is just released in the end of execution.
 [2012-04-20 22:24 UTC] felipe@php.net
-Status: Open +Status: Not a bug
 [2012-04-20 22:45 UTC] tshaw at oitc dot com
I am totally confused. 

You say that its OK for PHP to fail with a memory exhausted error when running a 
perfectly valid CLI script that happens to fail on the 180951 iteration? I say 
this is absolutely bug!  

There may be no memory leak but it surely is a bug as a CLI script that iterates 
a long period of time is not out of line.

I respectfully request you reconsider and change this back to a bug.
 [2012-04-21 01:46 UTC] anon at anon dot anon
@tshaw: The problem is that create_function is a nasty old construct that adds a new function every time you call it, even if the code to compile is the same each time. The created functions are *permanent* and create_function returns their name only -- so even if the variable containing their name goes out of scope, the created functions persist. Naturally, 10 million functions take a lot of memory. It's not technically a bug, just awful language design.

You can create the functions once statically and store their names, or since PHP 5.3.0, you can use anonymous functions instead. Try this:

function urlDecodeUnreservedChars( $string ) {
	$unreserved = array();
	$unreserved[] = dechex( ord( '-' ) );
	$unreserved[] = dechex( ord( '.' ) );
	$unreserved[] = dechex( ord( '_' ) );
	$unreserved[] = dechex( ord( '~' ) );
	return preg_replace_callback(
		array_map(function ($str) { return '/%' . strtoupper($str) . '/x'; }, $unreserved),
		function ($matches) { return chr(hexdec($matches[0])); }, $string
	);
}
 [2013-04-23 05:27 UTC] andrew at mcnaughty dot com
Actually I think this bug or something very like it still exists with an anonymous function:

I'm seeing a leak with the following code:

------
        $this->contact['email_greeting_display'] = preg_replace_callback(
            '@\{(?:contact\.)?([a-z0-9._]*)\}@',
            function($matches) use ($prefixes,$contact) {
                if ($matches[1] == 'individual_prefix') {
                    return $prefixes[$contact['prefix_id']];
                }
                else {
                    return $contact[$matches[1]];
                }
            },
            $format['greeting']
------
 [2014-07-06 14:55 UTC] roman-ak at wmkeeper dot com
It'd a bug, because:
<?
//PHP Version 5.4.4-10, memory=128MB
$source = "123";
//$source = preg_replace_callback('/\d+/', function($m){return $m[0];}, $source); //error Allowed memory size
//$source = preg_replace_callback('/\d+/', function() use($m){return $m[0];}, $source); //error Allowed memory size
//$source = preg_replace_callback('/\d+/', create_function('$m', 'return $m[0];'), $source); //work, but create_function for it's version php is old, about it in executed not notice, but notice in documentation
//$source = preg_replace_callback('/\d+/', function(){return;}, $source); //error Allowed memory size
//$source = preg_replace_callback('/\d+/', create_function('',''), $source); //work, but construction is old
//$f=function(){return;}; $source = preg_replace_callback('/\d+/', $f, $source); //error Allowed memory size
//function f(){return;}; $source = preg_replace_callback('/\d+/', 'f', $source); //work, but it's method unacceptable if need do loop and taken out of the loop is also impossible, because key is change and will protection of engine
if(!function_exists('f')){function f(){return;};} $source = preg_replace_callback('/\d+/', 'f', $source); //work, not be deprecated and not suitable for loop
?>
 [2014-07-06 16:40 UTC] roman-ak at wmkeeper dot com
If after problem string add: print 'test';, then been error: "Fatal error: Allowed memory size". If not add: print 'test';, then been error: "Warning: preg_replace_callback(): Requires argument 2" But after once error: "Allowed memory size", no longer appears: "Warning: preg_replace_callback()", allways "Fatal error: Allowed memory size".

Solution: in config virtual server add:
php_flag eaccelerator.enable 0
php_flag eaccelerator.optimizer 0
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sat Aug 24 17:01:27 2019 UTC