mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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.
This commit is contained in:
parent
f83368c6d9
commit
befe10fd21
6 changed files with 64 additions and 37 deletions
2
NEWS
2
NEWS
|
@ -23,6 +23,8 @@ PHP NEWS
|
||||||
exit code). (Nikita)
|
exit code). (Nikita)
|
||||||
. Fixed bug #79927 (Generator doesn't throw exception after multiple yield
|
. Fixed bug #79927 (Generator doesn't throw exception after multiple yield
|
||||||
from iterable). (Nikita)
|
from iterable). (Nikita)
|
||||||
|
. Fixed bug #78770 (Incorrect callability check inside internal methods).
|
||||||
|
(Nikita)
|
||||||
|
|
||||||
- Date:
|
- Date:
|
||||||
. Fixed bug #60302 (DateTime::createFromFormat should new static(), not new
|
. Fixed bug #60302 (DateTime::createFromFormat should new static(), not new
|
||||||
|
|
25
Zend/tests/bug78770.phpt
Normal file
25
Zend/tests/bug78770.phpt
Normal 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===
|
|
@ -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;
|
int ret = 0;
|
||||||
zend_class_entry *ce;
|
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 (!scope) {
|
||||||
if (error) *error = estrdup("cannot access \"self\" when no class scope is active");
|
if (error) *error = estrdup("cannot access \"self\" when no class scope is active");
|
||||||
} else {
|
} else {
|
||||||
fcc->called_scope = zend_get_called_scope(EG(current_execute_data));
|
fcc->called_scope = zend_get_called_scope(frame);
|
||||||
fcc->calling_scope = scope;
|
fcc->calling_scope = scope;
|
||||||
if (!fcc->object) {
|
if (!fcc->object) {
|
||||||
fcc->object = zend_get_this_object(EG(current_execute_data));
|
fcc->object = zend_get_this_object(frame);
|
||||||
}
|
}
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
|
@ -2879,16 +2884,16 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
|
||||||
} else if (!scope->parent) {
|
} else if (!scope->parent) {
|
||||||
if (error) *error = estrdup("cannot access \"parent\" when current class scope has no parent");
|
if (error) *error = estrdup("cannot access \"parent\" when current class scope has no parent");
|
||||||
} else {
|
} 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;
|
fcc->calling_scope = scope->parent;
|
||||||
if (!fcc->object) {
|
if (!fcc->object) {
|
||||||
fcc->object = zend_get_this_object(EG(current_execute_data));
|
fcc->object = zend_get_this_object(frame);
|
||||||
}
|
}
|
||||||
*strict_class = 1;
|
*strict_class = 1;
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
} else if (zend_string_equals_literal(lcname, "static")) {
|
} 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 (!called_scope) {
|
||||||
if (error) *error = estrdup("cannot access \"static\" when no class scope is active");
|
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->called_scope = called_scope;
|
||||||
fcc->calling_scope = called_scope;
|
fcc->calling_scope = called_scope;
|
||||||
if (!fcc->object) {
|
if (!fcc->object) {
|
||||||
fcc->object = zend_get_this_object(EG(current_execute_data));
|
fcc->object = zend_get_this_object(frame);
|
||||||
}
|
}
|
||||||
*strict_class = 1;
|
*strict_class = 1;
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
} else if ((ce = zend_lookup_class(name)) != NULL) {
|
} else if ((ce = zend_lookup_class(name)) != NULL) {
|
||||||
zend_class_entry *scope;
|
zend_class_entry *scope = get_scope(frame);
|
||||||
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;
|
|
||||||
fcc->calling_scope = ce;
|
fcc->calling_scope = ce;
|
||||||
if (scope && !fcc->object) {
|
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 &&
|
if (object &&
|
||||||
instanceof_function(object->ce, scope) &&
|
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;
|
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;
|
zend_class_entry *ce_org = fcc->calling_scope;
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
@ -3010,11 +3009,11 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
|
||||||
if (ce_org) {
|
if (ce_org) {
|
||||||
scope = ce_org;
|
scope = ce_org;
|
||||||
} else {
|
} else {
|
||||||
scope = zend_get_executed_scope();
|
scope = get_scope(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
cname = zend_string_init(Z_STRVAL_P(callable), clen, 0);
|
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);
|
zend_string_release_ex(cname, 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -3053,7 +3052,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
|
||||||
retval = 1;
|
retval = 1;
|
||||||
if ((fcc->function_handler->op_array.fn_flags & ZEND_ACC_CHANGED) &&
|
if ((fcc->function_handler->op_array.fn_flags & ZEND_ACC_CHANGED) &&
|
||||||
!strict_class) {
|
!strict_class) {
|
||||||
scope = zend_get_executed_scope();
|
scope = get_scope(frame);
|
||||||
if (scope &&
|
if (scope &&
|
||||||
instanceof_function(fcc->function_handler->common.scope, 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->calling_scope &&
|
||||||
((fcc->object && fcc->calling_scope->__call) ||
|
((fcc->object && fcc->calling_scope->__call) ||
|
||||||
(!fcc->object && fcc->calling_scope->__callstatic)))) {
|
(!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.scope != scope) {
|
||||||
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
|
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
|
||||||
|| !zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope)) {
|
|| !zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope)) {
|
||||||
|
@ -3112,7 +3111,7 @@ get_function_via_handler:
|
||||||
retval = 1;
|
retval = 1;
|
||||||
call_via_handler = (fcc->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
|
call_via_handler = (fcc->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
|
||||||
if (call_via_handler && !fcc->object) {
|
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 &&
|
if (object &&
|
||||||
instanceof_function(object->ce, fcc->calling_scope)) {
|
instanceof_function(object->ce, fcc->calling_scope)) {
|
||||||
fcc->object = object;
|
fcc->object = object;
|
||||||
|
@ -3137,7 +3136,7 @@ get_function_via_handler:
|
||||||
}
|
}
|
||||||
if (retval
|
if (retval
|
||||||
&& !(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC)) {
|
&& !(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.scope != scope) {
|
||||||
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
|
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
|
||||||
|| (!zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope))) {
|
|| (!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_bool ret;
|
||||||
zend_fcall_info_cache fcc_local;
|
zend_fcall_info_cache fcc_local;
|
||||||
|
@ -3259,7 +3260,7 @@ again:
|
||||||
}
|
}
|
||||||
|
|
||||||
check_func:
|
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) {
|
if (fcc == &fcc_local) {
|
||||||
zend_release_fcall_info_cache(fcc);
|
zend_release_fcall_info_cache(fcc);
|
||||||
}
|
}
|
||||||
|
@ -3291,7 +3292,7 @@ check_func:
|
||||||
return 1;
|
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;
|
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_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) {
|
if (callable_name) {
|
||||||
*callable_name = zend_get_callable_name_ex(callable, object);
|
*callable_name = zend_get_callable_name_ex(callable, object);
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,7 +332,6 @@ static int zend_create_closure_from_callable(zval *return_value, zval *callable,
|
||||||
ZEND_METHOD(Closure, fromCallable)
|
ZEND_METHOD(Closure, fromCallable)
|
||||||
{
|
{
|
||||||
zval *callable;
|
zval *callable;
|
||||||
int success;
|
|
||||||
char *error = NULL;
|
char *error = NULL;
|
||||||
|
|
||||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callable) == FAILURE) {
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callable) == FAILURE) {
|
||||||
|
@ -344,12 +343,7 @@ ZEND_METHOD(Closure, fromCallable)
|
||||||
RETURN_COPY(callable);
|
RETURN_COPY(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create closure as if it were called from parent scope */
|
if (zend_create_closure_from_callable(return_value, callable, &error) == FAILURE) {
|
||||||
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 (error) {
|
if (error) {
|
||||||
zend_type_error("Failed to create closure from callable: %s", error);
|
zend_type_error("Failed to create closure from callable: %s", error);
|
||||||
efree(error);
|
efree(error);
|
||||||
|
|
|
@ -17,6 +17,5 @@ final class Closure
|
||||||
|
|
||||||
public function call(object $newThis, mixed ...$arguments): mixed {}
|
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(callable $callback): Closure {}
|
||||||
public static function fromCallable($callback): Closure {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* This is a generated file, edit the .stub.php file instead.
|
/* 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_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0)
|
||||||
ZEND_END_ARG_INFO()
|
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_END_ARG_INFO()
|
||||||
|
|
||||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Closure_fromCallable, 0, 1, Closure, 0)
|
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()
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue