Don't use separate static variables in inherited methods

RFC: https://wiki.php.net/rfc/static_variable_inheritance

Closes GH-6719.
This commit is contained in:
Nikita Popov 2021-02-22 12:28:02 +01:00
parent 3e6b447979
commit 9a1da9f61f
8 changed files with 74 additions and 44 deletions

View File

@ -37,28 +37,26 @@ PHP 8.1 UPGRADE NOTES
// is deprecated
RFC: https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg
. When a method using static variables is inherited, the inherited method
will now initialize the static variables to their original values, rather
than the values at the time of inheritance:
. When a method using static variables is inherited (but not overridden), the
inherited method will now share static variables with the parent method.
class A {
public function counter() {
public static function counter() {
static $counter = 0;
$counter++;
return $counter;
}
}
class B extends A {}
var_dump((new A)->counter()); // int(1)
var_dump(A::counter()); // int(1)
var_dump(A::counter()); // int(2)
var_dump(B::counter()); // int(3), previously int(1)
var_dump(B::counter()); // int(4), previously int(2)
eval('class B extends A {}'); // eval() to prevent early binding.
var_dump((new B)->counter()); // int(1), previously int(2)
var_dump((new A)->counter()); // int(2)
var_dump((new B)->counter()); // int(2), previously int(3)
Previously the behavior would be different depending on whether A::counter()
was called before class B was declared, or after it was declared.
This means that static variables in methods now behave the same way as
static properties.
RFC: https://wiki.php.net/rfc/static_variable_inheritance
- Fileinfo:
. The fileinfo functions now accept and return, respectively, finfo objects

View File

@ -19,12 +19,15 @@ var_dump($d->foo(24));
var_dump($c->foo());
?>
--EXPECT--
NULL
array(1) {
[0]=>
int(42)
}
array(1) {
[0]=>
int(24)
}
array(1) {
[0]=>
int(42)
int(24)
}

View File

@ -19,13 +19,16 @@ var_dump($d->foo(24));
var_dump($c->foo());
?>
--EXPECT--
NULL
array(1) {
[0]=>
object(stdClass)#2 (0) {
}
}
array(1) {
[0]=>
int(24)
}
array(1) {
[0]=>
object(stdClass)#2 (0) {
}
int(24)
}

View File

@ -20,5 +20,5 @@ Bar::test();
--EXPECT--
int(1)
int(2)
int(1)
int(2)
int(3)
int(4)

View File

@ -0,0 +1,43 @@
--TEST--
Behavior of static variables in trait methods
--FILE--
<?php
trait T {
public static function counter() {
static $i = 0;
var_dump(++$i);
}
}
class C1 {
use T {
T::counter as counter1;
T::counter as counter2;
}
}
class C2 {
use T;
}
C1::counter();
C1::counter1();
C1::counter2();
C2::counter();
C1::counter();
C1::counter1();
C1::counter2();
C2::counter();
?>
--EXPECT--
int(1)
int(1)
int(1)
int(1)
int(2)
int(2)
int(2)
int(2)

View File

@ -88,16 +88,7 @@ static zend_function *zend_duplicate_internal_function(zend_function *func, zend
}
/* }}} */
static zend_function *zend_duplicate_user_function(zend_function *func) /* {{{ */
{
zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
memcpy(new_op_array, func, sizeof(zend_op_array));
zend_init_static_variables_map_ptr(new_op_array);
return (zend_function *) new_op_array;
}
/* }}} */
static zend_always_inline zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce, bool is_interface) /* {{{ */
static zend_always_inline zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce) /* {{{ */
{
if (UNEXPECTED(func->type == ZEND_INTERNAL_FUNCTION)) {
return zend_duplicate_internal_function(func, ce);
@ -108,12 +99,7 @@ static zend_always_inline zend_function *zend_duplicate_function(zend_function *
if (EXPECTED(func->op_array.function_name)) {
zend_string_addref(func->op_array.function_name);
}
if (is_interface
|| EXPECTED(!func->op_array.static_variables)) {
/* reuse the same op_array structure */
return func;
}
return zend_duplicate_user_function(func);
return func;
}
}
/* }}} */
@ -948,9 +934,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
if (!check_only && child->common.prototype != proto && child_zv) {
do {
if (child->common.scope != ce
&& child->type == ZEND_USER_FUNCTION
&& !child->op_array.static_variables) {
if (child->common.scope != ce && child->type == ZEND_USER_FUNCTION) {
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
/* Few parent interfaces contain the same method */
break;
@ -1021,7 +1005,7 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function
ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS;
}
parent = zend_duplicate_function(parent, ce, is_interface);
parent = zend_duplicate_function(parent, ce);
if (!is_interface) {
_zend_hash_append_ptr(&ce->function_table, key, parent);

View File

@ -19,8 +19,8 @@ Bar::test();
--EXPECT--
int(1)
int(2)
int(1)
int(2)
int(3)
int(4)
int(1)
int(1)
int(2)

View File

@ -763,7 +763,6 @@ static void zend_persist_class_method(zval *zv, zend_class_entry *ce)
}
if ((op_array->fn_flags & ZEND_ACC_IMMUTABLE)
&& !op_array->static_variables
&& !ZCG(current_persistent_script)->corrupted
&& zend_accel_in_shm(op_array)) {
zend_shared_alloc_register_xlat_entry(op_array, op_array);