]> granicus.if.org Git - php/commitdiff
Reference dynamic functions through dynamic_defs
authorNikita Popov <nikita.ppv@gmail.com>
Mon, 18 May 2020 13:46:06 +0000 (15:46 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 1 Mar 2021 10:35:54 +0000 (11:35 +0100)
Currently, dynamically declared functions and closures are inserted
into the function table under a runtime definition key, and then later
possibly renamed. When opcache is not used and a file containing a
closure is repeatedly included, this leads to a very large memory leak,
as the no longer needed closure declarations will never be freed
(https://bugs.php.net/bug.php?id=76982).

With this patch, dynamic functions are instead stored in a
dynamic_func_defs member on the op_array, which opcodes reference
by index. When the parent op_array is destroyed, the dynamic_func_defs
it contains are also destroyed (unless they are stilled used elsewhere,
e.g. because they have been bound, or are used by a live closure). This
resolves the fundamental part of the leak, though doesn't completely
fix it yet due to some arena allocations.

The main non-obvious change here is to static variable handling:
We can't destroy static_variables_ptr in destroy_op_array, as e.g.
that would clear the static variables in a dynamic function when
the op_array containing it is destroyed. Static variable destruction
is separated out for this reason (we already do static variable
destruction separately for normal functions, so we only need to
handle main scripts).

Closes GH-5595.

19 files changed:
Zend/Optimizer/compact_literals.c
Zend/Optimizer/zend_optimizer.c
Zend/tests/static_variable_in_dynamic_function.phpt [new file with mode: 0644]
Zend/tests/static_variable_in_dynamic_function_2.phpt [new file with mode: 0644]
Zend/zend.c
Zend/zend_closures.c
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_execute_API.c
Zend/zend_opcode.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
Zend/zend_vm_handlers.h
Zend/zend_vm_opcodes.c
ext/opcache/zend_accelerator_util_funcs.c
ext/opcache/zend_file_cache.c
ext/opcache/zend_persist.c
ext/opcache/zend_persist_calc.c
sapi/phpdbg/phpdbg_print.c

index 0e1529d2bd1953690730cc99c88838657829782a..55cc40afb9b52b4b4c5186a15a0f83e75d951c5f 100644 (file)
@@ -241,9 +241,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
                                case ZEND_RECV_INIT:
                                        LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
                                        break;
-                               case ZEND_DECLARE_FUNCTION:
-                                       LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
-                                       break;
                                case ZEND_DECLARE_CLASS:
                                case ZEND_DECLARE_CLASS_DELAYED:
                                        LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
@@ -776,7 +773,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
                                                bind_var_slot[opline->op2.constant] = opline->extended_value;
                                        }
                                        break;
-                               case ZEND_DECLARE_LAMBDA_FUNCTION:
                                case ZEND_DECLARE_ANON_CLASS:
                                case ZEND_DECLARE_CLASS_DELAYED:
                                        opline->extended_value = cache_size;
index ae548fc5200bdda0c8a334757c669098f532808e..cc9971a9f87bcf9768fa760c7e10e87dfacbbf64 100644 (file)
@@ -1367,16 +1367,24 @@ static bool needs_live_range(zend_op_array *op_array, zend_op *def_opline) {
        return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0;
 }
 
+static void zend_foreach_op_array_helper(
+               zend_op_array *op_array, zend_op_array_func_t func, void *context) {
+       func(op_array, context);
+       for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+               func(op_array->dynamic_func_defs[i], context);
+       }
+}
+
 void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context)
 {
        zend_class_entry *ce;
        zend_string *key;
        zend_op_array *op_array;
 
-       func(&script->main_op_array, context);
+       zend_foreach_op_array_helper(&script->main_op_array, func, context);
 
        ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) {
-               func(op_array, context);
+               zend_foreach_op_array_helper(op_array, func, context);
        } ZEND_HASH_FOREACH_END();
 
        ZEND_HASH_FOREACH_STR_KEY_PTR(&script->class_table, key, ce) {
@@ -1387,7 +1395,7 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void
                        if (op_array->scope == ce
                                        && op_array->type == ZEND_USER_FUNCTION
                                        && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
-                               func(op_array, context);
+                               zend_foreach_op_array_helper(op_array, func, context);
                        }
                } ZEND_HASH_FOREACH_END();
        } ZEND_HASH_FOREACH_END();
