mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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:
parent
252dd1e9f0
commit
2042fd34e0
53 changed files with 1231 additions and 15 deletions
4
NEWS
4
NEWS
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
50
Zend/tests/first_class_callable/constexpr/attributes.phpt
Normal file
50
Zend/tests/first_class_callable/constexpr/attributes.phpt
Normal 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>"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
})
|
|
@ -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(...) ]
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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
|
31
Zend/tests/first_class_callable/constexpr/autoload.phpt
Normal file
31
Zend/tests/first_class_callable/constexpr/autoload.phpt
Normal 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
|
22
Zend/tests/first_class_callable/constexpr/basic.phpt
Normal file
22
Zend/tests/first_class_callable/constexpr/basic.phpt
Normal 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"
|
|
@ -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"
|
24
Zend/tests/first_class_callable/constexpr/class_const.phpt
Normal file
24
Zend/tests/first_class_callable/constexpr/class_const.phpt
Normal 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"
|
39
Zend/tests/first_class_callable/constexpr/complex_array.phpt
Normal file
39
Zend/tests/first_class_callable/constexpr/complex_array.phpt
Normal 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)
|
18
Zend/tests/first_class_callable/constexpr/default_args.phpt
Normal file
18
Zend/tests/first_class_callable/constexpr/default_args.phpt
Normal 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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
24
Zend/tests/first_class_callable/constexpr/namespace_001.phpt
Normal file
24
Zend/tests/first_class_callable/constexpr/namespace_001.phpt
Normal 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"
|
24
Zend/tests/first_class_callable/constexpr/namespace_002.phpt
Normal file
24
Zend/tests/first_class_callable/constexpr/namespace_002.phpt
Normal 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"
|
28
Zend/tests/first_class_callable/constexpr/namespace_003.phpt
Normal file
28
Zend/tests/first_class_callable/constexpr/namespace_003.phpt
Normal 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"
|
68
Zend/tests/first_class_callable/constexpr/namespace_004.phpt
Normal file
68
Zend/tests/first_class_callable/constexpr/namespace_004.phpt
Normal 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"
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
30
Zend/tests/first_class_callable/constexpr/static_call.phpt
Normal file
30
Zend/tests/first_class_callable/constexpr/static_call.phpt
Normal 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"
|
|
@ -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"
|
|
@ -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"
|
28
Zend/tests/first_class_callable/constexpr/userland.phpt
Normal file
28
Zend/tests/first_class_callable/constexpr/userland.phpt
Normal 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"
|
127
Zend/zend_ast.c
127
Zend/zend_ast.c
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue