]> granicus.if.org Git - php/commitdiff
Fixed bug #75474
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 17 Feb 2021 13:45:35 +0000 (14:45 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Thu, 18 Feb 2021 10:39:28 +0000 (11:39 +0100)
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.

NEWS
Zend/tests/bug75474.phpt [new file with mode: 0644]
Zend/zend_closures.c

diff --git a/NEWS b/NEWS
index 5d6db8eb520e823607812e5f99422430560ef419..c6b626bb53a1c6f95dec0e1f3a8d11fb41ecaeb6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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)
diff --git a/Zend/tests/bug75474.phpt b/Zend/tests/bug75474.phpt
new file mode 100644 (file)
index 0000000..f71e3c6
--- /dev/null
@@ -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)
+}
index c06f90fabc9a270fefb2dde1d0be0eae41eadea1..38105a67f6a3d78d513004e1e798c7419b2865bf 100644 (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;