For fake closures, we need to share static variables with the
original function, not work on a separate copy. Calling a function
through Closure::fromCallable() should have the same behavior as
calling it directly.
This commit is contained in:
Nikita Popov 2021-02-17 14:45:35 +01:00
parent 5d160e309e
commit 6b0f14fe3b
3 changed files with 97 additions and 7 deletions

2
NEWS
View File

@ -5,6 +5,8 @@ PHP NEWS
- Core:
. Fixed inclusion order for phpize builds on Windows. (cmb)
. Added missing hashtable insertion APIs for arr/obj/ref. (Sara)
. Fixed bug #75474 (function scope static variables are not bound to a unique
function). (Nikita)
- FTP:
. Convert resource<ftp> to object \FTPConnection. (Sara)

75
Zend/tests/bug75474.phpt Normal file
View File

@ -0,0 +1,75 @@
--TEST--
Bug #75474: function scope static variables are not bound to a unique function
--FILE--
<?php
function bar($k, $v) {
static $foo = [];
$foo[$k] = $v;
return $foo;
}
var_dump(bar(0, 0));
var_dump(Closure::fromCallable("bar")(1, 1));
var_dump(bar(2, 2));
var_dump(Closure::fromCallable("bar")(3, 3));
$RF = new ReflectionFunction("bar");
var_dump($RF->getClosure()(4, 4));
var_dump(bar(5, 5));
?>
--EXPECT--
array(1) {
[0]=>
int(0)
}
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}
array(3) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
}
array(4) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
}
array(5) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
}
array(6) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
[5]=>
int(5)
}

View File

@ -486,6 +486,11 @@ static void zend_closure_free_storage(zend_object *object) /* {{{ */
zend_object_std_dtor(&closure->std);
if (closure->func.type == ZEND_USER_FUNCTION) {
/* We shared static_variables with the original function.
* Unshare now so we don't try to destroy them. */
if (closure->func.op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE) {
ZEND_MAP_PTR_INIT(closure->func.op_array.static_variables_ptr, NULL);
}
destroy_op_array(&closure->func.op_array);
}
@ -660,7 +665,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_internal_handler) /* {{{ */
}
/* }}} */
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */
static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool is_fake) /* {{{ */
{
zend_closure *closure;
@ -679,12 +684,15 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
closure->func.common.fn_flags &= ~ZEND_ACC_IMMUTABLE;
if (closure->func.op_array.static_variables) {
closure->func.op_array.static_variables =
zend_array_dup(closure->func.op_array.static_variables);
/* For fake closures, we want to reuse the static variables of the original function. */
if (!is_fake) {
if (closure->func.op_array.static_variables) {
closure->func.op_array.static_variables =
zend_array_dup(closure->func.op_array.static_variables);
}
ZEND_MAP_PTR_INIT(closure->func.op_array.static_variables_ptr,
&closure->func.op_array.static_variables);
}
ZEND_MAP_PTR_INIT(closure->func.op_array.static_variables_ptr,
&closure->func.op_array.static_variables);
/* Runtime cache is scope-dependent, so we cannot reuse it if the scope changed */
if (!ZEND_MAP_PTR_GET(closure->func.op_array.run_time_cache)
@ -754,11 +762,16 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
}
/* }}} */
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr)
{
zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ false);
}
ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */
{
zend_closure *closure;
zend_create_closure(res, func, scope, called_scope, this_ptr);
zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ true);
closure = (zend_closure *)Z_OBJ_P(res);
closure->func.common.fn_flags |= ZEND_ACC_FAKE_CLOSURE;