summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2020-08-14 10:22:42 +0200
committerNikita Popov <nikita.ppv@gmail.com>2020-08-14 10:24:06 +0200
commitbefe10fd21c82e82d8621558b7998424ab6d2c6d (patch)
tree36f9fb1e5e55a544df65cb47edfc4a3a16d37d42
parentf83368c6d960329c17df764947e793a863379582 (diff)
downloadphp-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--NEWS2
-rw-r--r--Zend/tests/bug78770.phpt25
-rw-r--r--Zend/zend_API.c59
-rw-r--r--Zend/zend_closures.c8
-rw-r--r--Zend/zend_closures.stub.php3
-rw-r--r--Zend/zend_closures_arginfo.h4
6 files changed, 64 insertions, 37 deletions
diff --git a/NEWS b/NEWS
index cd163a5f6c..c0d14769a2 100644
--- 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
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()