From 637bcd90917b13cbe279eba27c3153311d9a1665 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 22 Sep 2025 23:27:07 +0200 Subject: [PATCH 1/2] Infer static for closures See https://github.com/php/php-src/issues/19754#issuecomment-3321562012 --- Zend/tests/arrow_functions/005.phpt | 3 +-- Zend/tests/bug75079_2.phpt | 2 +- Zend/tests/closures/closure_020.phpt | 4 +--- Zend/tests/closures/closure_026.phpt | 11 ++------- Zend/tests/closures/closure_040.phpt | 4 +--- Zend/tests/closures/closure_041.phpt | 4 ---- Zend/tests/closures/closure_043.phpt | 10 +-------- Zend/tests/closures/closure_061.phpt | 4 ++-- Zend/tests/gc/bug60139.phpt | 4 ++-- Zend/tests/return_types/012.phpt | 5 +---- Zend/tests/return_types/013.phpt | 2 +- Zend/zend_closures.c | 26 +++++++++++++--------- Zend/zend_compile.c | 24 +++++++++++++++++++- Zend/zend_compile.h | 3 ++- ext/reflection/tests/closures_005.phpt | 2 +- ext/standard/tests/array/compact_this.phpt | 5 +---- 16 files changed, 56 insertions(+), 57 deletions(-) diff --git a/Zend/tests/arrow_functions/005.phpt b/Zend/tests/arrow_functions/005.phpt index 7371be57bbe20..ec51103d8f2ca 100644 --- a/Zend/tests/arrow_functions/005.phpt +++ b/Zend/tests/arrow_functions/005.phpt @@ -40,8 +40,7 @@ class Test { ?> --EXPECT-- -object(Test)#1 (0) { -} +NULL object(Test)#1 (0) { } object(Test)#1 (0) { diff --git a/Zend/tests/bug75079_2.phpt b/Zend/tests/bug75079_2.phpt index 8e0fe481621ae..a0a0baa516c7f 100644 --- a/Zend/tests/bug75079_2.phpt +++ b/Zend/tests/bug75079_2.phpt @@ -31,6 +31,6 @@ int(123) Fatal error: Uncaught Error: Cannot access private property Foo::$bar in %s:%d Stack trace: -#0 %s(%d): A->{closure:%s:%d}() +#0 %s(%d): A::{closure:%s:%d}() #1 {main} thrown in %s on line %d diff --git a/Zend/tests/closures/closure_020.phpt b/Zend/tests/closures/closure_020.phpt index 658a1a232e44f..95209765f1759 100644 --- a/Zend/tests/closures/closure_020.phpt +++ b/Zend/tests/closures/closure_020.phpt @@ -28,7 +28,7 @@ object(foo)#%d (2) { ["test":"foo":private]=> int(3) ["a"]=> - object(Closure)#%d (5) { + object(Closure)#%d (%d) { ["name"]=> string(%d) "{closure:%s:%d}" ["file"]=> @@ -40,8 +40,6 @@ object(foo)#%d (2) { ["a"]=> *RECURSION* } - ["this"]=> - *RECURSION* } } bool(true) diff --git a/Zend/tests/closures/closure_026.phpt b/Zend/tests/closures/closure_026.phpt index 99ce41201b5d8..a93debea9f3cd 100644 --- a/Zend/tests/closures/closure_026.phpt +++ b/Zend/tests/closures/closure_026.phpt @@ -33,15 +33,13 @@ object(foo)#%d (1) { ["a"]=> array(1) { [0]=> - object(Closure)#%d (4) { + object(Closure)#%d (%d) { ["name"]=> string(%d) "{closure:%s:%d}" ["file"]=> string(%d) "%s" ["line"]=> int(%d) - ["this"]=> - *RECURSION* } } } @@ -50,18 +48,13 @@ int(1) string(1) "a" array(1) { [0]=> - object(Closure)#%d (4) { + object(Closure)#%d (%d) { ["name"]=> string(%d) "{closure:%s:%d}" ["file"]=> string(%d) "%s" ["line"]=> int(%d) - ["this"]=> - object(foo)#%d (1) { - ["a"]=> - *RECURSION* - } } } int(1) diff --git a/Zend/tests/closures/closure_040.phpt b/Zend/tests/closures/closure_040.phpt index b733bdbef0537..6182e351ac04e 100644 --- a/Zend/tests/closures/closure_040.phpt +++ b/Zend/tests/closures/closure_040.phpt @@ -33,7 +33,5 @@ try { $cas->bindTo($a, 'A'); ?> ---EXPECTF-- +--EXPECT-- Closure::bindTo(): Argument #2 ($newScope) must be of type object|string|null, array given - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d diff --git a/Zend/tests/closures/closure_041.phpt b/Zend/tests/closures/closure_041.phpt index 64afde4ea22b1..79f22ecd3c1df 100644 --- a/Zend/tests/closures/closure_041.phpt +++ b/Zend/tests/closures/closure_041.phpt @@ -84,12 +84,8 @@ Warning: Cannot unbind $this of closure using $this, this will be an error in PH NULL After binding, with same-class instance for the bound ones - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d scoped to A: bool(false) bound: A (should be scoped to dummy class) - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d scoped to A: bool(true) bound: A After binding, with different instance for the bound ones diff --git a/Zend/tests/closures/closure_043.phpt b/Zend/tests/closures/closure_043.phpt index 8f60bd272469f..e6d0991779b6d 100644 --- a/Zend/tests/closures/closure_043.phpt +++ b/Zend/tests/closures/closure_043.phpt @@ -39,7 +39,7 @@ $d = $staticScoped->bindTo(new A, 'A'); echo "Done.\n"; ?> ---EXPECTF-- +--EXPECT-- Before binding bool(false) bool(false) @@ -55,10 +55,6 @@ bool(false) bool(false) After binding, null scope, with instance - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d After binding, with scope, no instance bool(true) bool(false) @@ -67,8 +63,4 @@ bool(true) bool(false) After binding, with scope, with instance - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d - -Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d Done. diff --git a/Zend/tests/closures/closure_061.phpt b/Zend/tests/closures/closure_061.phpt index 23ab2f87a1c5b..e55b7ef768fbb 100644 --- a/Zend/tests/closures/closure_061.phpt +++ b/Zend/tests/closures/closure_061.phpt @@ -157,10 +157,10 @@ bindTo(null, Cls::class): Success! bindTo(new Cls, null): -Cannot bind an instance to a static closure, this will be an error in PHP 9 +Cannot rebind scope of closure created from method, this will be an error in PHP 9 bindTo(new Cls, Cls::class): -Cannot bind an instance to a static closure, this will be an error in PHP 9 +Success! bindTo(null, null): Cannot rebind scope of closure created from method, this will be an error in PHP 9 diff --git a/Zend/tests/gc/bug60139.phpt b/Zend/tests/gc/bug60139.phpt index d5926b28f06b8..59ec17f0072c3 100644 --- a/Zend/tests/gc/bug60139.phpt +++ b/Zend/tests/gc/bug60139.phpt @@ -8,7 +8,7 @@ class Foo { public $x; public function __construct() { - $this->x = function() {}; + $this->x = function () { $this; }; } } @@ -17,7 +17,7 @@ class Bar { public function __construct() { $self = $this; - $this->x = function() use ($self) {}; + $this->x = function() use ($self) { $this; }; } } diff --git a/Zend/tests/return_types/012.phpt b/Zend/tests/return_types/012.phpt index 8548883049802..fe45feb41866e 100644 --- a/Zend/tests/return_types/012.phpt +++ b/Zend/tests/return_types/012.phpt @@ -15,7 +15,7 @@ $baz = new foo(); var_dump($baz->bar()); ?> --EXPECTF-- -object(Closure)#%d (5) { +object(Closure)#%d (4) { ["name"]=> string(%d) "{closure:%s:%d}" ["file"]=> @@ -27,7 +27,4 @@ object(Closure)#%d (5) { ["test"]=> string(3) "one" } - ["this"]=> - object(foo)#%d (0) { - } } diff --git a/Zend/tests/return_types/013.phpt b/Zend/tests/return_types/013.phpt index 5faf54da6d7f4..b30421a71e0df 100644 --- a/Zend/tests/return_types/013.phpt +++ b/Zend/tests/return_types/013.phpt @@ -17,6 +17,6 @@ var_dump($func=$baz->bar(), $func()); --EXPECTF-- Fatal error: Uncaught TypeError: foo::{closure:%s:%d}(): Return value must be of type array, null returned in %s:%d Stack trace: -#0 %s(%d): foo->{closure:%s:%d}() +#0 %s(%d): foo::{closure:%s:%d}() #1 {main} thrown in %s on line %d diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 22fd30463726f..6ccca4e4f0d72 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -75,14 +75,19 @@ ZEND_METHOD(Closure, __invoke) /* {{{ */ /* }}} */ static bool zend_valid_closure_binding( - zend_closure *closure, zval *newthis, zend_class_entry *scope) /* {{{ */ + zend_closure *closure, zval **newthis_ptr, zend_class_entry *scope) /* {{{ */ { + zval *newthis = *newthis_ptr; zend_function *func = &closure->func; bool is_fake_closure = (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0; if (newthis) { if (func->common.fn_flags & ZEND_ACC_STATIC) { - zend_error(E_WARNING, "Cannot bind an instance to a static closure, this will be an error in PHP 9"); - return 0; + // FIXME: Restrict this workaround to implicitly static closures? + // Will need an additional fn_flag. + zend_object *obj = Z_OBJ_P(newthis); + ZVAL_UNDEF(newthis); + GC_DTOR(obj); + *newthis_ptr = NULL; } if (is_fake_closure && func->common.scope && @@ -144,13 +149,14 @@ ZEND_METHOD(Closure, call) closure = (zend_closure *) Z_OBJ_P(ZEND_THIS); - newobj = Z_OBJ_P(newthis); - newclass = newobj->ce; + newclass = Z_OBJ_P(newthis)->ce; - if (!zend_valid_closure_binding(closure, newthis, newclass)) { + if (!zend_valid_closure_binding(closure, &newthis, newclass)) { return; } + newobj = newthis ? Z_OBJ_P(newthis) : NULL; + fci_cache.called_scope = newclass; fci_cache.object = fci.object = newobj; @@ -241,16 +247,16 @@ static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, z ce = NULL; } - if (!zend_valid_closure_binding(closure, newthis, ce)) { - return; - } - if (newthis) { called_scope = Z_OBJCE_P(newthis); } else { called_scope = ce; } + if (!zend_valid_closure_binding(closure, &newthis, ce)) { + return; + } + zend_create_closure(return_value, &closure->func, ce, called_scope, newthis); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b46b5d5bff8d2..1dadbe9e664c4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -345,6 +345,7 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra CG(context).in_jmp_frameless_branch = false; CG(context).active_property_info_name = NULL; CG(context).active_property_hook_kind = (zend_property_hook_kind)-1; + CG(context).closure_may_use_this = false; } /* }}} */ @@ -4022,6 +4023,8 @@ static void zend_compile_dynamic_call(znode *result, znode *name_node, zend_ast zend_string *method = zend_string_init(colon + 1, ZSTR_LEN(str) - (colon - ZSTR_VAL(str)) - 1, 0); zend_op *opline = get_next_op(); + CG(context).closure_may_use_this = true; + opline->opcode = ZEND_INIT_STATIC_METHOD_CALL; opline->op1_type = IS_CONST; opline->op1.constant = zend_add_class_name_literal(class); @@ -4731,6 +4734,12 @@ static uint32_t zend_compile_frameless_icall(znode *result, zend_ast_list *args, static void zend_compile_ns_call(znode *result, znode *name_node, zend_ast *args_ast, uint32_t lineno, uint32_t type) /* {{{ */ { int name_constants = zend_add_ns_func_name_literal(Z_STR(name_node->u.constant)); + zend_string *lc_func_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 2)); + + if (zend_string_equals_literal(lc_func_name, "call_user_func") + || zend_string_equals_literal(lc_func_name, "call_user_func_array")) { + CG(context).closure_may_use_this = true; + } /* Find frameless function with same name. */ zend_function *frameless_function = NULL; @@ -4738,7 +4747,6 @@ static void zend_compile_ns_call(znode *result, znode *name_node, zend_ast *args && !zend_args_contain_unpack_or_named(zend_ast_get_list(args_ast)) /* Avoid blowing up op count with nested frameless branches. */ && !CG(context).in_jmp_frameless_branch) { - zend_string *lc_func_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 2)); frameless_function = zend_hash_find_ptr(CG(function_table), lc_func_name); } @@ -5166,6 +5174,7 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{ if (name_ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(name_ast)) != IS_STRING) { zend_compile_expr(&name_node, name_ast); zend_compile_dynamic_call(result, &name_node, args_ast, ast->lineno); + CG(context).closure_may_use_this = true; return; } @@ -5198,6 +5207,9 @@ static void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{ zend_string_release(lcname); zval_ptr_dtor(&name_node.u.constant); return; + } else if (fbc && (zend_string_equals_literal(lcname, "call_user_func") + || zend_string_equals_literal(lcname, "call_user_func_array"))) { + CG(context).closure_may_use_this = true; } if (!fbc @@ -5356,6 +5368,8 @@ static void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type } } + CG(context).closure_may_use_this = true; + opline = get_next_op(); opline->opcode = ZEND_INIT_STATIC_METHOD_CALL; @@ -8595,6 +8609,13 @@ static zend_op_array *zend_compile_func_decl_ex( zend_compile_stmt(stmt_ast); + if ((decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) + && !(op_array->fn_flags & ZEND_ACC_USES_THIS) + && !CG(context).closure_may_use_this + && !info.varvars_used) { + op_array->fn_flags |= ZEND_ACC_STATIC; + } + if (is_method) { CG(zend_lineno) = decl->start_lineno; zend_check_magic_method_implementation( @@ -11941,6 +11962,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ return; case ZEND_AST_CLOSURE: case ZEND_AST_ARROW_FUNC: + CG(context).closure_may_use_this = true; zend_compile_func_decl(result, ast, FUNC_DECL_LEVEL_NESTED); return; case ZEND_AST_THROW: diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c07fa9bfa7d7e..bb532a1600ffa 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -208,7 +208,8 @@ typedef struct _zend_oparray_context { zend_string *active_property_info_name; zend_property_hook_kind active_property_hook_kind; bool in_jmp_frameless_branch; - bool has_assigned_to_http_response_header; + bool has_assigned_to_http_response_header; + bool closure_may_use_this; } zend_oparray_context; /* Class, property and method flags class|meth.|prop.|const*/ diff --git a/ext/reflection/tests/closures_005.phpt b/ext/reflection/tests/closures_005.phpt index 91d0c9e9be26a..7448d38b83407 100644 --- a/ext/reflection/tests/closures_005.phpt +++ b/ext/reflection/tests/closures_005.phpt @@ -21,7 +21,7 @@ var_dump( (new ReflectionMethod($foo, 'baz'))->isStatic(),); ?> --EXPECT-- -bool(false) +bool(true) bool(true) bool(true) bool(true) diff --git a/ext/standard/tests/array/compact_this.phpt b/ext/standard/tests/array/compact_this.phpt index f3677e03e276c..9b88d98fdf6db 100644 --- a/ext/standard/tests/array/compact_this.phpt +++ b/ext/standard/tests/array/compact_this.phpt @@ -39,8 +39,5 @@ array(1) { object(class@anonymous)#1 (0) { } } -array(1) { - ["this"]=> - object(class@anonymous)#1 (0) { - } +array(0) { } From 727086f47a1bc493bb83769c3ececf01ff6b0f68 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 24 Sep 2025 17:07:20 +0200 Subject: [PATCH 2/2] Constant lambda cache --- Zend/Optimizer/compact_literals.c | 6 ++++++ Zend/zend_compile.c | 25 ++++++++++++++++++++----- Zend/zend_execute_API.c | 12 ++++++++++++ Zend/zend_globals.h | 2 ++ Zend/zend_vm_def.h | 17 +++++++++++++++-- Zend/zend_vm_execute.h | 30 ++++++++++++++++++++++++++++-- Zend/zend_vm_opcodes.c | 2 +- 7 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index d0aaccec7ce2c..8360f496cf3b3 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -740,6 +740,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx cache_size += 2 * sizeof(void *); } break; + case ZEND_DECLARE_LAMBDA_FUNCTION: + if (opline->extended_value != (uint32_t)-1) { + opline->extended_value = cache_size; + cache_size += sizeof(void *); + } + break; } opline++; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1dadbe9e664c4..a53c2824ff9ee 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8427,6 +8427,7 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *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; + opline->extended_value = (uint32_t)-1; } else { opline = get_next_op(); opline->opcode = ZEND_DECLARE_FUNCTION; @@ -8609,11 +8610,25 @@ static zend_op_array *zend_compile_func_decl_ex( zend_compile_stmt(stmt_ast); - if ((decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) - && !(op_array->fn_flags & ZEND_ACC_USES_THIS) - && !CG(context).closure_may_use_this - && !info.varvars_used) { - op_array->fn_flags |= ZEND_ACC_STATIC; + if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) { + /* Attempt to infer static for closures that don't use $this. */ + if (!(op_array->fn_flags & ZEND_ACC_USES_THIS) + && !CG(context).closure_may_use_this + && !info.varvars_used) { + op_array->fn_flags |= ZEND_ACC_STATIC; + } + + zend_op_array *declaring_op_array = orig_oparray_context.op_array; + + if ((op_array->fn_flags & ZEND_ACC_STATIC) + && !op_array->static_variables + && declaring_op_array->last) { + zend_op *declare_lambda_op = &declaring_op_array->opcodes[declaring_op_array->last - 1]; + if (declare_lambda_op->opcode == ZEND_DECLARE_LAMBDA_FUNCTION) { + declare_lambda_op->extended_value = declaring_op_array->cache_size; + declaring_op_array->cache_size += sizeof(void *); + } + } } if (is_method) { diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 91b8c5ab210ef..f8a74866dcecb 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -203,6 +203,8 @@ void init_executor(void) /* {{{ */ zend_fiber_init(); zend_weakrefs_init(); + zend_stack_init(&EG(lambda_cache), sizeof(zend_object *)); + EG(active) = 1; } /* }}} */ @@ -267,6 +269,14 @@ void shutdown_destructors(void) /* {{{ */ } /* }}} */ +static void lambda_dtor(zend_object **closure_ptr) +{ + zend_object *closure = *closure_ptr; + if (GC_DELREF(closure) == 0) { + zend_objects_store_del(closure); + } +} + /* Free values held by the executor. */ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) { @@ -420,6 +430,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) zend_stack_clean(&EG(user_error_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1); zend_stack_clean(&EG(user_exception_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1); + zend_stack_clean(&EG(lambda_cache), (void (*)(void *)) lambda_dtor, 1); + #if ZEND_DEBUG if (!CG(unclean_shutdown)) { gc_collect_cycles(); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 48b978b535014..455949e475f1a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -319,6 +319,8 @@ struct _zend_executor_globals { zend_strtod_state strtod_state; + zend_stack lambda_cache; + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3ebf816cf3817..53999e1b8da8a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8333,13 +8333,21 @@ ZEND_VM_HANDLER(210, ZEND_DECLARE_ATTRIBUTED_CONST, CONST, CONST) ZEND_VM_NEXT_OPCODE_EX(1, 2); } -ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, NUM) +ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, NUM, NUM|CACHE_SLOT) { USE_OPLINE zend_function *func; zval *object; zend_class_entry *called_scope; + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = CACHED_PTR(opline->extended_value); + if (closure) { + ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure); + ZEND_VM_NEXT_OPCODE(); + } + } + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; if (Z_TYPE(EX(This)) == IS_OBJECT) { called_scope = Z_OBJCE(EX(This)); @@ -8356,7 +8364,12 @@ ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, NUM) SAVE_OPLINE(); zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); - + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = Z_OBJ_P(EX_VAR(opline->result.var)); + GC_ADDREF(closure); + CACHE_PTR(opline->extended_value, closure); + zend_stack_push(&EG(lambda_cache), &closure); + } ZEND_VM_NEXT_OPCODE(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b0d6f2bc33d96..75fee37f0e788 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5903,6 +5903,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DECLARE_LAMBD zval *object; zend_class_entry *called_scope; + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = CACHED_PTR(opline->extended_value); + if (closure) { + ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure); + ZEND_VM_NEXT_OPCODE(); + } + } + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; if (Z_TYPE(EX(This)) == IS_OBJECT) { called_scope = Z_OBJCE(EX(This)); @@ -5919,7 +5927,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DECLARE_LAMBD SAVE_OPLINE(); zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); - + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = Z_OBJ_P(EX_VAR(opline->result.var)); + GC_ADDREF(closure); + CACHE_PTR(opline->extended_value, closure); + zend_stack_push(&EG(lambda_cache), &closure); + } ZEND_VM_NEXT_OPCODE(); } @@ -61326,6 +61339,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DECLARE_LAMBDA_FUN zval *object; zend_class_entry *called_scope; + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = CACHED_PTR(opline->extended_value); + if (closure) { + ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure); + ZEND_VM_NEXT_OPCODE(); + } + } + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; if (Z_TYPE(EX(This)) == IS_OBJECT) { called_scope = Z_OBJCE(EX(This)); @@ -61342,7 +61363,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DECLARE_LAMBDA_FUN SAVE_OPLINE(); zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); - + if (opline->extended_value != (uint32_t)-1) { + zend_object *closure = Z_OBJ_P(EX_VAR(opline->result.var)); + GC_ADDREF(closure); + CACHE_PTR(opline->extended_value, closure); + zend_stack_push(&EG(lambda_cache), &closure); + } ZEND_VM_NEXT_OPCODE(); } diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 00ad38baaafeb..b12a78b1c9ce6 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -379,7 +379,7 @@ static uint32_t zend_vm_opcodes_flags[211] = { 0x00000000, 0x00000101, 0x00001000, - 0x00001003, + 0x01041003, 0x00000303, 0x00000003, 0x00000303,