diff options
author | Nikita Popov <nikita.ppv@gmail.com> | 2020-08-14 10:22:42 +0200 |
---|---|---|
committer | Nikita Popov <nikita.ppv@gmail.com> | 2020-08-14 10:24:06 +0200 |
commit | befe10fd21c82e82d8621558b7998424ab6d2c6d (patch) | |
tree | 36f9fb1e5e55a544df65cb47edfc4a3a16d37d42 | |
parent | f83368c6d960329c17df764947e793a863379582 (diff) | |
download | php-git-befe10fd21c82e82d8621558b7998424ab6d2c6d.tar.gz |
Fix bug #78770
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.
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | Zend/tests/bug78770.phpt | 25 | ||||
-rw-r--r-- | Zend/zend_API.c | 59 | ||||
-rw-r--r-- | Zend/zend_closures.c | 8 | ||||
-rw-r--r-- | Zend/zend_closures.stub.php | 3 | ||||
-rw-r--r-- | Zend/zend_closures_arginfo.h | 4 |
6 files changed, 64 insertions, 37 deletions
@@ -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 index 0000000000..b1cf783b95 --- /dev/null +++ b/Zend/tests/bug78770.phpt @@ -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=== diff --git a/Zend/zend_API.c b/Zend/zend_API.c index b82e4fbc16..03b65779da 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -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); } diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 68ae1e47b0..6b36bede37 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -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); diff --git a/Zend/zend_closures.stub.php b/Zend/zend_closures.stub.php index bf71c1b624..b7df588fe9 100644 --- a/Zend/zend_closures.stub.php +++ b/Zend/zend_closures.stub.php @@ -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 {} } diff --git a/Zend/zend_closures_arginfo.h b/Zend/zend_closures_arginfo.h index 56d2faec80..fe3407232b 100644 --- a/Zend/zend_closures_arginfo.h +++ b/Zend/zend_closures_arginfo.h @@ -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() |