diff --git a/Zend/tests/static_variable_in_dynamic_function.phpt b/Zend/tests/static_variable_in_dynamic_function.phpt
new file mode 100644 (file)
index 0000000..77b71a4
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Static variables in dynamically declared function (first use before dynamic def dtor)
+--FILE--
+<?php
+
+$code = <<<'CODE'
+if (1) {
+    function test() {
+        static $x = 0;
+        var_dump(++$x);
+    }
+    test();
+}
+CODE;
+eval($code);
+test();
+
+?>
+--EXPECT--
+int(1)
+int(2)
diff --git a/Zend/tests/static_variable_in_dynamic_function_2.phpt b/Zend/tests/static_variable_in_dynamic_function_2.phpt
new file mode 100644 (file)
index 0000000..f7c1603
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Static variables in dynamically declared function (first use after dynamic def dtor)
+--FILE--
+<?php
+
+$code = <<<'CODE'
+if (1) {
+    function test() {
+        static $x = 0;
+        var_dump(++$x);
+    }
+}
+CODE;
+eval($code);
+test();
+test();
+
+?>
+--EXPECT--
+int(1)
+int(2)
index ce6287e2d1f6ff47db1d9157a62e01af32c43897..9e5c7e2328af6f800c56c13c74e817f334ad5eb8 100644 (file)
@@ -1696,6 +1696,7 @@ ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count
                                        ret = zend_exception_error(EG(exception), E_ERROR);
                                }
                        }
+                       zend_destroy_static_vars(op_array);
                        destroy_op_array(op_array);
                        efree_size(op_array, sizeof(zend_op_array));
                } else if (type==ZEND_REQUIRE) {
index 452f99e6fb3c134bb7a98c2e8fe6f4faefc95db9..f1ffe78c692d16fbdd77fa3f27fcf937f0593fa5 100644 (file)
@@ -486,10 +486,9 @@ static void zend_closure_free_storage(zend_object *object) /* {{{ */
        zend_object_std_dtor(&closure->std);
 
        if (closure->func.type == ZEND_USER_FUNCTION) {
-               /* We shared static_variables with the original function.
-                * Unshare now so we don't try to destroy them. */
-               if (closure->func.op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE) {
-                       ZEND_MAP_PTR_INIT(closure->func.op_array.static_variables_ptr, NULL);
+               /* We don't own the static variables of fake closures. */
+               if (!(closure->func.op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE)) {
+                       zend_destroy_static_vars(&closure->func.op_array);
                }
                destroy_op_array(&closure->func.op_array);
        }
index 2b2dc9b24906106aeb8726f81a42bfb25b24839d..deff0dbe57368b03454cfe0a380a6ac8396fc92c 100644 (file)
@@ -1091,27 +1091,19 @@ static zend_never_inline ZEND_COLD ZEND_NORETURN void do_bind_function_error(zen
        }
 }
 
-ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
+ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ */
 {
-       zend_function *function;
-       zval *rtd_key, *zv;
-
-       rtd_key = lcname + 1;
-       zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
-       if (UNEXPECTED(!zv)) {
-               do_bind_function_error(Z_STR_P(lcname), NULL, 0);
+       zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func);
+       if (UNEXPECTED(!added_func)) {
+               do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0);
                return FAILURE;
        }
-       function = (zend_function*)Z_PTR_P(zv);
-       if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
-                       && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
-               zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
-       } else {
-               zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
+
+       if (func->op_array.refcount) {
+               ++*func->op_array.refcount;
        }
-       if (UNEXPECTED(!zv)) {
-               do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);
-               return FAILURE;
+       if (func->common.function_name) {
+               zend_string_addref(func->common.function_name);
        }
        return SUCCESS;
 }
@@ -6954,9 +6946,18 @@ zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string *name,
 }
 /* }}} */
 
+static uint32_t zend_add_dynamic_func_def(zend_op_array *def) {
+       zend_op_array *op_array = CG(active_op_array);
+       uint32_t def_offset = op_array->num_dynamic_func_defs++;
+       op_array->dynamic_func_defs = erealloc(
+               op_array->dynamic_func_defs, op_array->num_dynamic_func_defs * sizeof(zend_op_array *));
+       op_array->dynamic_func_defs[def_offset] = def;
+       return def_offset;
+}
+
 static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, bool toplevel) /* {{{ */
 {
-       zend_string *unqualified_name, *name, *lcname, *key;
+       zend_string *unqualified_name, *name, *lcname;
        zend_op *opline;
 
        unqualified_name = decl->name;
@@ -6992,25 +6993,16 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as
                return;
        }
 
-       /* Generate RTD keys until we find one that isn't in use yet. */
-       key = NULL;
-       do {
-               zend_tmp_string_release(key);
-               key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
-       } while (!zend_hash_add_ptr(CG(function_table), key, op_array));
-
+       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->extended_value = zend_alloc_cache_slot();
-               opline->op1_type = IS_CONST;
-               LITERAL_STR(opline->op1, key);
+               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));
-               /* RTD key is placed after lcname literal in op1 */
-               zend_add_literal_string(&key);
+               opline->op2.num = func_ref;
        }
        zend_string_release_ex(lcname, 0);
 }
index ce4b50aaee3c2a63eebeb1fbba1e90a083fd160c..6f64624fd87a28051a5956fa3c917f32d4fb61a4 100644 (file)
@@ -456,8 +456,13 @@ struct _zend_op_array {
        zend_string *doc_comment;
 
        int last_literal;
+       uint32_t num_dynamic_func_defs;
        zval *literals;
 
+       /* Functions that are declared dynamically are stored here and
+        * referenced by index from opcodes. */
+       zend_op_array **dynamic_func_defs;
+
        void *reserved[ZEND_MAX_RESERVED_RESOURCES];
 };
 
@@ -781,7 +786,7 @@ bool zend_handle_encoding_declaration(zend_ast *ast);
 /* parser-driven code generators */
 void zend_do_free(znode *op1);
 
-ZEND_API zend_result do_bind_function(zval *lcname);
+ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname);
 ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name);
 ZEND_API uint32_t zend_build_delayed_early_binding_list(const zend_op_array *op_array);
 ZEND_API void zend_do_delayed_early_binding(zend_op_array *op_array, uint32_t first_early_binding_opline);
@@ -812,6 +817,7 @@ ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...);
 ZEND_API int open_file_for_scanning(zend_file_handle *file_handle);
 ZEND_API void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_size);
 ZEND_API void destroy_op_array(zend_op_array *op_array);
+ZEND_API void zend_destroy_static_vars(zend_op_array *op_array);
 ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle);
 ZEND_API void zend_cleanup_mutable_class_data(zend_class_entry *ce);
 ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce);
index 1d484934e3e09b85423467187288163a8625e399..4e8c1d5a1f379c5df3bc5f69d77f7024b2ab4004 100644 (file)
@@ -1224,6 +1224,7 @@ ZEND_API zend_result zend_eval_stringl(const char *str, size_t str_len, zval *re
                }
 
                EG(no_extensions)=0;
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                retval = SUCCESS;
index ac9cc5f7041bced4bf27e469acff6af88cddd82e..4e30b9ff7ad9badb905a6c4c16eb4dd79ea96c8b 100644 (file)
@@ -85,6 +85,9 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz
        op_array->last_literal = 0;
        op_array->literals = NULL;
 
+       op_array->num_dynamic_func_defs = 0;
+       op_array->dynamic_func_defs = NULL;
+
        ZEND_MAP_PTR_INIT(op_array->run_time_cache, NULL);
        op_array->cache_size = zend_op_array_extension_handles * sizeof(void*);
 
@@ -511,16 +514,20 @@ void zend_class_add_ref(zval *zv)
        }
 }
 
-ZEND_API void destroy_op_array(zend_op_array *op_array)
+ZEND_API void zend_destroy_static_vars(zend_op_array *op_array)
 {
-       uint32_t i;
-
        if (ZEND_MAP_PTR(op_array->static_variables_ptr)) {
                HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
                if (ht) {
                        zend_array_destroy(ht);
+                       ZEND_MAP_PTR_SET(op_array->static_variables_ptr, NULL);
                }
        }
+}
+
+ZEND_API void destroy_op_array(zend_op_array *op_array)
+{
+       uint32_t i;
 
        if ((op_array->fn_flags & ZEND_ACC_HEAP_RT_CACHE)
         && ZEND_MAP_PTR(op_array->run_time_cache)) {
@@ -600,6 +607,19 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
        if (op_array->static_variables) {
                zend_array_destroy(op_array->static_variables);
        }
+       if (op_array->num_dynamic_func_defs) {
+               for (i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                       /* Closures overwrite static_variables in their copy.
+                        * Make sure to destroy them when the prototype function is destroyed. */
+                       if (op_array->dynamic_func_defs[i]->static_variables
+                                       && (op_array->dynamic_func_defs[i]->fn_flags & ZEND_ACC_CLOSURE)) {
+                               zend_array_destroy(op_array->dynamic_func_defs[i]->static_variables);
+                               op_array->dynamic_func_defs[i]->static_variables = NULL;
+                       }
+                       destroy_op_array(op_array->dynamic_func_defs[i]);
+               }
+               efree(op_array->dynamic_func_defs);
+       }
 }
 
 static void zend_update_extended_stmts(zend_op_array *op_array)
index b78f8ad61e54ed7d7a2d0e299b8f3204d0fddd41..8b4a86b7452f0f903501dbddb7fd64402949a460 100644 (file)
@@ -2824,6 +2824,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
                ZEND_VM_LEAVE();
        } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) {
                zend_detach_symbol_table(execute_data);
+               zend_destroy_static_vars(&EX(func)->op_array);
                destroy_op_array(&EX(func)->op_array);
                efree_size(EX(func), sizeof(zend_op_array));
 #ifdef ZEND_PREFER_RELOAD
@@ -6231,6 +6232,7 @@ ZEND_VM_HANDLER(73, ZEND_INCLUDE_OR_EVAL, CONST|TMPVAR|CV, ANY, EVAL, SPEC(OBSER
                        zend_vm_stack_free_call_frame(call);
                }
 
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                if (UNEXPECTED(EG(exception) != NULL)) {
@@ -7583,12 +7585,14 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT)
        ZEND_VM_NEXT_OPCODE();
 }
 
-ZEND_VM_HANDLER(141, ZEND_DECLARE_FUNCTION, ANY, ANY)
+ZEND_VM_HANDLER(141, ZEND_DECLARE_FUNCTION, ANY, NUM)
 {
+       zend_function *func;
        USE_OPLINE
 
        SAVE_OPLINE();
-       do_bind_function(RT_CONSTANT(opline, opline->op1));
+       func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num];
+       do_bind_function(func, RT_CONSTANT(opline, opline->op1));
        ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
 }
 
@@ -7853,23 +7857,14 @@ ZEND_VM_HANDLER(143, ZEND_DECLARE_CONST, CONST, CONST)
        ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
 }
 
-ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, UNUSED, CACHE_SLOT)
+ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, NUM)
 {
        USE_OPLINE
        zend_function *func;
-       zval *zfunc;
        zval *object;
        zend_class_entry *called_scope;
 
-       func = CACHED_PTR(opline->extended_value);
-       if (UNEXPECTED(func == NULL)) {
-               zfunc = zend_hash_find_ex(EG(function_table), Z_STR_P(RT_CONSTANT(opline, opline->op1)), 1);
-               ZEND_ASSERT(zfunc != NULL);
-               func = Z_FUNC_P(zfunc);
-               ZEND_ASSERT(func->type == ZEND_USER_FUNCTION);
-               CACHE_PTR(opline->extended_value, func);
-       }
-
+       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));
                if (UNEXPECTED((func->common.fn_flags & ZEND_ACC_STATIC) ||
index 419de3fa264638993225ae1c5a476ac406a8d40d..26903f940334a5501bd9b1b411f180693234339a 100644 (file)
@@ -1146,6 +1146,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper
                ZEND_VM_LEAVE();
        } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) {
                zend_detach_symbol_table(execute_data);
+               zend_destroy_static_vars(&EX(func)->op_array);
                destroy_op_array(&EX(func)->op_array);
                efree_size(EX(func), sizeof(zend_op_array));
 #ifdef ZEND_PREFER_RELOAD
@@ -2828,10 +2829,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE
 
 static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_FUNCTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
 {
+       zend_function *func;
        USE_OPLINE
 
        SAVE_OPLINE();
-       do_bind_function(RT_CONSTANT(opline, opline->op1));
+       func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num];
+       do_bind_function(func, RT_CONSTANT(opline, opline->op1));
        ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
 }
 
