From 1ef6e2808d6d86af58e703140b412ec6cc7a9342 Mon Sep 17 00:00:00 2001 From: Rowan Tommins Date: Wed, 8 Jun 2022 18:16:22 +0100 Subject: [PATCH] Extend deprecation notices to is_callable($foo) and callable $foo Implements https://wiki.php.net/rfc/partially-supported-callables-expand-deprecation-notices so that uses of "self" and "parent" in is_callable() and callable type constraints now raise a deprecation notice, independent of the one raised when and if the callable is actually invoked. A new flag is added to the existing check_flags parameter of zend_is_callable / zend_is_callable_ex, for use in internal calls that would otherwise repeat the notice multiple times. In particular, arguments to internal function calls are checked first based on arginfo, and then again during ZPP, so the former suppresses the deprecation notice. Some existing tests which raised this deprecation have been updated to avoid the syntax, but the existing version retained for maximum regression coverage until it is made an error. With thanks to Juliette Reinders Folmer for the RFC and initial investigation. --- UPGRADING | 5 +- UPGRADING.INTERNALS | 5 + Zend/tests/bug48899-deprecated.phpt | 27 ++ Zend/tests/bug48899.phpt | 4 +- Zend/tests/bug71622.phpt | 2 +- ...llable_self_parent_static_deprecation.phpt | 273 ++++++++++++++++-- ...is_callable_trampoline_uaf-deprecated.phpt | 28 ++ Zend/tests/is_callable_trampoline_uaf.phpt | 2 +- Zend/tests/lsb_013.phpt | 9 +- Zend/tests/traits/bug76773-deprecated.phpt | 35 +++ Zend/tests/traits/bug76773.phpt | 2 +- Zend/zend_API.c | 18 +- Zend/zend_API.h | 1 + Zend/zend_execute.c | 3 +- Zend/zend_execute_API.c | 2 +- ext/filter/callback_filter.c | 2 +- ext/session/session.c | 2 +- ext/spl/php_spl.c | 2 +- ext/standard/tests/bug75220.phpt | 6 +- ...s_callable_abstract_method-deprecated.phpt | 20 ++ .../is_callable_abstract_method.phpt | 8 +- main/streams/userspace.c | 2 +- 22 files changed, 409 insertions(+), 49 deletions(-) create mode 100644 Zend/tests/bug48899-deprecated.phpt create mode 100644 Zend/tests/is_callable_trampoline_uaf-deprecated.phpt create mode 100644 Zend/tests/traits/bug76773-deprecated.phpt create mode 100644 ext/standard/tests/general_functions/is_callable_abstract_method-deprecated.phpt diff --git a/UPGRADING b/UPGRADING index fcdf38d9a7bba..6d58ba57e1594 100644 --- a/UPGRADING +++ b/UPGRADING @@ -134,11 +134,10 @@ PHP 8.2 UPGRADE NOTES [new Foo, "Bar::method"] This does not affect normal method callables like "A::method" or - ["A", "method"]. A deprecation notice is only emitted on call. Both - is_callable() and the callable type will silently accept these callables - until support for them is removed entirely. + ["A", "method"]. RFC: https://wiki.php.net/rfc/deprecate_partially_supported_callables + RFC: https://wiki.php.net/rfc/partially-supported-callables-expand-deprecation-notices . The "${var}" and "${expr}" style string interpolations are deprecated and will be removed in PHP 9. Use "$var"/"{$var}" or "{${expr}}", respectively. diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index e0aebcbb6b522..a52196dc60997 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -46,6 +46,11 @@ PHP 8.2 INTERNALS UPGRADE NOTES smart_str_trim_to_size() before returning the string. * It is recommended to use smart_str_extract() or smart_str_trim_to_size() when using the smart_str API. +* zend_is_callable_ex, and functions which call it such as zend_is_callable and + zend_fcall_info_init, will issue deprecation notices if passed values which + are deprecated (see main UPGRADING notes). To suppress the notice, e.g. to + avoid duplicates when processing the same value multiple times, pass or add + IS_CALLABLE_SUPPRESS_DEPRECATIONS to the check_flags parameter. ======================== 2. Build system changes diff --git a/Zend/tests/bug48899-deprecated.phpt b/Zend/tests/bug48899-deprecated.phpt new file mode 100644 index 0000000000000..d731d57849c3e --- /dev/null +++ b/Zend/tests/bug48899-deprecated.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #48899 (is_callable returns true even if method does not exist in parent class) [original test with deprecated syntax] +--FILE-- +testIsCallable(); +$child->testIsCallable2(); + +?> +--EXPECTF-- +Deprecated: Callables of the form ["ChildClass", "parent::testIsCallable"] are deprecated in %s on line %d +bool(false) + +Deprecated: Callables of the form ["ChildClass", "static::testIsCallable2"] are deprecated in %s on line %d +bool(true) diff --git a/Zend/tests/bug48899.phpt b/Zend/tests/bug48899.phpt index 2cbf308c3eddc..6edf3076ab09f 100644 --- a/Zend/tests/bug48899.phpt +++ b/Zend/tests/bug48899.phpt @@ -7,10 +7,10 @@ class ParentClass { } class ChildClass extends ParentClass { public function testIsCallable() { - var_dump(is_callable(array($this, 'parent::testIsCallable'))); + var_dump(is_callable(array('ParentClass', 'testIsCallable'))); } public function testIsCallable2() { - var_dump(is_callable(array($this, 'static::testIsCallable2'))); + var_dump(is_callable(array('ChildClass', 'testIsCallable2'))); } } diff --git a/Zend/tests/bug71622.phpt b/Zend/tests/bug71622.phpt index c23547788a034..59d4101df6837 100644 --- a/Zend/tests/bug71622.phpt +++ b/Zend/tests/bug71622.phpt @@ -17,7 +17,7 @@ class Abc { public static function run() { $method = "foobar"; getMethodName($method); - var_dump(is_callable("self::$method")); + var_dump(is_callable("Abc::$method")); self::$method(); } } diff --git a/Zend/tests/callable_self_parent_static_deprecation.phpt b/Zend/tests/callable_self_parent_static_deprecation.phpt index a6696d234bc50..d01713d429f7e 100644 --- a/Zend/tests/callable_self_parent_static_deprecation.phpt +++ b/Zend/tests/callable_self_parent_static_deprecation.phpt @@ -9,27 +9,52 @@ class A { class B extends A { public function test() { // Different callables using self/parent/static - echo "Test different callables\n"; - call_user_func("self::foo"); - call_user_func("parent::foo"); - call_user_func("static::foo"); - call_user_func(["self", "foo"]); - call_user_func(["parent", "foo"]); - call_user_func(["static", "foo"]); - call_user_func(["B", "self::foo"]); - call_user_func(["B", "parent::foo"]); - call_user_func(["B", "static::foo"]); - call_user_func(["B", "A::foo"]); + $variants = [ + '"self::foo"' => "self::foo", + '"parent::foo"' => "parent::foo", + '"static::foo"' => "static::foo", + '["self", "foo"]' => ["self", "foo"], + '["parent", "foo"]' => ["parent", "foo"], + '["static", "foo"]' => ["static", "foo"], + '["B", "self::foo"]' => ["B", "self::foo"], + '["B", "parent::foo"]' => ["B", "parent::foo"], + '["B", "static::foo"]' => ["B", "static::foo"], + '["B", "A::foo"]' => ["B", "A::foo"], + '[$this, "self::foo"]' => [$this, "self::foo"], + '[$this, "parent::foo"]' => [$this, "parent::foo"], + '[$this, "static::foo"]' => [$this, "static::foo"], + '[$this, "A::foo"]' => [$this, "A::foo"], + ]; + + echo "==> Test call_user_func\n"; + foreach ($variants as $description => $callable) { + echo "$description\n"; + call_user_func($callable); + } + echo "\n==> Test call_user_func_array\n"; + foreach ($variants as $description => $callable) { + echo "$description\n"; + call_user_func_array($callable, []); + } // Also applies to other things performing calls - echo "Test array_map()\n"; - array_map("self::foo", [1]); + echo "\n==> Test array_map\n"; + foreach ($variants as $description => $callable) { + echo "$description\n"; + array_map($callable, [1]); + } - echo "Test is_callable() -- should be silent\n"; - var_dump(is_callable("self::foo")); + echo "\n==> Test is_callable()\n"; + foreach ($variants as $description => $callable) { + echo "$description\n"; + var_dump(is_callable($callable)); + } - echo "Test callable type hint -- should be silent\n"; - $this->callableTypeHint("self::foo"); + echo "\n==> Test callable type hint\n"; + foreach ($variants as $description => $callable) { + echo "$description\n"; + $this->callableTypeHint($callable); + } } public function callableTypeHint(callable $c) {} @@ -40,30 +65,236 @@ $b->test(); ?> --EXPECTF-- -Test different callables +==> Test call_user_func +"self::foo" + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +"parent::foo" + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +"static::foo" + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["self", "foo"] + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +["parent", "foo"] + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +["static", "foo"] + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["B", "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +["B", "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +["B", "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +["B", "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +[$this, "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +[$this, "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +[$this, "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +[$this, "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d + +==> Test call_user_func_array +"self::foo" + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +"parent::foo" + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +"static::foo" + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["self", "foo"] + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +["parent", "foo"] + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +["static", "foo"] + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["B", "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +["B", "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +["B", "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +["B", "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +[$this, "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +[$this, "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +[$this, "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +[$this, "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d + +==> Test array_map +"self::foo" Deprecated: Use of "self" in callables is deprecated in %s on line %d +"parent::foo" Deprecated: Use of "parent" in callables is deprecated in %s on line %d +"static::foo" Deprecated: Use of "static" in callables is deprecated in %s on line %d +["self", "foo"] Deprecated: Use of "self" in callables is deprecated in %s on line %d +["parent", "foo"] Deprecated: Use of "parent" in callables is deprecated in %s on line %d +["static", "foo"] Deprecated: Use of "static" in callables is deprecated in %s on line %d +["B", "self::foo"] Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +["B", "parent::foo"] Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +["B", "static::foo"] Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +["B", "A::foo"] Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d -Test array_map() +[$this, "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +[$this, "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +[$this, "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +[$this, "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d + +==> Test is_callable() +"self::foo" Deprecated: Use of "self" in callables is deprecated in %s on line %d -Test is_callable() -- should be silent bool(true) -Test callable type hint -- should be silent +"parent::foo" + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +bool(true) +"static::foo" + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +bool(true) +["self", "foo"] + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +bool(true) +["parent", "foo"] + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +bool(true) +["static", "foo"] + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +bool(true) +["B", "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +bool(true) +["B", "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +bool(true) +["B", "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +bool(true) +["B", "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +bool(true) +[$this, "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +bool(true) +[$this, "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +bool(true) +[$this, "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +bool(true) +[$this, "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +bool(true) + +==> Test callable type hint +"self::foo" + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +"parent::foo" + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +"static::foo" + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["self", "foo"] + +Deprecated: Use of "self" in callables is deprecated in %s on line %d +["parent", "foo"] + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +["static", "foo"] + +Deprecated: Use of "static" in callables is deprecated in %s on line %d +["B", "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +["B", "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +["B", "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +["B", "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +[$this, "self::foo"] + +Deprecated: Callables of the form ["B", "self::foo"] are deprecated in %s on line %d +[$this, "parent::foo"] + +Deprecated: Callables of the form ["B", "parent::foo"] are deprecated in %s on line %d +[$this, "static::foo"] + +Deprecated: Callables of the form ["B", "static::foo"] are deprecated in %s on line %d +[$this, "A::foo"] + +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d diff --git a/Zend/tests/is_callable_trampoline_uaf-deprecated.phpt b/Zend/tests/is_callable_trampoline_uaf-deprecated.phpt new file mode 100644 index 0000000000000..fc853e471973c --- /dev/null +++ b/Zend/tests/is_callable_trampoline_uaf-deprecated.phpt @@ -0,0 +1,28 @@ +--TEST-- +is_callable() with trampoline should not caused UAF [original test with deprecated syntax] +--FILE-- +bar('foo')); + +?> +--EXPECTF-- +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +bool(false) diff --git a/Zend/tests/is_callable_trampoline_uaf.phpt b/Zend/tests/is_callable_trampoline_uaf.phpt index 2410864410ab4..a36a995b8d673 100644 --- a/Zend/tests/is_callable_trampoline_uaf.phpt +++ b/Zend/tests/is_callable_trampoline_uaf.phpt @@ -6,7 +6,7 @@ is_callable() with trampoline should not caused UAF class B {} class A extends B { public function bar($func) { - var_dump(is_callable(array('parent', 'foo'))); + var_dump(is_callable(array('B', 'foo'))); } public function __call($func, $args) { diff --git a/Zend/tests/lsb_013.phpt b/Zend/tests/lsb_013.phpt index 3af7f1bea7eb0..c973db9059633 100644 --- a/Zend/tests/lsb_013.phpt +++ b/Zend/tests/lsb_013.phpt @@ -17,8 +17,15 @@ class Test2 extends Test1 { Test1::test(); Test2::test(); ?> ---EXPECT-- +--EXPECTF-- +Deprecated: Use of "static" in callables is deprecated in %s on line %d bool(false) + +Deprecated: Use of "static" in callables is deprecated in %s on line %d bool(false) + +Deprecated: Use of "static" in callables is deprecated in %s on line %d bool(true) + +Deprecated: Use of "static" in callables is deprecated in %s on line %d bool(true) diff --git a/Zend/tests/traits/bug76773-deprecated.phpt b/Zend/tests/traits/bug76773-deprecated.phpt new file mode 100644 index 0000000000000..e6dac201fc758 --- /dev/null +++ b/Zend/tests/traits/bug76773-deprecated.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #76773 (Traits used on the parent are ignored for child classes) [original test with deprecated syntax] +--FILE-- +hello(); +?> +--EXPECTF-- +ChildClass + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +ParentClass diff --git a/Zend/tests/traits/bug76773.phpt b/Zend/tests/traits/bug76773.phpt index 4ab53bf6d11d2..50b314794291d 100644 --- a/Zend/tests/traits/bug76773.phpt +++ b/Zend/tests/traits/bug76773.phpt @@ -9,7 +9,7 @@ trait MyTrait { echo __CLASS__, "\n"; - if (\is_callable(array('parent', __FUNCTION__))) { + if (get_parent_class(__CLASS__) !== false) { parent::hello(); } } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e2a77704733c3..6fa5e62dcd090 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3412,7 +3412,7 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc if (!scope) { if (error) *error = estrdup("cannot access \"self\" when no class scope is active"); } else { - if (error && !suppress_deprecation) { + if (!suppress_deprecation) { zend_error(E_DEPRECATED, "Use of \"self\" in callables is deprecated"); } fcc->called_scope = zend_get_called_scope(frame); @@ -3431,7 +3431,7 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc } else if (!scope->parent) { if (error) *error = estrdup("cannot access \"parent\" when current class scope has no parent"); } else { - if (error && !suppress_deprecation) { + if (!suppress_deprecation) { zend_error(E_DEPRECATED, "Use of \"parent\" in callables is deprecated"); } fcc->called_scope = zend_get_called_scope(frame); @@ -3451,7 +3451,7 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc if (!called_scope) { if (error) *error = estrdup("cannot access \"static\" when no class scope is active"); } else { - if (error && !suppress_deprecation) { + if (!suppress_deprecation) { zend_error(E_DEPRECATED, "Use of \"static\" in callables is deprecated"); } fcc->called_scope = called_scope; @@ -3500,7 +3500,7 @@ ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) { } } -static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_execute_data *frame, zend_fcall_info_cache *fcc, bool strict_class, char **error) /* {{{ */ +static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_execute_data *frame, zend_fcall_info_cache *fcc, bool strict_class, char **error, bool suppress_deprecation) /* {{{ */ { zend_class_entry *ce_org = fcc->calling_scope; bool retval = 0; @@ -3586,7 +3586,7 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ fcc->called_scope = fcc->object ? fcc->object->ce : fcc->calling_scope; } strict_class = 1; - } else if (!zend_is_callable_check_class(cname, scope, frame, fcc, &strict_class, error, ce_org != NULL)) { + } else if (!zend_is_callable_check_class(cname, scope, frame, fcc, &strict_class, error, suppress_deprecation || ce_org != NULL)) { zend_string_release_ex(cname, 0); return 0; } @@ -3597,7 +3597,7 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ if (error) zend_spprintf(error, 0, "class %s is not a subclass of %s", ZSTR_VAL(ce_org->name), ZSTR_VAL(fcc->calling_scope->name)); return 0; } - if (ce_org && error) { + if (ce_org && !suppress_deprecation) { zend_error(E_DEPRECATED, "Callables of the form [\"%s\", \"%s\"] are deprecated", ZSTR_VAL(ce_org->name), Z_STRVAL_P(callable)); @@ -3838,7 +3838,7 @@ ZEND_API bool zend_is_callable_at_frame( } check_func: - ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error); + ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS); if (fcc == &fcc_local) { zend_release_fcall_info_cache(fcc); } @@ -3875,7 +3875,7 @@ ZEND_API bool zend_is_callable_at_frame( return 1; } - if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error, false)) { + if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS)) { return 0; } } else { @@ -3938,7 +3938,7 @@ ZEND_API bool zend_make_callable(zval *callable, zend_string **callable_name) /* { zend_fcall_info_cache fcc; - if (zend_is_callable_ex(callable, NULL, 0, callable_name, &fcc, NULL)) { + if (zend_is_callable_ex(callable, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, callable_name, &fcc, NULL)) { if (Z_TYPE_P(callable) == IS_STRING && fcc.calling_scope) { zval_ptr_dtor_str(callable); array_init(callable); diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 5796c4f472ca4..af844d0a87ba7 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -393,6 +393,7 @@ ZEND_API zend_result zend_disable_class(const char *class_name, size_t class_nam ZEND_API ZEND_COLD void zend_wrong_param_count(void); #define IS_CALLABLE_CHECK_SYNTAX_ONLY (1<<0) +#define IS_CALLABLE_SUPPRESS_DEPRECATIONS (1<<1) ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc); ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, zend_object *object); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6891ab7b9eb80..46f88a1dbe6b4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1099,7 +1099,8 @@ static zend_always_inline bool zend_check_type_slow( } type_mask = ZEND_TYPE_FULL_MASK(*type); - if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { + if ((type_mask & MAY_BE_CALLABLE) && + zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { return 1; } if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) { diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9822ef692a83d..1ac9712938d4e 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1050,7 +1050,7 @@ ZEND_API zend_result zend_call_method_if_exists( fci.named_params = NULL; zend_fcall_info_cache fcc; - if (!zend_is_callable_ex(&fci.function_name, fci.object, 0, NULL, &fcc, NULL)) { + if (!zend_is_callable_ex(&fci.function_name, fci.object, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL)) { ZVAL_UNDEF(retval); return FAILURE; } diff --git a/ext/filter/callback_filter.c b/ext/filter/callback_filter.c index 290fdf26fe3c1..067f16a03ea24 100644 --- a/ext/filter/callback_filter.c +++ b/ext/filter/callback_filter.c @@ -22,7 +22,7 @@ void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL) zval args[1]; int status; - if (!option_array || !zend_is_callable(option_array, 0, NULL)) { + if (!option_array || !zend_is_callable(option_array, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL)) { zend_type_error("%s(): Option must be a valid callback", get_active_function_name()); zval_ptr_dtor(value); ZVAL_NULL(value); diff --git a/ext/session/session.c b/ext/session/session.c index cbfbda1941bba..75668d53e546c 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -2098,7 +2098,7 @@ PHP_FUNCTION(session_set_save_handler) /* At this point argc can only be between 6 and PS_NUM_APIS */ for (i = 0; i < argc; i++) { - if (!zend_is_callable(&args[i], 0, NULL)) { + if (!zend_is_callable(&args[i], IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL)) { zend_string *name = zend_get_callable_name(&args[i]); zend_argument_type_error(i + 1, "must be a valid callback, function \"%s\" not found or invalid function name", ZSTR_VAL(name)); zend_string_release(name); diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 839e978ec61ad..a1ae90367a22c 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -521,7 +521,7 @@ PHP_FUNCTION(spl_autoload_register) /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal * with it outselves. It is important that it is not refetched on every call, * because calls may occur from different scopes. */ - zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); + zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL); } if (fcc.function_handler->type == ZEND_INTERNAL_FUNCTION && diff --git a/ext/standard/tests/bug75220.phpt b/ext/standard/tests/bug75220.phpt index f0ba55a702380..cdc88d4c98c6f 100644 --- a/ext/standard/tests/bug75220.phpt +++ b/ext/standard/tests/bug75220.phpt @@ -22,7 +22,11 @@ class A extends B }; ?> ---EXPECT-- +--EXPECTF-- string(3) "foo" + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d bool(false) + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d bool(false) diff --git a/ext/standard/tests/general_functions/is_callable_abstract_method-deprecated.phpt b/ext/standard/tests/general_functions/is_callable_abstract_method-deprecated.phpt new file mode 100644 index 0000000000000..4853523993424 --- /dev/null +++ b/ext/standard/tests/general_functions/is_callable_abstract_method-deprecated.phpt @@ -0,0 +1,20 @@ +--TEST-- +is_callable() on abstract method via object should return false [original test with deprecated syntax] +--FILE-- + +--EXPECTF-- +Deprecated: Callables of the form ["B", "A::foo"] are deprecated in %s on line %d +bool(false) diff --git a/ext/standard/tests/general_functions/is_callable_abstract_method.phpt b/ext/standard/tests/general_functions/is_callable_abstract_method.phpt index 0f3d1a6cb10df..4cc9c6957f03a 100644 --- a/ext/standard/tests/general_functions/is_callable_abstract_method.phpt +++ b/ext/standard/tests/general_functions/is_callable_abstract_method.phpt @@ -9,11 +9,13 @@ abstract class A { class B extends A { function foo() {} -} -$foo = [new B, 'A::foo']; -var_dump(is_callable($foo)); + function test() { + var_dump(is_callable(['A', 'foo'])); + } +} +(new B)->test(); ?> --EXPECT-- bool(false) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 7114f09d6bdab..1b113423d7140 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -953,7 +953,7 @@ static int php_userstreamop_set_option(php_stream *stream, int option, int value switch (value) { case PHP_STREAM_TRUNCATE_SUPPORTED: - if (zend_is_callable_ex(&func_name, Z_OBJ(us->object), 0, NULL, NULL, NULL)) + if (zend_is_callable_ex(&func_name, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL)) ret = PHP_STREAM_OPTION_RETURN_OK; else ret = PHP_STREAM_OPTION_RETURN_ERR;