Skip to content

Commit a96a34b

Browse files
committed
fixup! Support first-class callables in const-expressions
1 parent 68525bb commit a96a34b

File tree

7 files changed

+153
-18
lines changed

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
@@ -1026,27 +1026,55 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
10261026
return FAILURE;
10271027
}
10281028
zend_string *method_name = zend_ast_get_str(ast->child[1]);
1029-
fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
1030-
if (!fptr) {
1031-
zend_undefined_method(ce, method_name);
1032-
return FAILURE;
1033-
}
1034-
if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
1035-
if (UNEXPECTED(fptr->common.scope != scope)) {
1036-
if (
1037-
UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE)
1038-
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope))
1039-
) {
1040-
zend_throw_error(NULL, "Call to %s method %s::%s() from %s%s",
1041-
zend_visibility_string(fptr->common.fn_flags), ZEND_FN_SCOPE_NAME(fptr), ZSTR_VAL(method_name),
1042-
scope ? "scope " : "global scope",
1043-
scope ? ZSTR_VAL(scope->name) : ""
1044-
);
1029+
if (ce->get_static_method) {
1030+
fptr = ce->get_static_method(ce, method_name);
1031+
} else {
1032+
fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
1033+
if (fptr) {
1034+
if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
1035+
if (UNEXPECTED(fptr->common.scope != scope)) {
1036+
if (
1037+
UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE)
1038+
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope))
1039+
) {
1040+
if (ce->__callstatic) {
1041+
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
1042+
} else {
1043+
zend_bad_method_call(fptr, method_name, scope);
1044+
}
1045+
1046+
return FAILURE;
1047+
}
1048+
}
10451049
}
1050+
} else {
1051+
if (ce->__callstatic) {
1052+
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
1053+
} else {
1054+
zend_undefined_method(ce, method_name);
1055+
}
1056+
1057+
return FAILURE;
10461058
}
10471059
}
1060+
10481061
if (!(fptr->common.fn_flags & ZEND_ACC_STATIC)) {
10491062
zend_non_static_method_call(fptr);
1063+
1064+
return FAILURE;
1065+
}
1066+
if ((fptr->common.fn_flags & ZEND_ACC_ABSTRACT)) {
1067+
zend_abstract_method_call(fptr);
1068+
1069+
return FAILURE;
1070+
} else if (fptr->common.scope->ce_flags & ZEND_ACC_TRAIT) {
1071+
zend_error(E_DEPRECATED,
1072+
"Calling static trait method %s::%s is deprecated, "
1073+
"it should only be called on a class using the trait",
1074+
ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name));
1075+
if (EG(exception)) {
1076+
return FAILURE;
1077+
}
10501078
}
10511079
break;
10521080
}

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)