@@ -4732,6 +4735,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HAN
                        zend_vm_stack_free_call_frame(call);
                }
 
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                if (UNEXPECTED(EG(exception) != NULL)) {
@@ -4801,6 +4805,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_OBSERVER_
                        zend_vm_stack_free_call_frame(call);
                }
 
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                if (UNEXPECTED(EG(exception) != NULL)) {
@@ -5152,6 +5157,32 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_CLASS_SPEC_CONST_HANDL
        ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
 }
 
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+       USE_OPLINE
+       zend_function *func;
+       zval *object;
+       zend_class_entry *called_scope;
+
+       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));
+               if (UNEXPECTED((func->common.fn_flags & ZEND_ACC_STATIC) ||
+                               (EX(func)->common.fn_flags & ZEND_ACC_STATIC))) {
+                       object = NULL;
+               } else {
+                       object = &EX(This);
+               }
+       } else {
+               called_scope = Z_CE(EX(This));
+               object = NULL;
+       }
+       zend_create_closure(EX_VAR(opline->result.var), func,
+               EX(func)->op_array.scope, called_scope, object);
+
+       ZEND_VM_NEXT_OPCODE();
+}
+
 static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
 {
        USE_OPLINE
@@ -10207,41 +10238,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_U
 }
 
 /* No specialization for op_types (CONST|TMPVAR|CV, UNUSED|CLASS_FETCH|CONST|VAR) */
-static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
-{
-       USE_OPLINE
-       zend_function *func;
-       zval *zfunc;
-       zval *object;
-       zend_class_entry *called_scope;
-
-       func = CACHED_PTR(opline->extended_value);
-       if (UNEXPECTED(func == NULL)) {
-               zfunc = zend_hash_find_ex(EG(function_table), Z_STR_P(RT_CONSTANT(opline, opline->op1)), 1);
-               ZEND_ASSERT(zfunc != NULL);
-               func = Z_FUNC_P(zfunc);
-               ZEND_ASSERT(func->type == ZEND_USER_FUNCTION);
-               CACHE_PTR(opline->extended_value, func);
-       }
-
-       if (Z_TYPE(EX(This)) == IS_OBJECT) {
-               called_scope = Z_OBJCE(EX(This));
-               if (UNEXPECTED((func->common.fn_flags & ZEND_ACC_STATIC) ||
-                               (EX(func)->common.fn_flags & ZEND_ACC_STATIC))) {
-                       object = NULL;
-               } else {
-                       object = &EX(This);
-               }
-       } else {
-               called_scope = Z_CE(EX(This));
-               object = NULL;
-       }
-       zend_create_closure(EX_VAR(opline->result.var), func,
-               EX(func)->op_array.scope, called_scope, object);
-
-       ZEND_VM_NEXT_OPCODE();
-}
-
 static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
 {
        USE_OPLINE
@@ -14356,6 +14352,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_TMPVAR_HA
                        zend_vm_stack_free_call_frame(call);
                }
 
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                if (UNEXPECTED(EG(exception) != NULL)) {
@@ -38239,6 +38236,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_CV_HANDLE
                        zend_vm_stack_free_call_frame(call);
                }
 
