Support first-class callables in const-expressions (#17213)

RFC: https://wiki.php.net/rfc/fcc_in_const_expr

Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
This commit is contained in:
Tim Düsterhus 2025-02-20 18:52:47 +01:00 committed by GitHub
parent 252dd1e9f0
commit 2042fd34e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1231 additions and 15 deletions

4
NEWS
View file

@ -14,8 +14,8 @@ PHP NEWS
. Fixed bug GH-16665 (\array and \callable should not be usable in
class_alias). (nielsdos)
. Added PHP_BUILD_DATE constant. (cmb)
. Added support for Closures in constant expressions. (timwolla,
Volker Dusch)
. Added support for Closures and first class callables in constant
expressions. (timwolla, Volker Dusch)
. Use `clock_gettime_nsec_np()` for high resolution timer on macOS
if available. (timwolla)
. Implement GH-15680 (Enhance zend_dump_op_array to properly represent

View file

@ -85,8 +85,10 @@ PHP 8.5 UPGRADE NOTES
- Core:
. Closure is now a proper subtype of callable
. Added support for Closures in constant expressions.
. Added support for Closures and first class callables in constant
expressions.
RFC: https://wiki.php.net/rfc/closures_in_const_expr
RFC: https://wiki.php.net/rfc/fcc_in_const_expr
. Fatal Errors (such as an exceeded maximum execution time) now include a
backtrace.
RFC: https://wiki.php.net/rfc/error_backtraces_v2

View file

@ -0,0 +1,50 @@
--TEST--
Allow defining FCC in attributes
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {
var_dump($value('abc'));
}
}
#[Attr(strrev(...))]
#[Attr(strlen(...))]
class C {}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
var_dump($reflectionAttribute->newInstance());
}
?>
--EXPECTF--
string(3) "cba"
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
int(3)
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}

View file

@ -0,0 +1,33 @@
--TEST--
AST printing for FCC in attributes
--FILE--
<?php
// Do not use `false &&` to fully evaluate the function / class definition.
try {
\assert(
!
#[Attr(strrev(...))]
function () { }
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
\assert(
!
new #[Attr(strrev(...))]
class {}
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
assert(!#[Attr(strrev(...))] function () {
})
assert(!new #[Attr(strrev(...))] class {
})

View file

@ -0,0 +1,27 @@
--TEST--
AST printing for FCC in attributes at runtime
--FILE--
<?php
namespace Test;
class Clazz {
#[Attr(strrev(...), \strrev(...), Clazz::foo(...), self::foo(...))]
function foo() { }
}
$r = new \ReflectionMethod(Clazz::class, 'foo');
foreach ($r->getAttributes() as $attribute) {
echo $attribute;
}
?>
--EXPECT--
Attribute [ Test\Attr ] {
- Arguments [4] {
Argument #0 [ Test\strrev(...) ]
Argument #1 [ \strrev(...) ]
Argument #2 [ \Test\Clazz::foo(...) ]
Argument #3 [ self::foo(...) ]
}
}

View file

@ -0,0 +1,28 @@
--TEST--
FCC in attribute may access private methods
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}
#[Attr(C::myMethod(...))]
class C {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}
?>
--EXPECT--
Called C::myMethod
string(3) "abc"

View file

@ -0,0 +1,34 @@
--TEST--
FCC in attribute may not access unrelated private methods
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}
class E {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
#[Attr(E::myMethod(...))]
class C {
}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d

View file

@ -0,0 +1,31 @@
--TEST--
FCC in const expression triggers autoloader.
--FILE--
<?php
spl_autoload_register(static function ($class) {
echo "Autoloading {$class}", PHP_EOL;
eval(
<<<'EOT'
class AutoloadedClass {
public static function withStaticMethod() {
echo "Called ", __METHOD__, PHP_EOL;
}
}
EOT
);
});
const Closure = AutoloadedClass::withStaticMethod(...);
var_dump(Closure);
(Closure)();
?>
--EXPECTF--
Autoloading AutoloadedClass
object(Closure)#%d (1) {
["function"]=>
string(16) "withStaticMethod"
}
Called AutoloadedClass::withStaticMethod

View file

@ -0,0 +1,22 @@
--TEST--
Allow defining FCC in const expressions.
--FILE--
<?php
const Closure = strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,22 @@
--TEST--
Allow defining FCC in const expressions with case-insensitive names.
--FILE--
<?php
const Closure = StrRev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,24 @@
--TEST--
Allow defining FCC in class constants.
--FILE--
<?php
class C {
const Closure = strrev(...);
}
var_dump(C::Closure);
var_dump((C::Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,39 @@
--TEST--
Allow defining FCC wrapped in an array in const expressions.
--FILE--
<?php
const Closure = [strrev(...), strlen(...)];
var_dump(Closure);
foreach (Closure as $closure) {
var_dump($closure("abc"));
}
?>
--EXPECTF--
array(2) {
[0]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
[1]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
string(3) "cba"
int(3)

View file

@ -0,0 +1,18 @@
--TEST--
FCC in default argument
--FILE--
<?php
function test(
Closure $name = strrev(...)
) {
var_dump($name("abc"));
}
test();
test(strlen(...));
?>
--EXPECT--
string(3) "cba"
int(3)

View file

@ -0,0 +1,20 @@
--TEST--
FCC in initializer errors for FCC on abstract method
--FILE--
<?php
abstract class Foo {
abstract public static function myMethod(string $foo);
}
const Closure = Foo::myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot call abstract method Foo::myMethod() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on variable.
--FILE--
<?php
const Closure = $foo(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d

View file

@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on Closure.
--FILE--
<?php
const Closure = (static function () { })(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d

View file

@ -0,0 +1,14 @@
--TEST--
FCC in initializer errors for FCC on Constant.
--FILE--
<?php
const Name = 'strrev';
const Closure = (Name)(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d

View file

@ -0,0 +1,20 @@
--TEST--
FCC in initializer errors for FCC on 'static::'.
--FILE--
<?php
class Foo {
public const Closure = static::myMethod(...);
public static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
var_dump(Foo::Closure);
(Foo::Closure)("abc");
?>
--EXPECTF--
Fatal error: "static" is not allowed in compile-time constants in %s on line %d

View file

@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on integer expression
--FILE--
<?php
const Closure = (0)(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Illegal function name in %s on line %d

View file

@ -0,0 +1,20 @@
--TEST--
FCC in initializer errors for FCC on instance call.
--FILE--
<?php
class Foo {
public function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
const Closure = (new Foo())->myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s on line %d

View file

@ -0,0 +1,23 @@
--TEST--
FCC in initializer errors for FCC on __callStatic() fallback.
--FILE--
<?php
class Foo {
public static function __callStatic(string $name, array $foo) {
echo "Called ", __METHOD__, "({$name})", PHP_EOL;
var_dump($foo);
}
}
const Closure = Foo::myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
Fatal error: Uncaught Error: Creating a callable for the magic __callStatic() method is not supported in constant expressions in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,18 @@
--TEST--
FCC in initializer errors for missing function with FQN matching global function.
--FILE--
<?php
namespace Foo;
const Closure = \Foo\strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined function Foo\strrev() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,18 @@
--TEST--
FCC in initializer errors for missing function with FQN matching global function.
--FILE--
<?php
namespace Foo;
const Closure = namespace\strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined function Foo\strrev() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,23 @@
--TEST--
FCC in initializer errors for static reference to instance method.
--FILE--
<?php
class Foo {
public function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
const Closure = Foo::myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
Fatal error: Uncaught Error: Non-static method Foo::myMethod() cannot be called statically in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,31 @@
--TEST--
FCC in initializer errors for static reference to instance method.
--FILE--
<?php
trait Foo {
public static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
const Closure = Foo::myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
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
object(Closure)#%d (2) {
["function"]=>
string(8) "myMethod"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called Foo::myMethod
string(3) "abc"

View file

@ -0,0 +1,31 @@
--TEST--
FCC in initializer errors for static reference to instance method (Exception).
--FILE--
<?php
set_error_handler(function (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});
trait Foo {
public static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
function foo(Closure $c = Foo::myMethod(...)) {
var_dump($c);
$c("abc");
}
try {
foo();
} catch (ErrorException $e) {
echo "Caught: ", $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Caught: Calling static trait method Foo::myMethod is deprecated, it should only be called on a class using the trait

View file

@ -0,0 +1,15 @@
--TEST--
FCC in initializer errors for missing class.
--FILE--
<?php
const Closure = ThisClassNotDoesExist::thisMethodIsNotRelevant(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Uncaught Error: Class "ThisClassNotDoesExist" not found in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,15 @@
--TEST--
FCC in initializer errors for missing function.
--FILE--
<?php
const Closure = this_function_does_not_exist(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined function this_function_does_not_exist() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,17 @@
--TEST--
FCC in initializer errors for missing method.
--FILE--
<?php
class ThisClassDoesExist { }
const Closure = ThisClassDoesExist::thisMethodDoesNotExist(...);
var_dump(Closure);
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined method ThisClassDoesExist::thisMethodDoesNotExist() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View file

@ -0,0 +1,24 @@
--TEST--
Allow defining FCC in const expressions in a namespace.
--FILE--
<?php
namespace Foo;
const Closure = \strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,24 @@
--TEST--
Allow defining FCC in const expressions in a namespace with global function fallback.
--FILE--
<?php
namespace Foo;
const Closure = strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,28 @@
--TEST--
Allow defining FCC in const expressions in a namespace with function matching a global function.
--FILE--
<?php
namespace Foo;
function strrev(string $value) {
return 'not the global one';
}
const Closure = strrev(...);
var_dump(Closure);
var_dump((Closure)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$value"]=>
string(10) "<required>"
}
}
string(18) "not the global one"

View file

@ -0,0 +1,68 @@
--TEST--
Allow defining FCC in const expressions in a namespace with function matching a global function later.
--FILE--
<?php
namespace Foo;
function foo(\Closure $c = strrev(...)) {
$d = strrev(...);
var_dump($c);
var_dump($c("abc"));
var_dump($d);
var_dump($d("abc"));
}
foo();
if (random_int(1, 2) > 0) {
function strrev(string $value) {
return 'not the global one';
}
}
foo();
?>
--EXPECTF--
object(Closure)#1 (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
object(Closure)#2 (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
object(Closure)#2 (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
object(Closure)#1 (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,26 @@
--TEST--
FCC in property initializer
--FILE--
<?php
class C {
public Closure $d = strrev(...);
}
$c = new C();
var_dump($c->d);
var_dump(($c->d)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,32 @@
--TEST--
FCC in property initializer may access private methods.
--FILE--
<?php
class C {
public Closure $d = C::myMethod(...);
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
$c = new C();
var_dump($c->d);
var_dump(($c->d)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(11) "C::myMethod"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called C::myMethod
string(3) "abc"
NULL

View file

@ -0,0 +1,27 @@
--TEST--
FCC in property initializer may not access unrelated private methods.
--FILE--
<?php
class E {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
class C {
public Closure $d = E::myMethod(...);
}
$c = new C();
var_dump($c->d);
var_dump(($c->d)("abc"));
?>
--EXPECTF--
Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d
Stack trace:
#0 %s(%d): [constant expression]()
#1 {main}
thrown in %s on line %d

View file

@ -0,0 +1,35 @@
--TEST--
FCC in property initializer may access protected methods of parent.
--FILE--
<?php
class P {
protected static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
class C extends P {
public Closure $d = self::myMethod(...);
}
$c = new C();
var_dump($c->d);
var_dump(($c->d)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(11) "C::myMethod"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called P::myMethod
string(3) "abc"
NULL

View file

@ -0,0 +1,30 @@
--TEST--
Allow defining FCC for static methods in const expressions.
--FILE--
<?php
class Foo {
public static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
const Closure = Foo::myMethod(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(8) "myMethod"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called Foo::myMethod
string(3) "abc"

View file

@ -0,0 +1,30 @@
--TEST--
Allow defining FCC for static methods referenced by 'self::' in const expressions.
--FILE--
<?php
class Foo {
public const Closure = self::myMethod(...);
public static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}
var_dump(Foo::Closure);
(Foo::Closure)("abc");
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(13) "Foo::myMethod"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called Foo::myMethod
string(3) "abc"

View file

@ -0,0 +1,25 @@
--TEST--
FCC in static property initializer
--FILE--
<?php
class C {
public static Closure $d = strrev(...);
}
var_dump(C::$d);
var_dump((C::$d)("abc"));
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"

View file

@ -0,0 +1,28 @@
--TEST--
Allow defining FCC for userland functions in const expressions.
--FILE--
<?php
function my_function(string $foo) {
echo "Called ", __FUNCTION__, PHP_EOL;
var_dump($foo);
}
const Closure = my_function(...);
var_dump(Closure);
(Closure)("abc");
?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(11) "my_function"
["parameter"]=>
array(1) {
["$foo"]=>
string(10) "<required>"
}
}
Called my_function
string(3) "abc"

View file

@ -54,6 +54,18 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(znode *node) {
return (zend_ast *) ast;
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void) {
zend_ast_fcc *ast;
ast = zend_ast_alloc(sizeof(zend_ast_fcc));
ast->kind = ZEND_AST_CALLABLE_CONVERT;
ast->attr = 0;
ast->lineno = CG(zend_lineno);
ZEND_MAP_PTR_INIT(ast->fptr, NULL);
return (zend_ast *) ast;
}
static zend_always_inline zend_ast * zend_ast_create_zval_int(zval *zv, uint32_t attr, uint32_t lineno) {
zend_ast_zval *ast;
@ -1002,6 +1014,111 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
return SUCCESS;
}
case ZEND_AST_CALL:
case ZEND_AST_STATIC_CALL:
{
zend_function *fptr;
switch (ast->kind) {
case ZEND_AST_CALL: {
ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[1];
fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);
if (!fptr) {
zend_string *function_name = zend_ast_get_str(ast->child[0]);
zend_string *function_name_lc = zend_string_tolower(function_name);
fptr = zend_fetch_function(function_name_lc);
if (!fptr && ast->child[0]->attr != ZEND_NAME_FQ) {
const char *backslash = zend_memrchr(ZSTR_VAL(function_name_lc), '\\', ZSTR_LEN(function_name_lc));
if (backslash) {
fptr = zend_fetch_function_str(backslash + 1, ZSTR_LEN(function_name_lc) - (backslash - ZSTR_VAL(function_name_lc) + 1));
}
}
zend_string_release(function_name_lc);
if (!fptr) {
zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function_name));
return FAILURE;
}
ZEND_MAP_PTR_SET(fcc_ast->fptr, fptr);
}
break;
}
case ZEND_AST_STATIC_CALL: {
ZEND_ASSERT(ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[2];
fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);
if (!fptr) {
zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
if (!ce) {
return FAILURE;
}
zend_string *method_name = zend_ast_get_str(ast->child[1]);
if (ce->get_static_method) {
fptr = ce->get_static_method(ce, method_name);
} else {
fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
if (fptr) {
if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
if (UNEXPECTED(fptr->common.scope != scope)) {
if (
UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE)
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope))
) {
if (ce->__callstatic) {
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
} else {
zend_bad_method_call(fptr, method_name, scope);
}
return FAILURE;
}
}
}
} else {
if (ce->__callstatic) {
zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
} else {
zend_undefined_method(ce, method_name);
}
return FAILURE;
}
}
if (!(fptr->common.fn_flags & ZEND_ACC_STATIC)) {
zend_non_static_method_call(fptr);
return FAILURE;
}
if ((fptr->common.fn_flags & ZEND_ACC_ABSTRACT)) {
zend_abstract_method_call(fptr);
return FAILURE;
} else if (fptr->common.scope->ce_flags & ZEND_ACC_TRAIT) {
zend_error(E_DEPRECATED,
"Calling static trait method %s::%s is deprecated, "
"it should only be called on a class using the trait",
ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name));
if (EG(exception)) {
return FAILURE;
}
}
ZEND_MAP_PTR_SET(fcc_ast->fptr, fptr);
}
break;
}
}
zend_create_fake_closure(result, fptr, scope, scope, NULL);
return SUCCESS;
}
case ZEND_AST_OP_ARRAY:
{
zend_function *func = (zend_function *)zend_ast_get_op_array(ast)->op_array;
@ -1092,6 +1209,8 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
size = sizeof(zend_ast_zval);
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
size = sizeof(zend_ast_op_array);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
size = sizeof(zend_ast_fcc);
} else if (zend_ast_is_list(ast)) {
uint32_t i;
zend_ast_list *list = zend_ast_get_list(ast);
@ -1159,6 +1278,14 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
new->lineno = old->lineno;
new->op_array = old->op_array;
buf = (void*)((char*)buf + sizeof(zend_ast_op_array));
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *old = (zend_ast_fcc*)ast;
zend_ast_fcc *new = (zend_ast_fcc*)buf;
new->kind = old->kind;
new->attr = old->attr;
new->lineno = old->lineno;
ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr));
buf = (void*)((char*)buf + sizeof(zend_ast_fcc));
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();

View file

@ -22,6 +22,7 @@
#define ZEND_AST_H
#include "zend_types.h"
#include "zend_map_ptr.h"
#ifndef ZEND_AST_SPEC
# define ZEND_AST_SPEC 1
@ -227,6 +228,13 @@ typedef struct _zend_ast_decl {
zend_ast *child[5];
} zend_ast_decl;
typedef struct _zend_ast_fcc {
zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
zend_ast_attr attr; /* Additional attribute, use depending on node type */
uint32_t lineno; /* Line number */
ZEND_MAP_PTR_DEF(zend_function *, fptr);
} zend_ast_fcc;
typedef void (*zend_ast_process_t)(zend_ast *ast);
extern ZEND_API zend_ast_process_t zend_ast_process;
@ -318,6 +326,8 @@ ZEND_API zend_ast *zend_ast_create_decl(
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void);
typedef struct {
bool had_side_effects;
} zend_ast_evaluate_ctx;

View file

@ -11099,7 +11099,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
|| kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST
|| kind == ZEND_AST_NAMED_ARG
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP
|| kind == ZEND_AST_CLOSURE;
|| kind == ZEND_AST_CLOSURE
|| kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT;
}
/* }}} */
@ -11208,9 +11209,8 @@ static void zend_compile_const_expr_magic_const(zend_ast **ast_ptr) /* {{{ */
}
/* }}} */
static void zend_compile_const_expr_new(zend_ast **ast_ptr)
static void zend_compile_const_expr_class_reference(zend_ast *class_ast)
{
zend_ast *class_ast = (*ast_ptr)->child[0];
if (class_ast->kind == ZEND_AST_CLASS) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use anonymous class in constant expression");
@ -11233,6 +11233,12 @@ static void zend_compile_const_expr_new(zend_ast **ast_ptr)
class_ast->attr = fetch_type << ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT;
}
static void zend_compile_const_expr_new(zend_ast **ast_ptr)
{
zend_ast *class_ast = (*ast_ptr)->child[0];
zend_compile_const_expr_class_reference(class_ast);
}
static void zend_compile_const_expr_closure(zend_ast **ast_ptr)
{
zend_ast_decl *closure_ast = (zend_ast_decl *) *ast_ptr;
@ -11253,6 +11259,58 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr)
*ast_ptr = zend_ast_create_op_array(op);
}
static void zend_compile_const_expr_fcc(zend_ast **ast_ptr)
{
zend_ast **args_ast;
switch ((*ast_ptr)->kind) {
case ZEND_AST_CALL:
args_ast = &(*ast_ptr)->child[1];
break;
case ZEND_AST_STATIC_CALL:
args_ast = &(*ast_ptr)->child[2];
break;
EMPTY_SWITCH_DEFAULT_CASE();
}
if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) {
zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
}
ZEND_MAP_PTR_NEW(((zend_ast_fcc *)*args_ast)->fptr);
switch ((*ast_ptr)->kind) {
case ZEND_AST_CALL: {
zend_ast *name_ast = (*ast_ptr)->child[0];
if (name_ast->kind != ZEND_AST_ZVAL) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use dynamic function name in constant expression");
}
zval *name_ast_zv = zend_ast_get_zval(name_ast);
if (Z_TYPE_P(name_ast_zv) != IS_STRING) {
zend_error_noreturn(E_COMPILE_ERROR, "Illegal function name");
}
bool is_fully_qualified;
zend_string *name = zend_resolve_function_name(Z_STR_P(name_ast_zv), name_ast->attr, &is_fully_qualified);
zval_ptr_dtor_nogc(name_ast_zv);
ZVAL_STR(name_ast_zv, name);
if (is_fully_qualified) {
name_ast->attr = ZEND_NAME_FQ;
}
break;
}
case ZEND_AST_STATIC_CALL: {
zend_ast *class_ast = (*ast_ptr)->child[0];
zend_compile_const_expr_class_reference(class_ast);
zend_ast *method_ast = (*ast_ptr)->child[1];
if (method_ast->kind != ZEND_AST_ZVAL) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use dynamic method name in constant expression");
}
if (Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING) {
zend_error_noreturn(E_COMPILE_ERROR, "Illegal method name");
}
break;
}
EMPTY_SWITCH_DEFAULT_CASE();
}
}
static void zend_compile_const_expr_args(zend_ast **ast_ptr)
{
zend_ast_list *list = zend_ast_get_list(*ast_ptr);
@ -11319,6 +11377,10 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */
zend_compile_const_expr_closure(ast_ptr);
/* Return, because we do not want to traverse the children. */
return;
case ZEND_AST_CALL:
case ZEND_AST_STATIC_CALL:
zend_compile_const_expr_fcc(ast_ptr);
break;
}
zend_ast_apply(ast, zend_compile_const_expr, context);

View file

@ -2448,7 +2448,7 @@ ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_index_write(HashTable *ht,
return retval;
}
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
{
zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method));
}

View file

@ -447,6 +447,7 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data,
ZEND_API ZEND_ATTRIBUTE_DEPRECATED HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer);
ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer, bool suspended_by_yield);
ZEND_API zval* ZEND_FASTCALL zend_fetch_static_property(zend_execute_data *ex, int fetch_type);
ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method);
ZEND_API void ZEND_FASTCALL zend_non_static_method_call(const zend_function *fbc);
ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data);

