From 45cb3f917a3e146d0d37e9b99ebe879d8f2d4cd5 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 21 Nov 2022 17:41:16 +0300 Subject: [PATCH] Fix a memory leak in tracig JIT when the same closure is called through Closure::call() and natively. Closure::call() makes a temporary copy of original closure function, modifies its scope, resets ZEND_ACC_CLOSURE flag and call it through zend_call_function(). As result the same function may be called with and without ZEND_ACC_CLOSURE flag, that confuses JIT and may lead to memory leak or even worse memory errors. The patch allocates "fake" closure object and keep ZEND_ACC_CLOSURE flag to always behave in the same way. --- Zend/zend_closures.c | 16 +++++---- ext/opcache/tests/jit/closure_001.phpt | 46 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 ext/opcache/tests/jit/closure_001.phpt diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 5bbc90029f9..e3fbd8e0ad7 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -160,15 +160,21 @@ ZEND_METHOD(Closure, call) /* copied upon generator creation */ GC_DELREF(&closure->std); } else { + zend_closure *fake_closure; zend_function *my_function; + + fake_closure = emalloc(sizeof(zend_closure)); + memset(&fake_closure->std, 0, sizeof(fake_closure->std)); + fake_closure->std.gc.refcount = 1; + fake_closure->std.gc.u.type_info = GC_NULL; + ZVAL_UNDEF(&fake_closure->this_ptr); + fake_closure->called_scope = NULL; + my_function = &fake_closure->func; if (ZEND_USER_CODE(closure->func.type)) { - my_function = emalloc(sizeof(zend_op_array)); memcpy(my_function, &closure->func, sizeof(zend_op_array)); } else { - my_function = emalloc(sizeof(zend_internal_function)); memcpy(my_function, &closure->func, sizeof(zend_internal_function)); } - my_function->common.fn_flags &= ~ZEND_ACC_CLOSURE; /* use scope of passed object */ my_function->common.scope = newclass; if (closure->func.type == ZEND_INTERNAL_FUNCTION) { @@ -194,10 +200,8 @@ ZEND_METHOD(Closure, call) if (fci_cache.function_handler->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) { efree(ZEND_MAP_PTR(my_function->op_array.run_time_cache)); } - efree_size(my_function, sizeof(zend_op_array)); - } else { - efree_size(my_function, sizeof(zend_internal_function)); } + efree_size(fake_closure, sizeof(zend_closure)); } if (Z_TYPE(closure_result) != IS_UNDEF) { diff --git a/ext/opcache/tests/jit/closure_001.phpt b/ext/opcache/tests/jit/closure_001.phpt new file mode 100644 index 00000000000..3e47d016d83 --- /dev/null +++ b/ext/opcache/tests/jit/closure_001.phpt @@ -0,0 +1,46 @@ +--TEST-- +Closures should be always called with ZEND_ACC_CLOSURE flag set +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.protect_memory=1 +opcache.jit_hot_func=2 +--EXTENSIONS-- +opcache +--FILE-- +call($foo)); +var_dump($f->call($foo)); +var_dump($f()); +?> +--EXPECT-- +object(Closure)#3 (1) { + ["this"]=> + object(Foo)#1 (0) { + } +} +object(Closure)#3 (1) { + ["this"]=> + object(Foo)#1 (0) { + } +} +object(Closure)#3 (0) { +}