Skip to content

Commit 5d160e3

Browse files
committed
Fix static variable behavior with inheritance
When a method is inherited, the static variables will now always use the initial values, rather than the values at the time of inheritance. As such, behavior no longer depends on whether inheritance happens before or after a method has been called. This is implemented by always keeping static_variables as the original values, and static_variables_ptr as the modified copy. Closes GH-6705.
1 parent e032847 commit 5d160e3

19 files changed

+97
-144
lines changed

UPGRADING

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@ PHP 8.1 UPGRADE NOTES
3737
// is deprecated
3838

3939
RFC: https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg
40+
. When a method using static variables is inherited, the inherited method
41+
will now initialize the static variables to their original values, rather
42+
than the values at the time of inheritance:
43+
44+
class A {
45+
public function counter() {
46+
static $counter = 0;
47+
$counter++;
48+
return $counter;
49+
}
50+
}
51+
52+
var_dump((new A)->counter()); // int(1)
53+
54+
eval('class B extends A {}'); // eval() to prevent early binding.
55+
56+
var_dump((new B)->counter()); // int(1), previously int(2)
57+
var_dump((new A)->counter()); // int(2)
58+
var_dump((new B)->counter()); // int(2), previously int(3)
59+
60+
Previously the behavior would be different depending on whether A::counter()
61+
was called before class B was declared, or after it was declared.
4062

4163
- Fileinfo:
4264
. The fileinfo functions now accept and return, respectively, finfo objects

Zend/tests/anon/015.phpt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ var_dump($d->foo(24));
1919
var_dump($c->foo());
2020
?>
2121
--EXPECT--
22-
array(1) {
23-
[0]=>
24-
int(42)
25-
}
22+
NULL
2623
array(1) {
2724
[0]=>
2825
int(24)

Zend/tests/anon/016.phpt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ var_dump($d->foo(24));
1919
var_dump($c->foo());
2020
?>
2121
--EXPECT--
22-
array(1) {
23-
[0]=>
24-
object(stdClass)#2 (0) {
25-
}
26-
}
22+
NULL
2723
array(1) {
2824
[0]=>
2925
int(24)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Closure::bindTo() should preserve used variables
3+
--FILE--
4+
<?php
5+
6+
$var = 0;
7+
$fn = function() use($var) {
8+
var_dump($var);
9+
};
10+
$fn();
11+
$fn = $fn->bindTo(null, null);
12+
$fn();
13+
14+
?>
15+
--EXPECT--
16+
int(0)
17+
int(0)

Zend/tests/method_static_var.phpt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ Initial value of static var in method depends on the include time of the class d
33
--FILE--
44
<?php
55

6-
/* The current behavior is probably a bug, but we should still test how it currently works. */
7-
86
class Foo {
97
public static function test() {
108
static $i = 0;
@@ -22,5 +20,5 @@ Bar::test();
2220
--EXPECT--
2321
int(1)
2422
int(2)
23+
int(1)
2524
int(2)
26-
int(3)

Zend/zend_compile.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,28 +1029,32 @@ static uint32_t zend_add_try_element(uint32_t try_op) /* {{{ */
10291029
}
10301030
/* }}} */
10311031

1032+
void zend_init_static_variables_map_ptr(zend_op_array *op_array)
1033+
{
1034+
if (op_array->static_variables) {
1035+
ZEND_MAP_PTR_INIT(op_array->static_variables_ptr,
1036+
zend_arena_alloc(&CG(arena), sizeof(HashTable *)));
1037+
ZEND_MAP_PTR_SET(op_array->static_variables_ptr, NULL);
1038+
}
1039+
}
1040+
10321041
ZEND_API void function_add_ref(zend_function *function) /* {{{ */
10331042
{
10341043
if (function->type == ZEND_USER_FUNCTION) {
10351044
zend_op_array *op_array = &function->op_array;
1036-
10371045
if (op_array->refcount) {
10381046
(*op_array->refcount)++;
10391047
}
1040-
if (op_array->static_variables
1041-
&& !(GC_FLAGS(op_array->static_variables) & IS_ARRAY_IMMUTABLE)) {
1042-
GC_ADDREF(op_array->static_variables);
1043-
}
10441048

10451049
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
10461050
ZEND_ASSERT(op_array->fn_flags & ZEND_ACC_PRELOADED);
10471051
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
1048-
ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
10491052
} else {
1050-
ZEND_MAP_PTR_INIT(op_array->static_variables_ptr, &op_array->static_variables);
1051-
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*)));
1053+
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void *)));
10521054
ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL);
10531055
}
1056+
1057+
zend_init_static_variables_map_ptr(op_array);
10541058
}
10551059