+               zend_destroy_static_vars(new_op_array);
                destroy_op_array(new_op_array);
                efree_size(new_op_array, sizeof(zend_op_array));
                if (UNEXPECTED(EG(exception) != NULL)) {
@@ -53314,7 +53312,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
                        (void*)&&ZEND_NULL_LABEL,
                        (void*)&&ZEND_MAKE_REF_SPEC_CV_UNUSED_LABEL,
                        (void*)&&ZEND_DECLARE_FUNCTION_SPEC_LABEL,
-                       (void*)&&ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_LABEL,
+                       (void*)&&ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_LABEL,
                        (void*)&&ZEND_DECLARE_CONST_SPEC_CONST_CONST_LABEL,
                        (void*)&&ZEND_DECLARE_CLASS_SPEC_CONST_LABEL,
                        (void*)&&ZEND_DECLARE_CLASS_DELAYED_SPEC_CONST_CONST_LABEL,
@@ -54625,6 +54623,7 @@ zend_leave_helper_SPEC_LABEL:
                ZEND_VM_LEAVE();
        } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) {
                zend_detach_symbol_table(execute_data);
+               zend_destroy_static_vars(&EX(func)->op_array);
                destroy_op_array(&EX(func)->op_array);
                efree_size(EX(func), sizeof(zend_op_array));
 #ifdef ZEND_PREFER_RELOAD
@@ -55133,6 +55132,10 @@ zend_leave_helper_SPEC_LABEL:
                                VM_TRACE(ZEND_DECLARE_CLASS_SPEC_CONST)
                                ZEND_DECLARE_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
                                HYBRID_BREAK();
+                       HYBRID_CASE(ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST):
+                               VM_TRACE(ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST)
+                               ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
+                               HYBRID_BREAK();
                        HYBRID_CASE(ZEND_YIELD_FROM_SPEC_CONST):
                                VM_TRACE(ZEND_YIELD_FROM_SPEC_CONST)
                                ZEND_YIELD_FROM_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
@@ -55609,10 +55612,6 @@ zend_leave_helper_SPEC_LABEL:
                                VM_TRACE(ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_UNUSED)
                                ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
                                HYBRID_BREAK();
-                       HYBRID_CASE(ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED):
-                               VM_TRACE(ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED)
-                               ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
-                               HYBRID_BREAK();
                        HYBRID_CASE(ZEND_YIELD_SPEC_CONST_UNUSED):
                                VM_TRACE(ZEND_YIELD_SPEC_CONST_UNUSED)
                                ZEND_YIELD_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
@@ -61342,7 +61341,7 @@ void zend_vm_init(void)
                ZEND_NULL_HANDLER,
                ZEND_MAKE_REF_SPEC_CV_UNUSED_HANDLER,
                ZEND_DECLARE_FUNCTION_SPEC_HANDLER,
-               ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER,
+               ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_HANDLER,
                ZEND_DECLARE_CONST_SPEC_CONST_CONST_HANDLER,
                ZEND_DECLARE_CLASS_SPEC_CONST_HANDLER,
                ZEND_DECLARE_CLASS_DELAYED_SPEC_CONST_CONST_HANDLER,
index ed57433e178cc66a357a27bd35157507b68cfc8b..2fbc04273d5ac820c476ad118aa9c96f007ae88b 100644 (file)
        _(2286, ZEND_MAKE_REF_SPEC_VAR_UNUSED) \
        _(2288, ZEND_MAKE_REF_SPEC_CV_UNUSED) \
        _(2289, ZEND_DECLARE_FUNCTION_SPEC) \
-       _(2290, ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED) \
+       _(2290, ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST) \
        _(2291, ZEND_DECLARE_CONST_SPEC_CONST_CONST) \
        _(2292, ZEND_DECLARE_CLASS_SPEC_CONST) \
        _(2293, ZEND_DECLARE_CLASS_DELAYED_SPEC_CONST_CONST) \
index b5cb9fd15ffb5bde7a3a64f64cc125dbbb594b90..f25dc2e12ddd09da8c6151635d42af9f8b4db5aa 100644 (file)
@@ -368,8 +368,8 @@ static uint32_t zend_vm_opcodes_flags[201] = {
        0x00047305,
        0x00000000,
        0x00000101,
-       0x00000000,
-       0x00040103,
+       0x00001000,
+       0x00001003,
        0x00000303,
        0x00000003,
        0x00000303,
index 327e8a4d55467b36aeca3db1feb00c49e183577a..ad456c1bcb23a1f955ae3c57364fc4c5572de527 100644 (file)
@@ -138,23 +138,9 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source)
                ZEND_ASSERT(p->key);
                t = zend_hash_find_ex(target, p->key, 1);
                if (UNEXPECTED(t != NULL)) {
-                       if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
-                               /* Runtime definition key. There are two circumstances under which the key can
-                                * already be defined:
-                                *  1. The file has been re-included without being changed in the meantime. In
-                                *     this case we can keep the old value, because we know that the definition
-                                *     hasn't changed.
-                                *  2. The file has been changed in the meantime, but the RTD key ends up colliding.
-                                *     This would be a bug.
-                                * As we can't distinguish these cases, we assume that it is 1. and keep the old
-                                * value. */
-                               continue;
-                       } else {
-                               goto failure;
-                       }
-               } else {
-                       _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1);
+                       goto failure;
                }
