Skip to content

Commit befe10f

Browse files
committed
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.
1 parent f83368c commit befe10f

File tree

6 files changed

+64
-37
lines changed

6 files changed

+64
-37
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ PHP NEWS
2323
exit code). (Nikita)
2424
. Fixed bug #79927 (Generator doesn't throw exception after multiple yield
2525
from iterable). (Nikita)
26+
. Fixed bug #78770 (Incorrect callability check inside internal methods).
27+
(Nikita)
2628

2729
- Date:
2830
. Fixed bug #60302 (DateTime::createFromFormat should new static(), not new

Zend/tests/bug78770.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Bug #78770: Incorrect callability check inside internal methods
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("intl")) die("skip requires intl");
6+
?>
7+
--FILE--
8+
<?php
9+
10+
class Test {
11+
public function method() {
12+
IntlChar::enumCharTypes([$this, 'privateMethod']);
13+
IntlChar::enumCharTypes('self::privateMethod');
14+
}
15+
16+
private function privateMethod($start, $end, $name) {
17+
}
18+
}
19+
20+
(new Test)->method();
21+
22+
?>
23+
===DONE===
24+
--EXPECT--
25+
===DONE===

Zend/zend_API.c

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2850,7 +2850,12 @@ ZEND_API int zend_disable_class(const char *class_name, size_t class_name_length
28502850
}
28512851
/* }}} */
28522852

