[php-src] master: Merge branch 'PHP-8.5'
Ilia Alshanetsky <[email protected]> Fri, 31 Jul 2026 15:53:26 +0000
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-31T11:51:25-04:00
Commit: https://github.com/php/php-src/commit/1da01f076b75539e8f02b6494b29f6a3c752110e
Raw diff: https://github.com/php/php-src/commit/1da01f076b75539e8f02b6494b29f6a3c752110e.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
Free previous state when re-calling Reflection*::__construct()
Master additionally stores the reflected closure in intern->obj for the
invoke() identity check, so the merge releases the previous object before
overwriting it and clears the slot when re-constructing onto a named
method.
Changed paths:
A ext/reflection/tests/ReflectionClassConstant_double_construct.phpt
A ext/reflection/tests/ReflectionMethod_double_construct_closure.phpt
M ext/reflection/php_reflection.c
Diff:
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 6682346a5b08..42ab6dcd8ae5 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -3241,15 +3241,26 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
{
/* Store the original closure object so we can validate it in invoke/invokeArgs.
* Each closure has a unique __invoke signature, so we must reject different closures. */
+ zval_ptr_dtor(&intern->obj);
ZVAL_OBJ_COPY(&intern->obj, orig_obj);
} else if ((mptr = zend_hash_str_find_ptr(&ce->function_table, lcname, method_name_len)) == NULL) {
efree(lcname);
zend_throw_exception_ex(reflection_exception_ptr, 0,
"Method %s::%s() does not exist", ZSTR_VAL(ce->name), method_name);
RETURN_THROWS();
+ } else {
+ zval_ptr_dtor(&intern->obj);
+ ZVAL_UNDEF(&intern->obj);
}
efree(lcname);
+ if (intern->ptr) {
+ ZEND_ASSERT(is_constructor);
+ _free_function(intern->ptr);
+ zval_ptr_dtor(reflection_prop_name(object));
+ zval_ptr_dtor(reflection_prop_class(object));
+ }
+
ZVAL_STR_COPY(reflection_prop_name(object), mptr->common.function_name);
ZVAL_STR_COPY(reflection_prop_class(object), mptr->common.scope->name);
intern->ptr = mptr;
@@ -3753,6 +3764,11 @@ ZEND_METHOD(ReflectionClassConstant, __construct)
RETURN_THROWS();
}
+ if (intern->ptr) {
+ zval_ptr_dtor(reflection_prop_name(object));
+ zval_ptr_dtor(reflection_prop_class(object));
+ }
+
intern->ptr = constant;
intern->ref_type = REF_TYPE_CLASS_CONSTANT;
intern->ce = constant->ce;
diff --git a/ext/reflection/tests/ReflectionClassConstant_double_construct.phpt b/ext/reflection/tests/ReflectionClassConstant_double_construct.phpt
new file mode 100644
index 000000000000..efd5472c4aff
--- /dev/null
+++ b/ext/reflection/tests/ReflectionClassConstant_double_construct.phpt
@@ -0,0 +1,33 @@
+--TEST--
+ReflectionClassConstant double construct call does not leak $name and $class
+--FILE--
+<?php
+
+class C {
+ const FOO = 1;
+}
+
+function test(ReflectionClassConstant $r) {
+ /* implode() so that the name is not an interned string. */
+ $r->__construct(C::class, implode('', ['F', 'O', 'O']));
+}
+
+$r = new ReflectionClassConstant(C::class, 'FOO');
+for ($i = 0; $i < 10; $i++) {
+ test($r);
+}
+
+$before = memory_get_usage();
+for ($i = 0; $i < 1000; $i++) {
+ test($r);
+}
+$after = memory_get_usage();
+
+var_dump($before === $after);
+var_dump($r->name, $r->class);
+
+?>
+--EXPECT--
+bool(true)
+string(3) "FOO"
+string(1) "C"
diff --git a/ext/reflection/tests/ReflectionMethod_double_construct_closure.phpt b/ext/reflection/tests/ReflectionMethod_double_construct_closure.phpt
new file mode 100644
index 000000000000..3642974d9a6a
--- /dev/null
+++ b/ext/reflection/tests/ReflectionMethod_double_construct_closure.phpt
@@ -0,0 +1,28 @@
+--TEST--
+ReflectionMethod double construct call on Closure::__invoke() does not leak
+--FILE--
+<?php
+
+function test(ReflectionMethod $r) {
+ $r->__construct(function () {}, '__invoke');
+}
+
+$r = new ReflectionMethod(function () {}, '__invoke');
+for ($i = 0; $i < 10; $i++) {
+ test($r);
+}
+
+$before = memory_get_usage();
+for ($i = 0; $i < 1000; $i++) {
+ test($r);
+}
+$after = memory_get_usage();
+
+var_dump($before === $after);
+var_dump($r->name, $r->class);
+
+?>
+--EXPECT--
+bool(true)
+string(8) "__invoke"
+string(7) "Closure"