Bug #64291 [Com]: Indeterminate GC of evaled lambda function resources
| From: | Terry at ellisons dot org dot uk | Date: | Thu, 05 Dec 2013 16:15:37 +0000 |
| Subject: | Bug #64291 [Com]: Indeterminate GC of evaled lambda function resources | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-183138@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=64291&edit=1
ID: 64291
Comment by: Terry at ellisons dot org dot uk
Reported by: Terry at ellisons dot org dot uk
Summary: Indeterminate GC of evaled lambda function resources
Status: Open
Type: Bug
Package: Scripting Engine problem
Operating System: Ubuntu 12.10
PHP Version: 5.4.12
Block user comment: N
Private report: N
New Comment:
Dmitry,
I have just realised that this "mangled names should be unique" issue applied to any
runtime bound function or class as the following -- albeit perverse example shows:
--TEST--
ISSUE #65915A Temporary class entries are not unique
--INI--
opcache.enable=0
--SKIPIF--
--FILE--
<?php
$tmp = tempnam(__DIR__, 'test');
foreach (['a','b'] as $f) {
file_put_contents($tmp, <<<END
<?php
function $f() {
class Hello { const WORLD = "Hello world from $f\\n"; }
}
END
);
require $tmp;
}
a();
echo Hello::WORLD;
unlink($tmp);
?>
--CLEAN--
--EXPECT--
Hello world from a
Here the two functions both compile a class with a mangled name
"\0$class$filename$string_addr" which is the same for the a() and b() copies
so b() version overwrites the a() one, and the DECLARE_CLASS opcode in a() incorrecly binds to the
wrong class, hence Hello::WORLD incorrectly prints out the "from b" version.
However, I suspect in practice that this is unlikely to manifest itself in real word apps.
Previous Comments:
------------------------------------------------------------------------
[2013-11-25 13:26:28] Terry at ellisons dot org dot uk
Sorry, the above should read opcache.enable=0 though the failure is the same for opcache enabled and
not enabled in the eval case.
------------------------------------------------------------------------
[2013-11-25 13:24:08] Terry at ellisons dot org dot uk
And here's the eval version:
--TEST--
ISSUE #64291 Temporary function entries for closures are not unique
--INI--
opcache.enable=1
--FILE--
<?php
foreach (['a','b'] as $f) {
$tmp = "function $f() {return function(){ return '$f'; };}\n";
eval($tmp);
echo $tmp;
}
$a = a();
$b = b();
printf( "%s, %s\n ", $a(), $b());
?>
--EXPECT--
function a() {return function(){ return 'a'; };}
function b() {return function(){ return 'b'; };}
a, b
------------------------------------------------------------------------
[2013-11-25 12:42:16] Terry at ellisons dot org dot uk
What threw me was the botch with the temporary entries "\0{closure}$filenane$offset" are
used in the EG(function_table). This is as clear as mud. When a file is compiled, a function table
entry is created for each closue in the source. This entry is never executed directly, but is used
by the ZEND_DECLARE_LAMBDA_FUNCTION to construct the closure object which contains a deep copy of
this zend_function record. It is this copy that used when the closure is called. So long as the
ZEND_DECLARE_LAMBDA_FUNCTION
are executed within the same scope as the compile, this should normally be unique, but it is quite
easy to construct a test case which the unique assumption fails:
--TEST--
ISSUE #65915 Temporary function entries for closures are not unique
--INI--
opcache.enable=0
--SKIPIF--
--FILE--
<?php
$tmp = tempnam(__DIR__, 'test');
foreach (['a','b'] as $f) {
file_put_contents($tmp, "<?php function $f() {return function(){ return '$f';
};}");
echo file_get_contents($tmp), "\n";
require $tmp;
}
$a = a();
$b = b();
printf( "%s, %s\n ", $a(), $b());
unlink($tmp);
?>
--CLEAN--
--EXPECT--
<?php function a() {return function(){ return 'a'; };}
<?php function b() {return function(){ return 'b'; };}
a, b
------------------------------------------------------------------------
[2013-11-25 08:53:35] dmitry@php.net
Your second script prints "a, a" only with OPCache, because it caches the included
temporary file. Without OPCache it prints the expected "a, b".
I also don't think that the first script indicates a bug. The more functions you create the
more memory it requires.
------------------------------------------------------------------------
[2013-11-22 12:34:08] Terry at ellisons dot org dot uk
Just to note that my patch is not strong enough when used with OPcache, since a simple static
running count can fail when the interpreter is forked. Better alternatives include
* some form of unique id, e.g. a UUID or uniquid(true)
* a content based hash, such as md5file(__FILE__) -- though if this were to be adopted then it would
be better always to generate this as part of the compile creating say __FILE_MD5__, though this
would add a few % to the compile time.
This all needs wider discussion of these issues on the Internals ML.
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=64291
--
Edit this bug report at https://bugs.php.net/bug.php?id=64291&edit=1