Skip to content

Commit 8e7ecbf

Browse files
committed
Free previous state when re-calling Reflection*::__construct()
ReflectionMethod::__construct() overwrote intern->ptr without releasing the previous Closure::__invoke() trampoline, and ReflectionClassConstant::__construct() overwrote the $name and $class slots without dropping their previous references. ReflectionFunction, ReflectionParameter and ReflectionProperty already release prior state on re-entry. Closes GH-22968
1 parent a96f6ff commit 8e7ecbf

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

ext/reflection/php_reflection.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3420,6 +3420,13 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
34203420
}
34213421
efree(lcname);
34223422

3423+
if (intern->ptr) {
3424+
ZEND_ASSERT(is_constructor);
3425+
_free_function(intern->ptr);
3426+
zval_ptr_dtor(reflection_prop_name(object));
3427+
zval_ptr_dtor(reflection_prop_class(object));
3428+
}
3429+
34233430
ZVAL_STR_COPY(reflection_prop_name(object), mptr->common.function_name);
34243431
ZVAL_STR_COPY(reflection_prop_class(object), mptr->common.scope->name);
34253432
intern->ptr = mptr;
@@ -3932,6 +3939,11 @@ ZEND_METHOD(ReflectionClassConstant, __construct)
39323939
RETURN_THROWS();
39333940
}
39343941

3942+
if (intern->ptr) {
3943+
zval_ptr_dtor(reflection_prop_name(object));
3944+
zval_ptr_dtor(reflection_prop_class(object));
3945+
}
3946+
39353947
intern->ptr = constant;
39363948
intern->ref_type = REF_TYPE_CLASS_CONSTANT;
39373949
intern->ce = constant->ce;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
ReflectionClassConstant double construct call does not leak $name and $class
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
const FOO = 1;
8+
}
9+
10+
function test(ReflectionClassConstant $r) {
11+
/* implode() so that the name is not an interned string. */
12+
$r->__construct(C::class, implode('', ['F', 'O', 'O']));
13+
}
14+
15+
$r = new ReflectionClassConstant(C::class, 'FOO');
16+
for ($i = 0; $i < 10; $i++) {
17+
test($r);
18+
}
19+
20+
$before = memory_get_usage();
21+
for ($i = 0; $i < 1000; $i++) {
22+
test($r);
23+
}
24+
$after = memory_get_usage();
25+
26+
var_dump($before === $after);
27+
var_dump($r->name, $r->class);
28+
29+
?>
30+
--EXPECT--
31+
bool(true)
32+
string(3) "FOO"
33+
string(1) "C"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
ReflectionMethod double construct call on Closure::__invoke() does not leak
3+
--FILE--
4+
<?php
5+
6+
function test(ReflectionMethod $r) {
7+
$r->__construct(function () {}, '__invoke');
8+
}
9+
10+
$r = new ReflectionMethod(function () {}, '__invoke');
11+
for ($i = 0; $i < 10; $i++) {
12+
test($r);
13+
}
14+
15+
$before = memory_get_usage();
16+
for ($i = 0; $i < 1000; $i++) {
17+
test($r);
18+
}
19+
$after = memory_get_usage();
20+
21+
var_dump($before === $after);
22+
var_dump($r->name, $r->class);
23+
24+
?>
25+
--EXPECT--
26+
bool(true)
27+
string(8) "__invoke"
28+
string(7) "Closure"

0 commit comments

Comments
 (0)