]> granicus.if.org Git - php/commitdiff
Fix bug #78770
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 14 Aug 2020 08:22:42 +0000 (10:22 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 14 Aug 2020 08:24:06 +0000 (10:24 +0200)
Refactor the zend_is_callable implementation to check callability
at a particular frame (this is an implementation detail for now,
but could be exposed in the API if useful). Pick the first parent
user frame as the one to check.

NEWS
Zend/tests/bug78770.phpt [new file with mode: 0644]
Zend/zend_API.c
Zend/zend_closures.c
Zend/zend_closures.stub.php
Zend/zend_closures_arginfo.h

diff --git a/NEWS b/NEWS
index cd163a5f6cc34b71eba84151f5c8af6dcde8d284..c0d14769a227f3d9fb035b2bbbc092919996d88c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,8 @@ PHP                                                                        NEWS
     exit code). (Nikita)
   . Fixed bug #79927 (Generator doesn't throw exception after multiple yield
     from iterable). (Nikita)
+  . Fixed bug #78770 (Incorrect callability check inside internal methods).
+    (Nikita)
 
 - Date:
   . Fixed bug #60302 (DateTime::createFromFormat should new static(), not new
diff --git a/Zend/tests/bug78770.phpt b/Zend/tests/bug78770.phpt
new file mode 100644 (file)
index 0000000..b1cf783
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+Bug #78770: Incorrect callability check inside internal methods
+--SKIPIF--
+<?php
+if (!extension_loaded("intl")) die("skip requires intl");
+?>
+--FILE--
+<?php
+
+class Test {
+    public function method() {
+        IntlChar::enumCharTypes([$this, 'privateMethod']);
+        IntlChar::enumCharTypes('self::privateMethod');
+    }
+
+    private function privateMethod($start, $end, $name) {
+    }
+}
+
+(new Test)->method();
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
index b82e4fbc164b6e304a357a86b8d2390414e75ebf..03b65779da2c842b37f90c785b5964eee50e9ee2 100644 (file)
@@ -2850,7 +2850,12 @@ ZEND_API int zend_disable_class(const char *class_name, size_t class_name_length
 }
 /* }}} */
 