10561060
if (function->common.function_name) {
@@ -7021,9 +7025,8 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{
70217025
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
70227026
op_array->fn_flags |= ZEND_ACC_PRELOADED;
70237027
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
7024-
ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
70257028
} else {
7026-
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*)));
7029+
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void *)));
70277030
ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL);
70287031
}
70297032

@@ -7112,6 +7115,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{
71127115
zend_do_extended_stmt();
71137116
zend_emit_final_return(0);
71147117

7118+
zend_init_static_variables_map_ptr(op_array);
71157119
pass_two(CG(active_op_array));
71167120
zend_oparray_context_end(&orig_oparray_context);
71177121

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ void zend_verify_namespace(void);
795795
void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline);
796796

797797
ZEND_API void function_add_ref(zend_function *function);
798+
void zend_init_static_variables_map_ptr(zend_op_array *op_array);
798799

799800
#define INITIAL_OP_ARRAY_SIZE 64
800801

Zend/zend_execute_API.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,10 @@ void shutdown_executor(void) /* {{{ */
285285
if (op_array->type == ZEND_INTERNAL_FUNCTION) {
286286
break;
287287
}
288-
if (op_array->static_variables) {
288+
if (ZEND_MAP_PTR(op_array->static_variables_ptr)) {
289289
HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
290290
if (ht) {
291-
zend_array_release(ht);
291+
zend_array_destroy(ht);
292292
ZEND_MAP_PTR_SET(op_array->static_variables_ptr, NULL);
293293
}
294294
}
@@ -308,10 +308,10 @@ void shutdown_executor(void) /* {{{ */
308308
zend_op_array *op_array;
309309
ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) {
310310
if (op_array->type == ZEND_USER_FUNCTION) {
311-
if (op_array->static_variables) {
311+
if (ZEND_MAP_PTR(op_array->static_variables_ptr)) {
312312
HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
313313
if (ht) {
314-
zend_array_release(ht);
314+
zend_array_destroy(ht);
315315
ZEND_MAP_PTR_SET(op_array->static_variables_ptr, NULL);
316316
}
317317
}

Zend/zend_inheritance.c

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,10 @@ static zend_function *zend_duplicate_internal_function(zend_function *func, zend
8989

9090
static zend_function *zend_duplicate_user_function(zend_function *func) /* {{{ */
9191
{
92-
zend_function *new_function;
93-
94-
new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
95-
memcpy(new_function, func, sizeof(zend_op_array));
96-
97-
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
98-
ZEND_ASSERT(new_function->op_array.fn_flags & ZEND_ACC_PRELOADED);
99-
ZEND_MAP_PTR_NEW(new_function->op_array.static_variables_ptr);
100-
} else {
101-
ZEND_MAP_PTR_INIT(new_function->op_array.static_variables_ptr, &new_function->op_array.static_variables);
102-
}
103-
104-
HashTable *static_properties_ptr = ZEND_MAP_PTR_GET(func->op_array.static_variables_ptr);
105-
if (static_properties_ptr) {
106-
/* See: Zend/tests/method_static_var.phpt */
107-
ZEND_MAP_PTR_SET(new_function->op_array.static_variables_ptr, static_properties_ptr);
108-
GC_TRY_ADDREF(static_properties_ptr);
109-
} else {
110-
GC_TRY_ADDREF(new_function->op_array.static_variables);
111-
}
112-
113-
return new_function;
92+
zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
93+
memcpy(new_op_array, func, sizeof(zend_op_array));
94+
zend_init_static_variables_map_ptr(new_op_array);
95+
return (zend_function *) new_op_array;
11496
}
11597
/* }}} */
11698

@@ -2504,20 +2486,29 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce)
25042486
for (; p != end; p++) {
25052487
zend_op_array *op_array, *new_op_array;
25062488
void ***run_time_cache_ptr;
2489+
size_t alloc_size;
25072490

25082491
op_array = Z_PTR(p->val);
25092492
ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION);
25102493
ZEND_ASSERT(op_array->scope == pce);
25112494
ZEND_ASSERT(op_array->prototype == NULL);
2512-
new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array) + sizeof(void*));
2495+
alloc_size = sizeof(zend_op_array) + sizeof(void *);
2496+
if (op_array->static_variables) {
2497+
alloc_size += sizeof(HashTable *);
2498+
}
2499+
new_op_array = zend_arena_alloc(&CG(arena), alloc_size);
25132500
Z_PTR(p->val) = new_op_array;
25142501
memcpy(new_op_array, op_array, sizeof(zend_op_array));
25152502
run_time_cache_ptr = (void***)(new_op_array + 1);
25162503
*run_time_cache_ptr = NULL;
25172504
new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE;
25182505
new_op_array->scope = ce;
25192506
ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, run_time_cache_ptr);
2520-
ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, &new_op_array->static_variables);
2507+
if (op_array->static_variables) {
2508+
HashTable **static_variables_ptr = (HashTable **) (run_time_cache_ptr + 1);
2509+
*static_variables_ptr = NULL;
2510+
ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, static_variables_ptr);
2511+
}
25212512

