diff --git a/NEWS b/NEWS index 384565082cf92..c9a77af4b63a9 100644 --- a/NEWS +++ b/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 diff --git a/UPGRADING b/UPGRADING index 7cfe2372ab98d..708303b32594c 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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 diff --git a/Zend/tests/first_class_callable/constexpr/attributes.phpt b/Zend/tests/first_class_callable/constexpr/attributes.phpt new file mode 100644 index 0000000000000..b011a7f678137 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/attributes.phpt @@ -0,0 +1,50 @@ +--TEST-- +Allow defining FCC in attributes +--EXTENSIONS-- +reflection +--FILE-- +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) "" + } + } +} +int(3) +object(Attr)#%d (1) { + ["value"]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} diff --git a/Zend/tests/first_class_callable/constexpr/attributes_ast_printing.phpt b/Zend/tests/first_class_callable/constexpr/attributes_ast_printing.phpt new file mode 100644 index 0000000000000..2916c3dfff86d --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/attributes_ast_printing.phpt @@ -0,0 +1,33 @@ +--TEST-- +AST printing for FCC in attributes +--FILE-- +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 { +}) diff --git a/Zend/tests/first_class_callable/constexpr/attributes_ast_printing_runtime.phpt b/Zend/tests/first_class_callable/constexpr/attributes_ast_printing_runtime.phpt new file mode 100644 index 0000000000000..8ee0acbb6f0e4 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/attributes_ast_printing_runtime.phpt @@ -0,0 +1,27 @@ +--TEST-- +AST printing for FCC in attributes at runtime +--FILE-- +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(...) ] + } +} diff --git a/Zend/tests/first_class_callable/constexpr/attributes_scope_001.phpt b/Zend/tests/first_class_callable/constexpr/attributes_scope_001.phpt new file mode 100644 index 0000000000000..6e75004624191 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/attributes_scope_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +FCC in attribute may access private methods +--EXTENSIONS-- +reflection +--FILE-- +getAttributes() as $reflectionAttribute) { + ($reflectionAttribute->newInstance()->value)('abc'); +} + +?> +--EXPECT-- +Called C::myMethod +string(3) "abc" diff --git a/Zend/tests/first_class_callable/constexpr/attributes_scope_002.phpt b/Zend/tests/first_class_callable/constexpr/attributes_scope_002.phpt new file mode 100644 index 0000000000000..f93240c5fac8e --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/attributes_scope_002.phpt @@ -0,0 +1,34 @@ +--TEST-- +FCC in attribute may not access unrelated private methods +--EXTENSIONS-- +reflection +--FILE-- +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 diff --git a/Zend/tests/first_class_callable/constexpr/autoload.phpt b/Zend/tests/first_class_callable/constexpr/autoload.phpt new file mode 100644 index 0000000000000..2dd2561f9a369 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/autoload.phpt @@ -0,0 +1,31 @@ +--TEST-- +FCC in const expression triggers autoloader. +--FILE-- + +--EXPECTF-- +Autoloading AutoloadedClass +object(Closure)#%d (1) { + ["function"]=> + string(16) "withStaticMethod" +} +Called AutoloadedClass::withStaticMethod diff --git a/Zend/tests/first_class_callable/constexpr/basic.phpt b/Zend/tests/first_class_callable/constexpr/basic.phpt new file mode 100644 index 0000000000000..ce60cba1aa84f --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/basic.phpt @@ -0,0 +1,22 @@ +--TEST-- +Allow defining FCC in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/case_insensitive.phpt b/Zend/tests/first_class_callable/constexpr/case_insensitive.phpt new file mode 100644 index 0000000000000..1feaa718793de --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/case_insensitive.phpt @@ -0,0 +1,22 @@ +--TEST-- +Allow defining FCC in const expressions with case-insensitive names. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/class_const.phpt b/Zend/tests/first_class_callable/constexpr/class_const.phpt new file mode 100644 index 0000000000000..ab0ea29d50369 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/class_const.phpt @@ -0,0 +1,24 @@ +--TEST-- +Allow defining FCC in class constants. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/complex_array.phpt b/Zend/tests/first_class_callable/constexpr/complex_array.phpt new file mode 100644 index 0000000000000..388d09d056a42 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/complex_array.phpt @@ -0,0 +1,39 @@ +--TEST-- +Allow defining FCC wrapped in an array in const expressions. +--FILE-- + +--EXPECTF-- +array(2) { + [0]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } + [1]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} +string(3) "cba" +int(3) diff --git a/Zend/tests/first_class_callable/constexpr/default_args.phpt b/Zend/tests/first_class_callable/constexpr/default_args.phpt new file mode 100644 index 0000000000000..a35efc8961c17 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/default_args.phpt @@ -0,0 +1,18 @@ +--TEST-- +FCC in default argument +--FILE-- + +--EXPECT-- +string(3) "cba" +int(3) diff --git a/Zend/tests/first_class_callable/constexpr/error_abstract.phpt b/Zend/tests/first_class_callable/constexpr/error_abstract.phpt new file mode 100644 index 0000000000000..877f8f3c0b463 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_abstract.phpt @@ -0,0 +1,20 @@ +--TEST-- +FCC in initializer errors for FCC on abstract method +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call abstract method Foo::myMethod() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_dynamic_001.phpt b/Zend/tests/first_class_callable/constexpr/error_dynamic_001.phpt new file mode 100644 index 0000000000000..44821cf6e60ad --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_dynamic_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +FCC in initializer errors for FCC on variable. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_dynamic_002.phpt b/Zend/tests/first_class_callable/constexpr/error_dynamic_002.phpt new file mode 100644 index 0000000000000..e373ff51620d5 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_dynamic_002.phpt @@ -0,0 +1,12 @@ +--TEST-- +FCC in initializer errors for FCC on Closure. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_dynamic_003.phpt b/Zend/tests/first_class_callable/constexpr/error_dynamic_003.phpt new file mode 100644 index 0000000000000..41090d89556f3 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_dynamic_003.phpt @@ -0,0 +1,14 @@ +--TEST-- +FCC in initializer errors for FCC on Constant. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_dynamic_004.phpt b/Zend/tests/first_class_callable/constexpr/error_dynamic_004.phpt new file mode 100644 index 0000000000000..f42c06cf86328 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_dynamic_004.phpt @@ -0,0 +1,20 @@ +--TEST-- +FCC in initializer errors for FCC on 'static::'. +--FILE-- + +--EXPECTF-- +Fatal error: "static" is not allowed in compile-time constants in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_dynamic_005.phpt b/Zend/tests/first_class_callable/constexpr/error_dynamic_005.phpt new file mode 100644 index 0000000000000..e13527444f1ca --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_dynamic_005.phpt @@ -0,0 +1,12 @@ +--TEST-- +FCC in initializer errors for FCC on integer expression +--FILE-- + +--EXPECTF-- +Fatal error: Illegal function name in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_instance_call.phpt b/Zend/tests/first_class_callable/constexpr/error_instance_call.phpt new file mode 100644 index 0000000000000..cfee7c40b7844 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_instance_call.phpt @@ -0,0 +1,20 @@ +--TEST-- +FCC in initializer errors for FCC on instance call. +--FILE-- +myMethod(...); + +var_dump(Closure); +(Closure)("abc"); + +?> +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_magic_callStatic.phpt b/Zend/tests/first_class_callable/constexpr/error_magic_callStatic.phpt new file mode 100644 index 0000000000000..ea3d4822118ce --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_magic_callStatic.phpt @@ -0,0 +1,23 @@ +--TEST-- +FCC in initializer errors for FCC on __callStatic() fallback. +--FILE-- + +--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 diff --git a/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_001.phpt b/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_001.phpt new file mode 100644 index 0000000000000..b80a222c2941d --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +FCC in initializer errors for missing function with FQN matching global function. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined function Foo\strrev() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_002.phpt b/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_002.phpt new file mode 100644 index 0000000000000..62bc7e9e0b8bf --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_namespace_no_fallback_002.phpt @@ -0,0 +1,18 @@ +--TEST-- +FCC in initializer errors for missing function with FQN matching global function. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined function Foo\strrev() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_static_call_instance_method.phpt b/Zend/tests/first_class_callable/constexpr/error_static_call_instance_method.phpt new file mode 100644 index 0000000000000..9a6b27b232e6c --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_static_call_instance_method.phpt @@ -0,0 +1,23 @@ +--TEST-- +FCC in initializer errors for static reference to instance method. +--FILE-- + +--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 diff --git a/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_001.phpt b/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_001.phpt new file mode 100644 index 0000000000000..efffa4a1d366a --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +FCC in initializer errors for static reference to instance method. +--FILE-- + +--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) "" + } +} +Called Foo::myMethod +string(3) "abc" diff --git a/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_002.phpt b/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_002.phpt new file mode 100644 index 0000000000000..d36f7c97651e0 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_static_call_trait_method_002.phpt @@ -0,0 +1,31 @@ +--TEST-- +FCC in initializer errors for static reference to instance method (Exception). +--FILE-- +getMessage(), PHP_EOL; +} + + +?> +--EXPECT-- +Caught: Calling static trait method Foo::myMethod is deprecated, it should only be called on a class using the trait diff --git a/Zend/tests/first_class_callable/constexpr/error_unknown_class.phpt b/Zend/tests/first_class_callable/constexpr/error_unknown_class.phpt new file mode 100644 index 0000000000000..39ce1736ddecd --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_unknown_class.phpt @@ -0,0 +1,15 @@ +--TEST-- +FCC in initializer errors for missing class. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "ThisClassNotDoesExist" not found in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/error_unknown_function.phpt b/Zend/tests/first_class_callable/constexpr/error_unknown_function.phpt new file mode 100644 index 0000000000000..38b291d9907ad --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_unknown_function.phpt @@ -0,0 +1,15 @@ +--TEST-- +FCC in initializer errors for missing function. +--FILE-- + +--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 diff --git a/Zend/tests/first_class_callable/constexpr/error_unknown_method.phpt b/Zend/tests/first_class_callable/constexpr/error_unknown_method.phpt new file mode 100644 index 0000000000000..345d67ee74dae --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/error_unknown_method.phpt @@ -0,0 +1,17 @@ +--TEST-- +FCC in initializer errors for missing method. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined method ThisClassDoesExist::thisMethodDoesNotExist() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/first_class_callable/constexpr/namespace_001.phpt b/Zend/tests/first_class_callable/constexpr/namespace_001.phpt new file mode 100644 index 0000000000000..f920844957894 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/namespace_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Allow defining FCC in const expressions in a namespace. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/namespace_002.phpt b/Zend/tests/first_class_callable/constexpr/namespace_002.phpt new file mode 100644 index 0000000000000..17d70ad511af7 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/namespace_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +Allow defining FCC in const expressions in a namespace with global function fallback. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/namespace_003.phpt b/Zend/tests/first_class_callable/constexpr/namespace_003.phpt new file mode 100644 index 0000000000000..2fe1d45296ce7 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/namespace_003.phpt @@ -0,0 +1,28 @@ +--TEST-- +Allow defining FCC in const expressions in a namespace with function matching a global function. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$value"]=> + string(10) "" + } +} +string(18) "not the global one" diff --git a/Zend/tests/first_class_callable/constexpr/namespace_004.phpt b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt new file mode 100644 index 0000000000000..0fc23422199d5 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt @@ -0,0 +1,68 @@ +--TEST-- +Allow defining FCC in const expressions in a namespace with function matching a global function later. +--FILE-- + 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) "" + } +} +string(3) "cba" +object(Closure)#2 (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" +object(Closure)#2 (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" +object(Closure)#1 (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/property_initializer.phpt b/Zend/tests/first_class_callable/constexpr/property_initializer.phpt new file mode 100644 index 0000000000000..53cae06216bd1 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/property_initializer.phpt @@ -0,0 +1,26 @@ +--TEST-- +FCC in property initializer +--FILE-- +d); +var_dump(($c->d)("abc")); + + +?> +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/property_initializer_scope_001.phpt b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_001.phpt new file mode 100644 index 0000000000000..67428d1c6470e --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_001.phpt @@ -0,0 +1,32 @@ +--TEST-- +FCC in property initializer may access private methods. +--FILE-- +d); +var_dump(($c->d)("abc")); + +?> +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(11) "C::myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called C::myMethod +string(3) "abc" +NULL diff --git a/Zend/tests/first_class_callable/constexpr/property_initializer_scope_002.phpt b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_002.phpt new file mode 100644 index 0000000000000..ecee256012ece --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_002.phpt @@ -0,0 +1,27 @@ +--TEST-- +FCC in property initializer may not access unrelated private methods. +--FILE-- +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 diff --git a/Zend/tests/first_class_callable/constexpr/property_initializer_scope_003.phpt b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_003.phpt new file mode 100644 index 0000000000000..3959efbd16a05 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/property_initializer_scope_003.phpt @@ -0,0 +1,35 @@ +--TEST-- +FCC in property initializer may access protected methods of parent. +--FILE-- +d); +var_dump(($c->d)("abc")); + +?> +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(11) "C::myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called P::myMethod +string(3) "abc" +NULL diff --git a/Zend/tests/first_class_callable/constexpr/static_call.phpt b/Zend/tests/first_class_callable/constexpr/static_call.phpt new file mode 100644 index 0000000000000..26761a041a4cc --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/static_call.phpt @@ -0,0 +1,30 @@ +--TEST-- +Allow defining FCC for static methods in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(8) "myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called Foo::myMethod +string(3) "abc" diff --git a/Zend/tests/first_class_callable/constexpr/static_call_self.phpt b/Zend/tests/first_class_callable/constexpr/static_call_self.phpt new file mode 100644 index 0000000000000..6cbf1726caa91 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/static_call_self.phpt @@ -0,0 +1,30 @@ +--TEST-- +Allow defining FCC for static methods referenced by 'self::' in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(13) "Foo::myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called Foo::myMethod +string(3) "abc" diff --git a/Zend/tests/first_class_callable/constexpr/static_property_initializer.phpt b/Zend/tests/first_class_callable/constexpr/static_property_initializer.phpt new file mode 100644 index 0000000000000..d46b88aae9be0 --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/static_property_initializer.phpt @@ -0,0 +1,25 @@ +--TEST-- +FCC in static property initializer +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/first_class_callable/constexpr/userland.phpt b/Zend/tests/first_class_callable/constexpr/userland.phpt new file mode 100644 index 0000000000000..6ead1e51ff31c --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/userland.phpt @@ -0,0 +1,28 @@ +--TEST-- +Allow defining FCC for userland functions in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(11) "my_function" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called my_function +string(3) "abc" diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d4f71ee5ad99d..8b0a66fda8096 100644 --- a/Zend/zend_ast.c +++ b/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(); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index d3689af53ff7e..d0dad8490c4e3 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -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; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b4dea55414ffc..9711d05a1b1c6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 9742eaa190165..0c24aac48470a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -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)); } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 1734269186116..3b8ed89ec4f38 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -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); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..c671b3a295e5c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -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: diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index cdcdd1622746f..b6d17a1149d48 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -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)); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 1f2580a5adcaa..130e84b5c5874 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -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) { diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 56b8b2d69cac2..9fa0d4d85805c 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -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; diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 2a13f751ce3fa..e612dcd80748b 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -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(); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index bf0b3d1988144..df088d1231b75 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -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(); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 0c53923354c42..bb4f9c7170f28 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -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();