diff --git a/NEWS b/NEWS index 2bc7d6131aa81..476c00741d4a7 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,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) - Curl: . Added curl_multi_get_handles(). (timwolla) diff --git a/UPGRADING b/UPGRADING index 9daf6cca22603..c319d7299a4c0 100644 --- a/UPGRADING +++ b/UPGRADING @@ -44,6 +44,10 @@ PHP 8.5 UPGRADE NOTES 2. New Features ======================================== +- Core: + . Added support for Closures in constant expressions. + RFC: https://wiki.php.net/rfc/closures_in_const_expr + - DOM: . Added Dom\Element::$outerHTML. diff --git a/Zend/tests/closure_const_expr/attributes.phpt b/Zend/tests/closure_const_expr/attributes.phpt new file mode 100644 index 0000000000000..c83b821d12969 --- /dev/null +++ b/Zend/tests/closure_const_expr/attributes.phpt @@ -0,0 +1,57 @@ +--TEST-- +Allow defining closures in attributes +--EXTENSIONS-- +reflection +--FILE-- +getAttributes() as $reflectionAttribute) { + var_dump($reflectionAttribute->newInstance()); +} + +?> +--EXPECTF-- +object(Attr)#%d (1) { + ["value"]=> + object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + } +} +array(1) { + [0]=> + string(3) "foo" +} +object(Attr)#%d (1) { + ["value"]=> + object(Closure)#%d (4) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["parameter"]=> + array(1) { + ["$args"]=> + string(10) "" + } + } +} diff --git a/Zend/tests/closure_const_expr/attributes_ast_printing.phpt b/Zend/tests/closure_const_expr/attributes_ast_printing.phpt new file mode 100644 index 0000000000000..e87abf8c90611 --- /dev/null +++ b/Zend/tests/closure_const_expr/attributes_ast_printing.phpt @@ -0,0 +1,41 @@ +--TEST-- +AST printing for closures in attributes +--FILE-- +getMessage(), "\n"; +} + +try { + \assert( + ! + new #[Attr(static function ($foo) { + echo $foo; + })] + class {} + ); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +assert(!#[Attr(static function ($foo) { + echo $foo; +})] function () { +}) +assert(!new #[Attr(static function ($foo) { + echo $foo; +})] class { +}) diff --git a/Zend/tests/closure_const_expr/attributes_scope_001.phpt b/Zend/tests/closure_const_expr/attributes_scope_001.phpt new file mode 100644 index 0000000000000..6d89f410ca02b --- /dev/null +++ b/Zend/tests/closure_const_expr/attributes_scope_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +Closure in attribute may access private variables +--EXTENSIONS-- +reflection +--FILE-- +secret, PHP_EOL; +})] +class C { + public function __construct( + private string $secret, + ) {} +} + +foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) { + ($reflectionAttribute->newInstance()->value)(new C('secret')); +} + +?> +--EXPECT-- +secret diff --git a/Zend/tests/closure_const_expr/attributes_scope_002.phpt b/Zend/tests/closure_const_expr/attributes_scope_002.phpt new file mode 100644 index 0000000000000..ac168fd02de46 --- /dev/null +++ b/Zend/tests/closure_const_expr/attributes_scope_002.phpt @@ -0,0 +1,35 @@ +--TEST-- +Closure in attribute may not access unrelated private variables +--EXTENSIONS-- +reflection +--FILE-- +secret, PHP_EOL; +})] +class C { +} + +class E { + public function __construct( + private string $secret, + ) {} +} + +foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) { + ($reflectionAttribute->newInstance()->value)(new E('secret')); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d +Stack trace: +#0 %s(%d): C::{closure:%s:%d}(Object(E)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/basic.phpt b/Zend/tests/closure_const_expr/basic.phpt new file mode 100644 index 0000000000000..d1242c989b2d8 --- /dev/null +++ b/Zend/tests/closure_const_expr/basic.phpt @@ -0,0 +1,23 @@ +--TEST-- +Allow defining Closures in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(3) +} +called diff --git a/Zend/tests/closure_const_expr/class_const.phpt b/Zend/tests/closure_const_expr/class_const.phpt new file mode 100644 index 0000000000000..d392d0d7dfdc6 --- /dev/null +++ b/Zend/tests/closure_const_expr/class_const.phpt @@ -0,0 +1,25 @@ +--TEST-- +Allow defining Closures in class constants. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(4) +} +called diff --git a/Zend/tests/closure_const_expr/complex_array.phpt b/Zend/tests/closure_const_expr/complex_array.phpt new file mode 100644 index 0000000000000..87044cb3659f5 --- /dev/null +++ b/Zend/tests/closure_const_expr/complex_array.phpt @@ -0,0 +1,41 @@ +--TEST-- +Allow defining Closures wrapped in an array in const expressions. +--FILE-- + +--EXPECTF-- +array(2) { + [0]=> + object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(3) + } + [1]=> + object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(5) + } +} +called +also called diff --git a/Zend/tests/closure_const_expr/complex_new.phpt b/Zend/tests/closure_const_expr/complex_new.phpt new file mode 100644 index 0000000000000..9437bbdc259f4 --- /dev/null +++ b/Zend/tests/closure_const_expr/complex_new.phpt @@ -0,0 +1,33 @@ +--TEST-- +Allow defining Closures passed as constructor arguments in const expressions. +--FILE-- +c)(); + +?> +--EXPECTF-- +object(Dummy)#%d (1) { + ["c"]=> + object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(9) + } +} +called diff --git a/Zend/tests/closure_const_expr/default_args.phpt b/Zend/tests/closure_const_expr/default_args.phpt new file mode 100644 index 0000000000000..f1a9ee48f1146 --- /dev/null +++ b/Zend/tests/closure_const_expr/default_args.phpt @@ -0,0 +1,22 @@ +--TEST-- +Closures in default argument +--FILE-- + +--EXPECT-- +default +explicit diff --git a/Zend/tests/closure_const_expr/disallows_non_static.phpt b/Zend/tests/closure_const_expr/disallows_non_static.phpt new file mode 100644 index 0000000000000..3e67a0a4829f8 --- /dev/null +++ b/Zend/tests/closure_const_expr/disallows_non_static.phpt @@ -0,0 +1,18 @@ +--TEST-- +Disallows using non-static closures. +--FILE-- +d); +($foo->d)(); + +?> +--EXPECTF-- +Fatal error: Closures in constant expressions must be static in %s on line %d diff --git a/Zend/tests/closure_const_expr/disallows_using_variables.phpt b/Zend/tests/closure_const_expr/disallows_using_variables.phpt new file mode 100644 index 0000000000000..a7a212decf332 --- /dev/null +++ b/Zend/tests/closure_const_expr/disallows_using_variables.phpt @@ -0,0 +1,17 @@ +--TEST-- +Disallows using variables. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use(...) variables in constant expression in %s on line %d diff --git a/Zend/tests/closure_const_expr/property_initializer.phpt b/Zend/tests/closure_const_expr/property_initializer.phpt new file mode 100644 index 0000000000000..077a4e4f79a11 --- /dev/null +++ b/Zend/tests/closure_const_expr/property_initializer.phpt @@ -0,0 +1,27 @@ +--TEST-- +Closure in property initializer +--FILE-- +d); +($c->d)(); + + +?> +--EXPECTF-- +object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(4) +} +called diff --git a/Zend/tests/closure_const_expr/property_initializer_scope_001.phpt b/Zend/tests/closure_const_expr/property_initializer_scope_001.phpt new file mode 100644 index 0000000000000..bbd5b5b0ad84c --- /dev/null +++ b/Zend/tests/closure_const_expr/property_initializer_scope_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +Closure in property initializer may access private variables +--FILE-- +secret, PHP_EOL; + }; + + public function __construct( + private string $secret, + ) {} +} + +$c = new C('secret'); +($c->d)($c); + + +?> +--EXPECTF-- +secret diff --git a/Zend/tests/closure_const_expr/property_initializer_scope_002.phpt b/Zend/tests/closure_const_expr/property_initializer_scope_002.phpt new file mode 100644 index 0000000000000..a1c6942da1765 --- /dev/null +++ b/Zend/tests/closure_const_expr/property_initializer_scope_002.phpt @@ -0,0 +1,30 @@ +--TEST-- +Closure in property initializer may not access unrelated private variables +--FILE-- +secret, PHP_EOL; + }; + + +} + +class E { + public function __construct( + private string $secret, + ) {} +} + +$c = new C(); +($c->d)(new E('secret')); + + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d +Stack trace: +#0 %s(%d): C::{closure:%s:%d}(Object(E)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/static_initalizer.phpt b/Zend/tests/closure_const_expr/static_initalizer.phpt new file mode 100644 index 0000000000000..65bfb37393d42 --- /dev/null +++ b/Zend/tests/closure_const_expr/static_initalizer.phpt @@ -0,0 +1,27 @@ +--TEST-- +Closure in static initializer +--FILE-- + +--EXPECTF-- +object(Closure)#%d (3) { + ["name"]=> + string(17) "{closure:foo():4}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(4) +} +called diff --git a/Zend/tests/closure_const_expr/static_property_initializer.phpt b/Zend/tests/closure_const_expr/static_property_initializer.phpt new file mode 100644 index 0000000000000..0b7249d78bd6f --- /dev/null +++ b/Zend/tests/closure_const_expr/static_property_initializer.phpt @@ -0,0 +1,26 @@ +--TEST-- +Closure in static property initializer +--FILE-- + +--EXPECTF-- +object(Closure)#%d (3) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(4) +} +called diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d33747412ef0a..74dd471fb2cba 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -23,6 +23,7 @@ #include "zend_language_parser.h" #include "zend_smart_str.h" #include "zend_exceptions.h" +#include "zend_closures.h" #include "zend_constants.h" #include "zend_enum.h" @@ -989,6 +990,13 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( } return SUCCESS; } + case ZEND_AST_OP_ARRAY: + { + zend_function *func = Z_PTR_P(&((zend_ast_zval*)(ast))->val); + + zend_create_closure(result, func, scope, scope, NULL); + return SUCCESS; + } case ZEND_AST_PROP: case ZEND_AST_NULLSAFE_PROP: { @@ -1068,7 +1076,7 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast) { size_t size; - if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT) { + if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) { size = sizeof(zend_ast_zval); } else if (zend_ast_is_list(ast)) { uint32_t i; @@ -1126,6 +1134,13 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf) new->child[i] = NULL; } } + } else if (ast->kind == ZEND_AST_OP_ARRAY) { + zend_ast_zval *new = (zend_ast_zval*)buf; + new->kind = ZEND_AST_OP_ARRAY; + new->attr = ast->attr; + ZVAL_COPY(&new->val, &((zend_ast_zval *) ast)->val); + Z_LINENO(new->val) = zend_ast_get_lineno(ast); + buf = (void*)((char*)buf + sizeof(zend_ast_zval)); } else { uint32_t i, children = zend_ast_get_num_children(ast); zend_ast *new = (zend_ast*)buf; @@ -1189,6 +1204,8 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast) } } else if (EXPECTED(ast->kind == ZEND_AST_CONSTANT)) { zend_string_release_ex(zend_ast_get_constant_name(ast), 0); + } else if (EXPECTED(ast->kind == ZEND_AST_OP_ARRAY)) { + ZEND_ASSERT(!Z_REFCOUNTED(((zend_ast_zval*)(ast))->val)); } else if (EXPECTED(ast->kind >= ZEND_AST_FUNC_DECL)) { zend_ast_decl *decl = (zend_ast_decl *) ast; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 8d1d93f2564a7..6f68a961b7989 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -35,6 +35,7 @@ enum _zend_ast_kind { /* special nodes */ ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT, ZEND_AST_CONSTANT, + ZEND_AST_OP_ARRAY, ZEND_AST_ZNODE, /* declaration nodes */ @@ -362,7 +363,7 @@ static zend_always_inline uint32_t zend_ast_get_lineno(zend_ast *ast) { if (ast->kind == ZEND_AST_ZVAL) { zval *zv = zend_ast_get_zval(ast); return Z_LINENO_P(zv); - } else if (ast->kind == ZEND_AST_CONSTANT) { + } else if (ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) { zval *zv = &((zend_ast_zval *) ast)->val; return Z_LINENO_P(zv); } else { diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7e5351ddfafdb..c307a823898a1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8138,7 +8138,13 @@ static uint32_t zend_add_dynamic_func_def(zend_op_array *def) { return def_offset; } -static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, bool toplevel) /* {{{ */ +enum func_decl_level { + FUNC_DECL_LEVEL_TOPLEVEL, + FUNC_DECL_LEVEL_NESTED, + FUNC_DECL_LEVEL_CONSTEXPR, +}; + +static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, enum func_decl_level level) /* {{{ */ { zend_string *unqualified_name, *name, *lcname; zend_op *opline; @@ -8208,25 +8214,34 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, } zend_register_seen_symbol(lcname, ZEND_SYMBOL_FUNCTION); - if (!toplevel) { - uint32_t func_ref = zend_add_dynamic_func_def(op_array); - if (op_array->fn_flags & ZEND_ACC_CLOSURE) { - opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL); - opline->op2.num = func_ref; - } else { - opline = get_next_op(); - opline->opcode = ZEND_DECLARE_FUNCTION; - opline->op1_type = IS_CONST; - LITERAL_STR(opline->op1, zend_string_copy(lcname)); - opline->op2.num = func_ref; + switch (level) { + case FUNC_DECL_LEVEL_CONSTEXPR: + zend_add_dynamic_func_def(op_array); + break; + case FUNC_DECL_LEVEL_NESTED: { + uint32_t func_ref = zend_add_dynamic_func_def(op_array); + if (op_array->fn_flags & ZEND_ACC_CLOSURE) { + opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL); + opline->op2.num = func_ref; + } else { + opline = get_next_op(); + opline->opcode = ZEND_DECLARE_FUNCTION; + opline->op1_type = IS_CONST; + LITERAL_STR(opline->op1, zend_string_copy(lcname)); + opline->op2.num = func_ref; + } + break; } + case FUNC_DECL_LEVEL_TOPLEVEL: + /* Nothing to do. */ + break; } return lcname; } /* }}} */ static zend_op_array *zend_compile_func_decl_ex( - znode *result, zend_ast *ast, bool toplevel, + znode *result, zend_ast *ast, enum func_decl_level level, const zend_property_info *property_info, zend_property_hook_kind hook_kind ) { @@ -8272,7 +8287,7 @@ static zend_op_array *zend_compile_func_decl_ex( bool has_body = stmt_ast != NULL; lcname = zend_begin_method_decl(op_array, decl->name, has_body); } else { - lcname = zend_begin_func_decl(result, op_array, decl, toplevel); + lcname = zend_begin_func_decl(result, op_array, decl, level); if (decl->kind == ZEND_AST_ARROW_FUNC) { find_implicit_binds(&info, params_ast, stmt_ast); compile_implicit_lexical_binds(&info, result, op_array); @@ -8320,7 +8335,7 @@ static zend_op_array *zend_compile_func_decl_ex( CG(active_class_entry) = NULL; } - if (toplevel) { + if (level == FUNC_DECL_LEVEL_TOPLEVEL) { op_array->fn_flags |= ZEND_ACC_TOP_LEVEL; } @@ -8367,7 +8382,7 @@ static zend_op_array *zend_compile_func_decl_ex( CG(zend_lineno) = decl->start_lineno; zend_check_magic_method_implementation( CG(active_class_entry), (zend_function *) op_array, lcname, E_COMPILE_ERROR); - } else if (toplevel) { + } else if (level == FUNC_DECL_LEVEL_TOPLEVEL) { /* Only register the function after a successful compile */ if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) { CG(zend_lineno) = decl->start_lineno; @@ -8387,7 +8402,7 @@ static zend_op_array *zend_compile_func_decl_ex( /* Pop the loop variable stack separator */ zend_stack_del_top(&CG(loop_var_stack)); - if (toplevel) { + if (level == FUNC_DECL_LEVEL_TOPLEVEL) { zend_observer_function_declared_notify(op_array, lcname); } @@ -8401,9 +8416,9 @@ static zend_op_array *zend_compile_func_decl_ex( return op_array; } -static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) +static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, enum func_decl_level level) { - return zend_compile_func_decl_ex(result, ast, toplevel, /* property_info */ NULL, (zend_property_hook_kind)-1); + return zend_compile_func_decl_ex(result, ast, level, /* property_info */ NULL, (zend_property_hook_kind)-1); } zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name) { @@ -8542,7 +8557,7 @@ static void zend_compile_property_hooks( hook->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name)); zend_function *func = (zend_function *) zend_compile_func_decl_ex( - NULL, (zend_ast *) hook, /* toplevel */ false, prop_info, hook_kind); + NULL, (zend_ast *) hook, FUNC_DECL_LEVEL_NESTED, prop_info, hook_kind); func->common.prop_info = prop_info; @@ -11049,7 +11064,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_CONST_ENUM_INIT || 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_PROP || kind == ZEND_AST_NULLSAFE_PROP + || kind == ZEND_AST_CLOSURE; } /* }}} */ @@ -11183,6 +11199,29 @@ 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_closure(zend_ast **ast_ptr) +{ + zend_ast_decl *closure_ast = (zend_ast_decl *) *ast_ptr; + zend_ast *uses_ast = closure_ast->child[1]; + if (!(closure_ast->flags & ZEND_ACC_STATIC)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Closures in constant expressions must be static"); + } + if (uses_ast) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use(...) variables in constant expression"); + } + + znode node; + zend_op_array *op = zend_compile_func_decl(&node, *ast_ptr, FUNC_DECL_LEVEL_CONSTEXPR); + + zend_ast_destroy(*ast_ptr); + zval z; + ZVAL_PTR(&z, op); + *ast_ptr = zend_ast_create_zval(&z); + (*ast_ptr)->kind = ZEND_AST_OP_ARRAY; +} + static void zend_compile_const_expr_args(zend_ast **ast_ptr) { zend_ast_list *list = zend_ast_get_list(*ast_ptr); @@ -11245,6 +11284,9 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */ case ZEND_AST_ARG_LIST: zend_compile_const_expr_args(ast_ptr); break; + case ZEND_AST_CLOSURE: + zend_compile_const_expr_closure(ast_ptr); + break; } zend_ast_apply(ast, zend_compile_const_expr, context); @@ -11287,7 +11329,7 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */ if (ast->kind == ZEND_AST_FUNC_DECL) { CG(zend_lineno) = ast->lineno; - zend_compile_func_decl(NULL, ast, 1); + zend_compile_func_decl(NULL, ast, FUNC_DECL_LEVEL_TOPLEVEL); CG(zend_lineno) = ((zend_ast_decl *) ast)->end_lineno; } else if (ast->kind == ZEND_AST_CLASS) { CG(zend_lineno) = ast->lineno; @@ -11369,7 +11411,7 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ break; case ZEND_AST_FUNC_DECL: case ZEND_AST_METHOD: - zend_compile_func_decl(NULL, ast, 0); + zend_compile_func_decl(NULL, ast, FUNC_DECL_LEVEL_NESTED); break; case ZEND_AST_ENUM_CASE: zend_compile_enum_case(ast); @@ -11546,7 +11588,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ return; case ZEND_AST_CLOSURE: case ZEND_AST_ARROW_FUNC: - zend_compile_func_decl(result, ast, 0); + zend_compile_func_decl(result, ast, FUNC_DECL_LEVEL_NESTED); return; case ZEND_AST_THROW: zend_compile_throw(result, ast); diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index fe54c477cea77..f12170e8c9936 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -363,6 +363,9 @@ static void zend_file_cache_serialize_ast(zend_ast *ast, zend_file_cache_serialize_ast(tmp, script, info, buf); } } + } else if (ast->kind == ZEND_AST_OP_ARRAY) { + /* The op_array itself will be serialized as part of the dynamic_func_defs. */ + SERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val)); } else { uint32_t children = zend_ast_get_num_children(ast); for (i = 0; i < children; i++) { @@ -1242,6 +1245,9 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast, zend_file_cache_unserialize_ast(list->child[i], script, buf); } } + } else if (ast->kind == ZEND_AST_OP_ARRAY) { + /* The op_array itself will be unserialized as part of the dynamic_func_defs. */ + UNSERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val)); } else { uint32_t children = zend_ast_get_num_children(ast); for (i = 0; i < children; i++) { diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c5ddc040b22d8..11babd8aa4067 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -188,6 +188,10 @@ static zend_ast *zend_persist_ast(zend_ast *ast) } } node = (zend_ast *) copy; + } else if (ast->kind == ZEND_AST_OP_ARRAY) { + zend_ast_zval *copy = zend_shared_memdup(ast, sizeof(zend_ast_zval)); + zend_persist_op_array(©->val); + node = (zend_ast *) copy; } else { uint32_t children = zend_ast_get_num_children(ast); node = zend_shared_memdup(ast, zend_ast_size(children)); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index d7a0455e3e5c1..fe58d3f2de277 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -86,6 +86,9 @@ static void zend_persist_ast_calc(zend_ast *ast) zend_persist_ast_calc(list->child[i]); } } + } else if (ast->kind == ZEND_AST_OP_ARRAY) { + ADD_SIZE(sizeof(zend_ast_zval)); + zend_persist_op_array_calc(&((zend_ast_zval*)(ast))->val); } else { uint32_t children = zend_ast_get_num_children(ast); ADD_SIZE(zend_ast_size(children));