Skip to content

Commit e12d463

Browse files
committed
fixup! Support first-class callables in const-expressions
1 parent 5e42085 commit e12d463

7 files changed

+153
-18
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
FCC in initializer errors for FCC on abstract method
3+
--FILE--
4+
<?php
5+
6+
abstract class Foo {
7+
abstract public static function myMethod(string $foo);
8+
}
9+
10+
const Closure = Foo::myMethod(...);
11+
12+
var_dump(Closure);
13+
(Closure)("abc");
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: Uncaught Error: Cannot call abstract method Foo::myMethod() in %s:%d
18+
Stack trace:
19+
#0 {main}
20+
thrown in %s on line %d
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
FCC in initializer errors for FCC on __callStatic() fallback.
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public static function __callStatic(string $name, array $foo) {
8+
echo "Called ", __METHOD__, "({$name})", PHP_EOL;
9+
var_dump($foo);
10+
}
11+
}
12+
13+
const Closure = Foo::myMethod(...);
14+
15+
var_dump(Closure);
16+
(Closure)("abc");
17+
18+
?>
19+
--EXPECTF--
20+
Fatal error: Uncaught Error: Creating a callable for the magic __callStatic() method is not supported in constant expressions in %s:%d
21+
Stack trace:
22+
#0 {main}
23+
thrown in %s on line %d
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
FCC in initializer errors for static reference to instance method.
3+
--FILE--
4+
<?php
5+
6+
trait Foo {
7+
public static function myMethod(string $foo) {
8+
echo "Called ", __METHOD__, PHP_EOL;
9+
var_dump($foo);
10+
}
11+
}
12+
13+
const Closure = Foo::myMethod(...);
14+
15+
var_dump(Closure);
16+
(Closure)("abc");
17+
18+
?>
19+
--EXPECTF--
20+
Deprecated: Calling static trait method Foo::myMethod is deprecated, it should only be called on a class using the trait in %s on line %d
21+
object(Closure)#%d (2) {
22+
["function"]=>
23+
string(8) "myMethod"
24+
["parameter"]=>
25+
array(1) {
26+
["$foo"]=>
27+
string(10) "<required>"
28+
}
29+
}
30+
Called Foo::myMethod
31+
string(3) "abc"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
FCC in initializer errors for static reference to instance method (Exception).
3+
--FILE--
4+
<?php
5+
6+
set_error_handler(function (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
7+
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
8+
});
9+
10+
trait Foo {
11+
public static function myMethod(string $foo) {
12+
echo "Called ", __METHOD__, PHP_EOL;
13+
var_dump($foo);
14+
}
15+
}
16+
17+
function foo(Closure $c = Foo::myMethod(...)) {
18+
var_dump($c);
19+
$c("abc");
20+
}
21+
22+
try {
23+
foo();
24+
} catch (ErrorException $e) {
25+
echo "Caught: ", $e->getMessage(), PHP_EOL;
26+
}
27+
28+
29+
?>
30+
--EXPECT--
31+
Caught: Calling static trait method Foo::myMethod is deprecated, it should only be called on a class using the trait

Zend/zend_ast.c

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,27 +1014,55 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
10141014
return FAILURE;
10151015
}
10161016
zend_string *method_name = zend_ast_get_str(ast->child[1]);
1017-
fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
1018-
if (!fptr) {
1019-
zend_undefined_method(ce, method_name);
1020-
return FAILURE;
1021-
}
1022-
if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
1023-
if (UNEXPECTED(fptr->common.scope != scope)) {
1024-
if (
1025-
UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE)
1026-
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope))
1027-
) {
1028-
zend_throw_error(NULL, "Call to %s method %s::%s() from %s%s",
1029-
zend_visibility_string(fptr->common.fn_flags), ZEND_FN_SCOPE_NAME(fptr), ZSTR_VAL(method_name),
1030-
scope ? "scope " : "global scope",
1031-
scope ? ZSTR_VAL(scope->name) : ""
1032-
);
1017+
if (ce->get_static_method) {
1018+
fptr = ce->get_static_method(ce, method_name);
1019+
} else {
1020+
fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
1021+
if (fptr) {
1022+
if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
1023+
if (UNEXPECTED(fptr->common.scope != scope)) {
1024+
if (
1025+
UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE)
1026+
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope))
1027+
) {
1028+
if (ce->__callstatic) {
1029+
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
1030+
} else {
1031+
zend_bad_method_call(fptr, method_name, scope);
1032+
}
1033+
1034+
return FAILURE;
1035+
}
1036+
}
10331037
}
1038+
} else {
1039+
if (ce->__callstatic) {
1040+
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
1041+
} else {
1042+
zend_undefined_method(ce, method_name);
1043+
}
1044+
1045+
return FAILURE;
10341046
}
10351047
}
1048+
10361049
if (!(fptr->common.fn_flags & ZEND_ACC_STATIC)) {
10371050
zend_non_static_method_call(fptr);
1051+
1052+
return FAILURE;
1053+
}
1054+
if ((fptr->common.fn_flags & ZEND_ACC_ABSTRACT)) {
1055+
zend_abstract_method_call(fptr);
1056+
1057+
return FAILURE;
1058+
} else if (fptr->common.scope->ce_flags & ZEND_ACC_TRAIT) {
1059+
zend_error(E_DEPRECATED,
1060+
"Calling static trait method %s::%s is deprecated, "
1061+
"it should only be called on a class using the trait",
1062+
ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name));
1063+
if (EG(exception)) {
1064+
return FAILURE;
1065+
}
10381066
}
10391067
break;
10401068
}

Zend/zend_object_handlers.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,7 +1742,7 @@ static zend_always_inline zend_function *zend_get_user_call_function(zend_class_
17421742
}
17431743
/* }}} */
17441744

1745-
static ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope) /* {{{ */
1745+
ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope) /* {{{ */
17461746
{
17471747
zend_throw_error(NULL, "Call to %s method %s::%s() from %s%s",
17481748
zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZSTR_VAL(method_name),
@@ -1752,7 +1752,7 @@ static ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc,
17521752
}
17531753
/* }}} */
17541754

1755-
static ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc) /* {{{ */
1755+
ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc) /* {{{ */
17561756
{
17571757
zend_throw_error(NULL, "Cannot call abstract method %s::%s()",
17581758
ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));

Zend/zend_object_handlers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2);
272272
ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr, bool check_only);
273273
/* Use zend_std_get_properties_ex() */
274274
ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj);
275+
ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope);
276+
ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc);
275277

276278
static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object)
277279
{

0 commit comments

Comments
 (0)