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.
This commit is contained in:
Nikita Popov 2020-08-14 10:22:42 +02:00
parent f83368c6d9
commit befe10fd21
6 changed files with 64 additions and 37 deletions

2
NEWS
View file

@ -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

25
Zend/tests/bug78770.phpt Normal file
View file

@ -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===

View 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);
}

View 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);

View 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 {}
}

View 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()