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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: tshaw at oitc dot com
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2025 The PHP Group
All rights reserved.
Last updated: Sun Jun 15 13:01:35 2025 UTC