Skip to content

Commit 45cb3f9

Browse files
committedNov 21, 2022
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.
1 parent aef9660 commit 45cb3f9

File tree

2 files changed

+56
-6
lines changed

2 files changed

+56
-6
lines changed
 

‎Zend/zend_closures.c

+10-6
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,21 @@ ZEND_METHOD(Closure, call)
160160
/* copied upon generator creation */
161161
GC_DELREF(&closure->std);
162162
} else {
163+
zend_closure *fake_closure;
163164
zend_function *my_function;
165+
166+
fake_closure = emalloc(sizeof(zend_closure));
167+
memset(&fake_closure->std, 0, sizeof(fake_closure->std));
168+
fake_closure->std.gc.refcount = 1;
169+
fake_closure->std.gc.u.type_info = GC_NULL;
170+
ZVAL_UNDEF(&fake_closure->this_ptr);
171+
fake_closure->called_scope = NULL;
172+
my_function = &fake_closure->func;
164173
if (ZEND_USER_CODE(closure->func.type)) {
165-
my_function = emalloc(sizeof(zend_op_array));
166174
memcpy(my_function, &closure->func, sizeof(zend_op_array));
167175
} else {
168-
my_function = emalloc(sizeof(zend_internal_function));
169176
memcpy(my_function, &closure->func, sizeof(zend_internal_function));
170177
}
171-
my_function->common.fn_flags &= ~ZEND_ACC_CLOSURE;
172178
/* use scope of passed object */
173179
my_function->common.scope = newclass;
174180
if (closure->func.type == ZEND_INTERNAL_FUNCTION) {
@@ -194,10 +200,8 @@ ZEND_METHOD(Closure, call)
194200
if (fci_cache.function_handler->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) {
195201
efree(ZEND_MAP_PTR(my_function->op_array.run_time_cache));
196202
}
197-
efree_size(my_function, sizeof(zend_op_array));
198-
} else {
199-
efree_size(my_function, sizeof(zend_internal_function));
200203
}
204+
efree_size(fake_closure, sizeof(zend_closure));
201205
}
202206

203207
if (Z_TYPE(closure_result) != IS_UNDEF) {
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Closures should be always called with ZEND_ACC_CLOSURE flag set
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.jit_buffer_size=1M
8+
opcache.protect_memory=1
9+
opcache.jit_hot_func=2
10+
--EXTENSIONS--
11+
opcache
12+
--FILE--
13+
<?php
14+
class Foo {
15+
}
16+
17+
function bar() {
18+
return function () {
19+
return function () {
20+
return function () {
21+
return 42;
22+
};
23+
};
24+
};
25+
}
26+
27+
$foo = new Foo;
28+
$f = bar();
29+
30+
var_dump($f->call($foo));
31+
var_dump($f->call($foo));
32+
var_dump($f());
33+
?>
34+
--EXPECT--
35+
object(Closure)#3 (1) {
36+
["this"]=>
37+
object(Foo)#1 (0) {
38+
}
39+
}
40+
object(Closure)#3 (1) {
41+
["this"]=>
42+
object(Foo)#1 (0) {
43+
}
44+
}
45+
object(Closure)#3 (0) {
46+
}

0 commit comments

Comments
 (0)