+               _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1);
        }
        target->nInternalPointer = 0;
        return;
@@ -190,7 +176,15 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source)
                t = zend_hash_find_ex(target, p->key, 1);
                if (UNEXPECTED(t != NULL)) {
                        if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
-                               /* See comment in zend_accel_function_hash_copy(). */
+                               /* Runtime definition key. There are two circumstances under which the key can
+                                * already be defined:
+                                *  1. The file has been re-included without being changed in the meantime. In
+                                *     this case we can keep the old value, because we know that the definition
+                                *     hasn't changed.
+                                *  2. The file has been changed in the meantime, but the RTD key ends up colliding.
+                                *     This would be a bug.
+                                * As we can't distinguish these cases, we assume that it is 1. and keep the old
+                                * value. */
                                continue;
                        } else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
                                zend_class_entry *ce1 = Z_PTR(p->val);
index d89c462df58cb248cca040e42b1fadc291d5ba2c..1021992b3acdb99ed2571f138dd88419a0d9238e 100644 (file)
@@ -606,6 +606,20 @@ static void zend_file_cache_serialize_op_array(zend_op_array            *op_arra
                        }
                }
 
+               if (op_array->num_dynamic_func_defs) {
+                       zend_op_array **defs;
+                       SERIALIZE_PTR(op_array->dynamic_func_defs);
+                       defs = op_array->dynamic_func_defs;
+                       UNSERIALIZE_PTR(defs);
+                       for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                               zend_op_array *def;
+                               SERIALIZE_PTR(defs[i]);
+                               def = defs[i];
+                               UNSERIALIZE_PTR(def);
+                               zend_file_cache_serialize_op_array(def, script, info, buf);
+                       }
+               }
+
                SERIALIZE_STR(op_array->function_name);
                SERIALIZE_STR(op_array->filename);
                SERIALIZE_PTR(op_array->live_range);
@@ -1394,6 +1408,14 @@ static void zend_file_cache_unserialize_op_array(zend_op_array           *op_arr
                        }
                }
 
+               if (op_array->num_dynamic_func_defs) {
+                       UNSERIALIZE_PTR(op_array->dynamic_func_defs);
+                       for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                               UNSERIALIZE_PTR(op_array->dynamic_func_defs[i]);
+                               zend_file_cache_unserialize_op_array(op_array->dynamic_func_defs[i], script, buf);
+                       }
+               }
+
                UNSERIALIZE_STR(op_array->function_name);
                UNSERIALIZE_STR(op_array->filename);
                UNSERIALIZE_PTR(op_array->live_range);
index 5e406ec4b6b6d181eb08a24ecca3924da513eadf..6688e473cb70fbea922df00019ead50f9bd0ea13 100644 (file)
@@ -82,6 +82,7 @@
 typedef void (*zend_persist_func_t)(zval*);
 
 static void zend_persist_zval(zval *z);
+static void zend_persist_op_array(zval *zv);
 
 static const uint32_t uninitialized_bucket[-HT_MIN_MASK] =
        {HT_INVALID_IDX, HT_INVALID_IDX};
@@ -663,6 +664,17 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
                }
        }
 
+       if (op_array->num_dynamic_func_defs) {
+               op_array->dynamic_func_defs = zend_shared_memdup_put_free(
+                       op_array->dynamic_func_defs, sizeof(zend_function *) * op_array->num_dynamic_func_defs);
+               for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                       zval tmp;
+                       ZVAL_PTR(&tmp, op_array->dynamic_func_defs[i]);
+                       zend_persist_op_array(&tmp);
+                       op_array->dynamic_func_defs[i] = Z_PTR(tmp);
+               }
+       }
+
        ZCG(mem) = (void*)((char*)ZCG(mem) + ZEND_ALIGNED_SIZE(zend_extensions_op_array_persist(op_array, ZCG(mem))));
 
 #ifdef HAVE_JIT