2853-
static int zend_is_callable_check_class(zend_string *name, zend_class_entry *scope, zend_fcall_info_cache *fcc, int *strict_class, char **error) /* {{{ */
2853+
static zend_always_inline zend_class_entry *get_scope(zend_execute_data *frame)
2854+
{
2855+
return frame && frame->func ? frame->func->common.scope : NULL;
2856+
}
2857+
2858+
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) /* {{{ */
28542859
{
28552860
int ret = 0;
28562861
zend_class_entry *ce;
@@ -2866,10 +2871,10 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
28662871
if (!scope) {
28672872
if (error) *error = estrdup("cannot access \"self\" when no class scope is active");
28682873
} else {
2869-
fcc->called_scope = zend_get_called_scope(EG(current_execute_data));
2874+
fcc->called_scope = zend_get_called_scope(frame);
28702875
fcc->calling_scope = scope;
28712876
if (!fcc->object) {
2872-
fcc->object = zend_get_this_object(EG(current_execute_data));
2877+
fcc->object = zend_get_this_object(frame);
28732878
}
28742879
ret = 1;
28752880
}
@@ -2879,39 +2884,33 @@ static int zend_is_callable_check_class(zend_string *name, zend_class_entry *sco
28792884
} else if (!scope->parent) {
28802885
if (error) *error = estrdup("cannot access \"parent\" when current class scope has no parent");
28812886
} else {
2882-
fcc->called_scope = zend_get_called_scope(EG(current_execute_data));
2887+
fcc->called_scope = zend_get_called_scope(frame);
28832888
fcc->calling_scope = scope->parent;
28842889
if (!fcc->object) {
2885-
fcc->object = zend_get_this_object(EG(current_execute_data));
2890+
fcc->object = zend_get_this_object(frame);
28862891
}
28872892
*strict_class = 1;
28882893
ret = 1;
28892894
}
28902895
} else if (zend_string_equals_literal(lcname, "static")) {
2891-
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
2896+
zend_class_entry *called_scope = zend_get_called_scope(frame);
28922897

28932898
if (!called_scope) {
28942899
if (error) *error = estrdup("cannot access \"static\" when no class scope is active");
28952900
} else {
28962901
fcc->called_scope = called_scope;
28972902
fcc->calling_scope = called_scope;
28982903
if (!fcc->object) {
2899-
fcc->object = zend_get_this_object(EG(current_execute_data));
2904+
fcc->object = zend_get_this_object(frame);
29002905
}
29012906
*strict_class = 1;
29022907
ret = 1;
29032908
}
29042909
} else if ((ce = zend_lookup_class(name)) != NULL) {
2905-
zend_class_entry *scope;
2906-
zend_execute_data *ex = EG(current_execute_data);
2907-
2908-
while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) {
2909-
ex = ex->prev_execute_data;
2910-
}
2911-
scope = ex ? ex->func->common.scope : NULL;
2910+
zend_class_entry *scope = get_scope(frame);
29122911
fcc->calling_scope = ce;
29132912
if (scope && !fcc->object) {
2914-
zend_object *object = zend_get_this_object(EG(current_execute_data));
2913+
zend_object *object = zend_get_this_object(frame);
29152914

29162915
if (object &&
29172916
instanceof_function(object->ce, scope) &&
@@ -2945,7 +2944,7 @@ ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) {
29452944
fcc->function_handler = NULL;
29462945
}
29472946

2948-
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) /* {{{ */
2947+
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) /* {{{ */
29492948
{
29502949
zend_class_entry *ce_org = fcc->calling_scope;
29512950
int retval = 0;
@@ -3010,11 +3009,11 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
30103009
if (ce_org) {
30113010
scope = ce_org;
30123011
} else {
3013-
scope = zend_get_executed_scope();
3012+
scope = get_scope(frame);
30143013
}
30153014

30163015
cname = zend_string_init(Z_STRVAL_P(callable), clen, 0);
3017-
if (!zend_is_callable_check_class(cname, scope, fcc, &strict_class, error)) {
3016+
if (!zend_is_callable_check_class(cname, scope, frame, fcc, &strict_class, error)) {
30183017
zend_string_release_ex(cname, 0);
30193018
return 0;
30203019
}
@@ -3053,7 +3052,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
30533052
retval = 1;
30543053
if ((fcc->function_handler->op_array.fn_flags & ZEND_ACC_CHANGED) &&
30553054
!strict_class) {
3056-
scope = zend_get_executed_scope();
3055+
scope = get_scope(frame);
30573056
if (scope &&
30583057
instanceof_function(fcc->function_handler->common.scope, scope)) {
30593058

@@ -3072,7 +3071,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
30723071
(fcc->calling_scope &&
30733072
((fcc->object && fcc->calling_scope->__call) ||
30743073
(!fcc->object && fcc->calling_scope->__callstatic)))) {
3075-
scope = zend_get_executed_scope();
3074+
scope = get_scope(frame);
30763075
if (fcc->function_handler->common.scope != scope) {
30773076
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
30783077
|| !zend_check_protected(zend_get_function_root_class(fcc->function_handler), scope)) {
@@ -3112,7 +3111,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
31123111
retval = 1;
31133112
call_via_handler = (fcc->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
31143113
if (call_via_handler && !fcc->object) {
3115-
zend_object *object = zend_get_this_object(EG(current_execute_data));
3114+
zend_object *object = zend_get_this_object(frame);
31163115
if (object &&
31173116
instanceof_function(object->ce, fcc->calling_scope)) {
31183117
fcc->object = object;
@@ -3137,7 +3136,7 @@ static zend_always_inline int zend_is_callable_check_func(int check_flags, zval
31373136
}
31383137
if (retval
31393138
&& !(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC)) {
3140-
scope = zend_get_executed_scope();
3139+
scope = get_scope(frame);
31413140
if (fcc->function_handler->common.scope != scope) {
31423141
if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE)
31433142
|| (!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) /* {{{ */
32273226
}
32283227
/* }}} */
32293228

3230-
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) /* {{{ */
3229+
static zend_always_inline zend_bool zend_is_callable_impl(
3230+
zval *callable, zend_object *object, zend_execute_data *frame,
3231+
uint32_t check_flags, zend_fcall_info_cache *fcc, char **error) /* {{{ */
32313232
{
32323233
zend_bool ret;
32333234
zend_fcall_info_cache fcc_local;
@@ -3259,7 +3260,7 @@ static zend_always_inline zend_bool zend_is_callable_impl(zval *callable, zend_o
32593260
}
32603261

32613262
check_func:
3262-
ret = zend_is_callable_check_func(check_flags, callable, fcc, strict_class, error);
3263+
ret = zend_is_callable_check_func(check_flags, callable, frame, fcc, strict_class, error);
32633264
if (fcc == &fcc_local) {
32643265
zend_release_fcall_info_cache(fcc);
32653266
}
@@ -3291,7 +3292,7 @@ static zend_always_inline zend_bool zend_is_callable_impl(zval *callable, zend_o
32913292
return 1;
32923293
}
32933294

3294-
if (!zend_is_callable_check_class(Z_STR_P(obj), zend_get_executed_scope(), fcc, &strict_class, error)) {
3295+
if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error)) {
32953296
return 0;
32963297
}
32973298

@@ -3348,7 +3349,13 @@ static zend_always_inline zend_bool zend_is_callable_impl(zval *callable, zend_o
33483349

33493350
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) /* {{{ */
33503351
{
3351-
zend_bool ret = zend_is_callable_impl(callable, object, check_flags, fcc, error);
3352+
/* Determine callability at the first parent user frame. */
3353+
zend_execute_data *frame = EG(current_execute_data);
3354+
while (frame && (!frame->func || !ZEND_USER_CODE(frame->func->type))) {
3355+
frame = frame->prev_execute_data;
3356+
}
3357+
3358+
zend_bool ret = zend_is_callable_impl(callable, object, frame, check_flags, fcc, error);
33523359
if (callable_name) {
33533360
*callable_name = zend_get_callable_name_ex(callable, object);
33543361
}

Zend/zend_closures.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ static int zend_create_closure_from_callable(zval *return_value, zval *callable,
332332
ZEND_METHOD(Closure, fromCallable)
333333
{
334334
zval *callable;
335-
int success;
336335
char *error = NULL;
337336

338337
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callable) == FAILURE) {
@@ -344,12 +343,7 @@ ZEND_METHOD(Closure, fromCallable)
344343
RETURN_COPY(callable);
345344
}
346345

347-
/* create closure as if it were called from parent scope */
348-
EG(current_execute_data) = EX(prev_execute_data);
349-
success = zend_create_closure_from_callable(return_value, callable, &error);
350-
EG(current_execute_data) = execute_data;
351-
352-
if (success == FAILURE) {
346+
if (zend_create_closure_from_callable(return_value, callable, &error) == FAILURE) {
353347
if (error) {
354348
zend_type_error("Failed to create closure from callable: %s", error);
355349
efree(error);

Zend/zend_closures.stub.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ public function bindTo(?object $newThis, $newScope = UNKNOWN): ?Closure {}
1717

1818
public function call(object $newThis, mixed ...$arguments): mixed {}
1919

20-
/** @param callable $callback callable is not a proper type due to bug #78770. */
21-
public static function fromCallable($callback): Closure {}
20+
public static function fromCallable(callable $callback): Closure {}
2221
}

Zend/zend_closures_arginfo.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 62198e96940fe0e86fe89601015c837aa5390e92 */
2+
* Stub hash: 124654da4652ea828875f471a2ddcc4afae147ae */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0)
55
ZEND_END_ARG_INFO()
@@ -21,7 +21,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Closure_call, 0, 1, IS_MIX
2121
ZEND_END_ARG_INFO()
2222

2323
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Closure_fromCallable, 0, 1, Closure, 0)
24-
ZEND_ARG_INFO(0, callback)
24+
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
2525
ZEND_END_ARG_INFO()
2626

2727

0 commit comments

Comments
 (0)