25222513
zend_update_inherited_handler(constructor);
25232514
zend_update_inherited_handler(destructor);

Zend/zend_language_scanner.l

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ static zend_op_array *zend_compile(int type)
636636
zend_emit_final_return(type == ZEND_USER_FUNCTION);
637637
op_array->line_start = 1;
638638
op_array->line_end = last_lineno;
639+
zend_init_static_variables_map_ptr(op_array);
639640
pass_two(op_array);
640641
zend_oparray_context_end(&original_oparray_context);
641642
zend_file_context_end(&original_file_context);

Zend/zend_opcode.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz
7777
op_array->last_live_range = 0;
7878

7979
op_array->static_variables = NULL;
80-
ZEND_MAP_PTR_INIT(op_array->static_variables_ptr, &op_array->static_variables);
80+
ZEND_MAP_PTR_INIT(op_array->static_variables_ptr, NULL);
8181
op_array->last_try_catch = 0;
8282

8383
op_array->fn_flags = 0;
@@ -515,12 +515,10 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
515515
{
516516
uint32_t i;
517517

518-
if (op_array->static_variables) {
518+
if (ZEND_MAP_PTR(op_array->static_variables_ptr)) {
519519
HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
520-
if (ht && !(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
521-
if (GC_DELREF(ht) == 0) {
522-
zend_array_destroy(ht);
523-
}
520+
if (ht) {
521+
zend_array_destroy(ht);
524522
}
525523
}
526524

@@ -599,6 +597,9 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
599597
}
600598
efree(arg_info);
601599
}
600+
if (op_array->static_variables) {
601+
zend_array_destroy(op_array->static_variables);
602+
}
602603
}
603604

604605
static void zend_update_extended_stmts(zend_op_array *op_array)

Zend/zend_vm_def.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8659,16 +8659,10 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)
86598659

86608660
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
86618661
if (!ht) {
8662-
ZEND_ASSERT(EX(func)->op_array.fn_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED));
86638662
ht = zend_array_dup(EX(func)->op_array.static_variables);
86648663
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
8665-
} else if (GC_REFCOUNT(ht) > 1) {
8666-
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
8667-
GC_DELREF(ht);
8668-
}
8669-
ht = zend_array_dup(ht);
8670-
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
86718664
}
8665+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
86728666

86738667
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)));
86748668

Zend/zend_vm_execute.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47468,16 +47468,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN
4746847468

4746947469
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
4747047470
if (!ht) {
47471-
ZEND_ASSERT(EX(func)->op_array.fn_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED));
4747247471
ht = zend_array_dup(EX(func)->op_array.static_variables);
4747347472
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
47474-
} else if (GC_REFCOUNT(ht) > 1) {
47475-
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
47476-
GC_DELREF(ht);
47477-
}
47478-
ht = zend_array_dup(ht);
47479-
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
4748047473
}
47474+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
4748147475

4748247476
value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)));
4748347477

0 commit comments

Comments
 (0)