@@ -676,16 +688,22 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
 static void zend_persist_op_array(zval *zv)
 {
        zend_op_array *op_array = Z_PTR_P(zv);
-
+       zend_op_array *old_op_array;
        ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION);
-       op_array = Z_PTR_P(zv) = zend_shared_memdup(Z_PTR_P(zv), sizeof(zend_op_array));
-       zend_persist_op_array_ex(op_array, NULL);
-       if (!ZCG(current_persistent_script)->corrupted) {
-               op_array->fn_flags |= ZEND_ACC_IMMUTABLE;
-               ZEND_MAP_PTR_NEW(op_array->run_time_cache);
-               if (op_array->static_variables) {
-                       ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
+
+       old_op_array = zend_shared_alloc_get_xlat_entry(op_array);
+       if (!old_op_array) {
+               op_array = Z_PTR_P(zv) = zend_shared_memdup_put(Z_PTR_P(zv), sizeof(zend_op_array));
+               zend_persist_op_array_ex(op_array, NULL);
+               if (!ZCG(current_persistent_script)->corrupted) {
+                       op_array->fn_flags |= ZEND_ACC_IMMUTABLE;
+                       ZEND_MAP_PTR_NEW(op_array->run_time_cache);
+                       if (op_array->static_variables) {
+                               ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
+                       }
                }
+       } else {
+               /* This can happen during preloading, if a dynamic function definition is declared. */
        }
 }
 
index 015490b07a16144dd88d8d31c0d47544989b6468..ad27d539be2c7b99a5a2bbe6acd228fddf542872 100644 (file)
@@ -46,6 +46,7 @@
        } while (0)
 
 static void zend_persist_zval_calc(zval *z);
+static void zend_persist_op_array_calc(zval *zv);
 
 static void zend_hash_persist_calc(HashTable *ht)
 {
@@ -287,16 +288,29 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
                }
        }
 
+       if (op_array->num_dynamic_func_defs) {
+               ADD_SIZE(sizeof(void *) * op_array->num_dynamic_func_defs);
+               for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                       zval tmp;
+                       ZVAL_PTR(&tmp, op_array->dynamic_func_defs[i]);
+                       zend_persist_op_array_calc(&tmp);
+               }
+       }
+
        ADD_SIZE(ZEND_ALIGNED_SIZE(zend_extensions_op_array_persist_calc(op_array)));
 }
 
 static void zend_persist_op_array_calc(zval *zv)
 {
        zend_op_array *op_array = Z_PTR_P(zv);
-
        ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION);
-       ADD_SIZE(sizeof(zend_op_array));
-       zend_persist_op_array_calc_ex(Z_PTR_P(zv));
+       if (!zend_shared_alloc_get_xlat_entry(op_array)) {
+               zend_shared_alloc_register_xlat_entry(op_array, op_array);
+               ADD_SIZE(sizeof(zend_op_array));
+               zend_persist_op_array_calc_ex(op_array);
+       } else {
+               /* This can happen during preloading, if a dynamic function definition is declared. */
+       }
 }
 
 static void zend_persist_class_method_calc(zval *zv)
index e3bbb9fed8a08f936e29e784f44b75af6d3d03d7..e009432a9e26e4d5b72edb5e3b3cf842f504edaf 100644 (file)
@@ -87,6 +87,13 @@ static inline void phpdbg_print_function_helper(zend_function *method) /* {{{ */
                                        efree(decode);
                                        opline++;
                                } while (opcode++ < end);
+
+                               for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+                                       zend_op_array *def = op_array->dynamic_func_defs[i];
+                                       phpdbg_out("\ndynamic def: %i, function name: %.*s\n",
+                                               i, (int) ZSTR_LEN(def->function_name), ZSTR_VAL(def->function_name));
+                                       phpdbg_print_function_helper((zend_function *) def);
+                               }
                        }
                } break;