View file

@ -896,7 +896,7 @@ return_type:
argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_argument_list possible_comma ')' { $$ = $2; }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create(ZEND_AST_CALLABLE_CONVERT); }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
;
non_empty_argument_list:

View file

@ -1748,7 +1748,7 @@ static zend_always_inline zend_function *zend_get_user_call_function(zend_class_
}
/* }}} */
static ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope) /* {{{ */
ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope) /* {{{ */
{
zend_throw_error(NULL, "Call to %s method %s::%s() from %s%s",
zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZSTR_VAL(method_name),
@ -1758,7 +1758,7 @@ static ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc,
}
/* }}} */
static ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc) /* {{{ */
ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc) /* {{{ */
{
zend_throw_error(NULL, "Cannot call abstract method %s::%s()",
ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));

View file

@ -272,6 +272,8 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2);
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);
/* Use zend_std_get_properties_ex() */
ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj);
ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope);
ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc);
static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object)
{

View file

@ -122,11 +122,6 @@ static ZEND_COLD void ZEND_FASTCALL zend_jit_invalid_method_call_tmp(zval *objec
zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
}
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
{
zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method));
}
static void ZEND_FASTCALL zend_jit_unref_helper(zval *zv)
{
zend_reference *ref;

View file

@ -366,6 +366,9 @@ static void zend_file_cache_serialize_ast(zend_ast *ast,
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be serialized as part of the dynamic_func_defs. */
SERIALIZE_PTR(zend_ast_get_op_array(ast)->op_array);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
ZEND_MAP_PTR_INIT(fcc->fptr, NULL);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
@ -1251,6 +1254,9 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast,
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be unserialized as part of the dynamic_func_defs. */
UNSERIALIZE_PTR(zend_ast_get_op_array(ast)->op_array);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
ZEND_MAP_PTR_NEW(fcc->fptr);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();

View file

@ -195,6 +195,9 @@ static zend_ast *zend_persist_ast(zend_ast *ast)
zend_persist_op_array(&z);
copy->op_array = Z_PTR(z);
node = (zend_ast *) copy;
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc));
node = (zend_ast *) copy;
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();

View file

@ -91,6 +91,8 @@ static void zend_persist_ast_calc(zend_ast *ast)
zval z;
ZVAL_PTR(&z, zend_ast_get_op_array(ast)->op_array);
zend_persist_op_array_calc(&z);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
ADD_SIZE(sizeof(zend_ast_fcc));
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();