-static int zend_is_callable_check_class(zend_string *name, zend_class_entry *scope, zend_fcall_info_cache *fcc, int *strict_class, char **error) /* {{{ */
+static zend_always_inline zend_class_entry *get_scope(zend_execute_data *frame)
+{
+       return frame && frame->func ? frame->func->common.scope : NULL;
+}
+
+static int zend_is_callable_check_class(zend_string *name, zend_class_entry *scope, zend_execute_data *frame, zend_fcall_info_cache *fcc, int *strict_class, char **error) /* {{{ */
 {
        int ret = 0;
        zend_class_entry *ce;
@@ -2866,10 +2871,10 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
                if (!scope) {
                        if (error) *error = estrdup("cannot access \"self\" when no class scope is active");
                } else {
-                       fcc->called_scope = zend_get_called_scope(EG(current_execute_data));
+                       fcc->called_scope = zend_get_called_scope(frame);
                        fcc->calling_scope = scope;
                        if (!fcc->object) {
-                               fcc->object = zend_get_this_object(EG(current_execute_data));
+                               fcc->object = zend_get_this_object(frame);
                        }
                        ret = 1;
                }
@@ -2879,16 +2884,16 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
                } else if (!scope->parent) {
                        if (error) *error = estrdup("cannot access \"parent\" when current class scope has no parent");
                } else {
-                       fcc->called_scope = zend_get_called_scope(EG(current_execute_data));
+                       fcc->called_scope = zend_get_called_scope(frame);
                        fcc->calling_scope = scope->parent;
                        if (!fcc->object) {
-                               fcc->object = zend_get_this_object(EG(current_execute_data));
+                               fcc->object = zend_get_this_object(frame);
                        }
                        *strict_class = 1;
                        ret = 1;
                }
        } else if (zend_string_equals_literal(lcname, "static")) {
-               zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
+               zend_class_entry *called_scope = zend_get_called_scope(frame);
 
                if (!called_scope) {
                        if (error) *error = estrdup("cannot access \"static\" when no class scope is active");
@@ -2896,22 +2901,16 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
                        fcc->called_scope = called_scope;
                        fcc->calling_scope = called_scope;
                        if (!fcc->object) {
-                               fcc->object = zend_get_this_object(EG(current_execute_data));
+                               fcc->object = zend_get_this_object(frame);
                        }
                        *strict_class = 1;
                        ret = 1;
                }
        } else if ((ce = zend_lookup_class(name)) != NULL) {
-               zend_class_entry *scope;
-               zend_execute_data *ex = EG(current_execute_data);
-
-               while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) {
-                       ex = ex->prev_execute_data;
-               }
-               scope = ex ? ex->func->common.scope : NULL;
+               zend_class_entry *scope = get_scope(frame);
                fcc->calling_scope = ce;
                if (scope && !fcc->object) {
-                       zend_object *object = zend_get_this_object(EG(current_execute_data));
+                       zend_object *object = zend_get_this_object(frame);
 
                        if (object &&
                            instanceof_function(object->ce, scope) &&
@@ -2945,7 +2944,7 @@ ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) {
        fcc->function_handler = NULL;
 }
 
-static zend_always_inline int zend_is_callable_check_func(int check_flags, zval *callable, zend_fcall_info_cache *fcc, int strict_class, char **error) /* {{{ */
+static zend_always_inline int zend_is_callable_check_func(int check_flags, zval *callable, zend_execute_data *frame, zend_fcall_info_cache *fcc, int strict_class, char **error) /* {{{ */
 {
        zend_class_entry *ce_org = fcc->calling_scope;
        int retval = 0;
@@ -3010,11 +3009,11 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
                if (ce_org) {
                        scope = ce_org;
                } else {
-                       scope = zend_get_executed_scope();
+                       scope = get_scope(frame);
                }
 
                cname = zend_string_init(Z_STRVAL_P(callable), clen, 0);
-               if (!zend_is_callable_check_class(cname, scope, fcc, &strict_class, error)) {
+               if (!zend_is_callable_check_class(cname, scope, frame, fcc, &strict_class, error)) {
                        zend_string_release_ex(cname, 0);
                        return 0;
                }
@@ -3053,7 +3052,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
                retval = 1;
                if ((fcc->function_handler->op_array.fn_flags & ZEND_ACC_CHANGED) &&
                    !strict_class) {
-                       scope = zend_get_executed_scope();
+                       scope = get_scope(frame);
                        if (scope &&
                            instanceof_function(fcc->function_handler->common.scope, scope)) {
 
@@ -3072,7 +3071,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
                    (fcc->calling_scope &&
                     ((fcc->object && fcc->calling_scope->__call) ||
                      (!fcc->object && fcc->calling_scope->__callstatic)))) {
-                       scope = zend_get_executed_scope();
+                       scope = get_scope(frame);
                        if (fcc->function_handler->common.scope != scope) {
                                if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
                                 || !zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope)) {
@@ -3112,7 +3111,7 @@ get_function_via_handler:
                                retval = 1;
                                call_via_handler = (fcc->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
                                if (call_via_handler && !fcc->object) {
-                                       zend_object *object = zend_get_this_object(EG(current_execute_data));
+                                       zend_object *object = zend_get_this_object(frame);
                                        if (object &&
                                            instanceof_function(object->ce, fcc->calling_scope)) {
                                                fcc->object = object;
@@ -3137,7 +3136,7 @@ get_function_via_handler:
                        }
                        if (retval
                         && !(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC)) {
-                               scope = zend_get_executed_scope();
+                               scope = get_scope(frame);
                                if (fcc->function_handler->common.scope != scope) {
                                        if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
                                         || (!zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope))) {
@@ -3227,7 +3226,9 @@ ZEND_API zend_string *zend_get_callable_name(zval *callable) /* {{{ */
 }
 /* }}} */
 
-static zend_always_inline zend_bool zend_is_callable_impl(zval *callable, zend_object *object, uint32_t check_flags, zend_fcall_info_cache *fcc, char **error) /* {{{ */
+static zend_always_inline zend_bool zend_is_callable_impl(
+               zval *callable, zend_object *object, zend_execute_data *frame,
+               uint32_t check_flags, zend_fcall_info_cache *fcc, char **error) /* {{{ */
 {
        zend_bool ret;
        zend_fcall_info_cache fcc_local;
@@ -3259,7 +3260,7 @@ again:
                        }
 
 check_func:
-                       ret = zend_is_callable_check_func(check_flags, callable, fcc, strict_class, error);
+                       ret = zend_is_callable_check_func(check_flags, callable, frame, fcc, strict_class, error);
                        if (fcc == &fcc_local) {
                                zend_release_fcall_info_cache(fcc);
                        }
@@ -3291,7 +3292,7 @@ check_func:
                                                        return 1;
                                                }
 
-                                               if (!zend_is_callable_check_class(Z_STR_P(obj), zend_get_executed_scope(), fcc, &strict_class, error)) {
+                                               if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error)) {
                                                        return 0;
                                                }
 
@@ -3348,7 +3349,13 @@ check_func:
 
 ZEND_API zend_bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error) /* {{{ */
 {
-       zend_bool ret = zend_is_callable_impl(callable, object, check_flags, fcc, error);
+       /* Determine callability at the first parent user frame. */
+       zend_execute_data *frame = EG(current_execute_data);
+       while (frame && (!frame->func || !ZEND_USER_CODE(frame->func->type))) {
+               frame = frame->prev_execute_data;
+       }
+
+       zend_bool ret = zend_is_callable_impl(callable, object, frame, check_flags, fcc, error);
        if (callable_name) {
                *callable_name = zend_get_callable_name_ex(callable, object);
        }
index 68ae1e47b0bf49220cbb14340c95f23c3be61a21..6b36bede37cbe95cdbc2d15fe1a2da7921592825 100644 (file)
@@ -332,7 +332,6 @@ static int zend_create_closure_from_callable(zval *return_value, zval *callable,
 ZEND_METHOD(Closure, fromCallable)
 {
        zval *callable;
-       int success;
        char *error = NULL;
 
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callable) == FAILURE) {
@@ -344,12 +343,7 @@ ZEND_METHOD(Closure, fromCallable)
                RETURN_COPY(callable);
        }
 
-       /* create closure as if it were called from parent scope */
-       EG(current_execute_data) = EX(prev_execute_data);
-       success = zend_create_closure_from_callable(return_value, callable, &error);
-       EG(current_execute_data) = execute_data;
-
-       if (success == FAILURE) {
+       if (zend_create_closure_from_callable(return_value, callable, &error) == FAILURE) {
                if (error) {
                        zend_type_error("Failed to create closure from callable: %s", error);
                        efree(error);
index bf71c1b62484c5f8303e94c960cf2fb21d38f9bf..b7df588fe9d5ab857dd466c5847e446b8b42598d 100644 (file)
@@ -17,6 +17,5 @@ final class Closure
 
     public function call(object $newThis, mixed ...$arguments): mixed {}
 
-    /** @param callable $callback callable is not a proper type due to bug #78770. */
-    public static function fromCallable($callback): Closure {}
+    public static function fromCallable(callable $callback): Closure {}
 }
index 56d2faec80256ec507ce9695ff60d4f5dc8c3fce..fe3407232b84e4d25845c440b6a8ec0f5f0b6ba6 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 62198e96940fe0e86fe89601015c837aa5390e92 */
+ * Stub hash: 124654da4652ea828875f471a2ddcc4afae147ae */
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0)
 ZEND_END_ARG_INFO()
@@ -21,7 +21,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Closure_call, 0, 1, IS_MIX
 ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Closure_fromCallable, 0, 1, Closure, 0)
-       ZEND_ARG_INFO(0, callback)
+       ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
 ZEND_END_ARG_INFO()