[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
| From: | ndossche | Date: | Thu, 08 Oct 2026 18:42:01 +0000 |
| Subject: | [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142103@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-08T20:40:48+02:00
Commit: https://github.com/php/php-src/commit/33d7f34694f942dd6a6696450fc45fb114a0a0fb
Raw diff: https://github.com/php/php-src/commit/33d7f34694f942dd6a6696450fc45fb114a0a0fb.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
zend_alloc: move a small block shrunk to the size of the bin below
Changed paths:
A Zend/tests/zend_alloc_realloc_shrink_boundary.phpt
M NEWS
M Zend/zend_alloc.c
M ext/zend_test/test.c
M ext/zend_test/test.stub.php
M ext/zend_test/test_arginfo.h
Diff:
diff --git a/NEWS b/NEWS
index 56d20ae2212e..19f11fd33db3 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@ PHP NEWS
in its previous chain. (Edmond)
. Fixed bug GH-23979 (Nullsafe operator must not flush delayed oplines of an
enclosing function). (ndossche)
+ . Fixed memory manager keeping a block reallocated to exactly the size of
+ the next smaller bin in its larger bin, which efree_size() then freed
+ into the wrong one. (Marc Bennewitz)
- Opcache:
. Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a
diff --git a/Zend/tests/zend_alloc_realloc_shrink_boundary.phpt
b/Zend/tests/zend_alloc_realloc_shrink_boundary.phpt
new file mode 100644
index 000000000000..0fae7c2acda0
--- /dev/null
+++ b/Zend/tests/zend_alloc_realloc_shrink_boundary.phpt
@@ -0,0 +1,40 @@
+--TEST--
+erealloc() of a small block keeps it in the bin its new size maps to
+--EXTENSIONS--
+zend_test
+--SKIPIF--
+<?php
+if (getenv("USE_ZEND_ALLOC") === "0") die("skip requires zmm");
+?>
+--FILE--
+<?php
+
+// The largest request of each small bin, found from fresh allocations.
+$largest = [];
+for ($n = 1; $n <= 3072; $n++) {
+ [, $block] = zend_test_erealloc_block_size($n, $n);
+ if ($block > 3072) {
+ break;
+ }
+ $largest[$block] = $n;
+}
+var_dump(count($largest) > 20);
+
+// Shrink a block of each bin to every smaller size, down to the size of the
+// bin below, which efree_size() maps to that bin.
+$mismatches = [];
+foreach ($largest as $old_size) {
+ for ($new_size = 1; $new_size < $old_size; $new_size++) {
+ [$block, $fresh] = zend_test_erealloc_block_size($old_size, $new_size);
+ if ($block !== $fresh) {
+ $mismatches[] = "$old_size -> $new_size: block of $block, expected
$fresh";
+ }
+ }
+}
+var_dump(array_slice($mismatches, 0, 5));
+
+?>
+--EXPECT--
+bool(true)
+array(0) {
+}
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index 95986d709cf8..c5ab74c70bb0 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -1712,8 +1712,9 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void
*p
/* Check if requested size fits into current bin */
if (size <= old_size) {
- /* Check if truncation is necessary */
- if (old_bin_num > 0 && size < bin_data_size[old_bin_num - 1]) {
+ /* Check if truncation is necessary. A size that fits the
+ * bin below moves there, as efree_size() maps it to that bin. */
+ if (old_bin_num > 0 && size <= bin_data_size[old_bin_num - 1]) {
/* truncation */
ret = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(size) ZEND_FILE_LINE_RELAY_CC
ZEND_FILE_LINE_ORIG_RELAY_CC);
copy_size = use_copy_size ? MIN(size, copy_size) : size;
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index e1db746297f2..0260d25cd40a 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -195,6 +195,37 @@ static ZEND_FUNCTION(zend_leak_bytes)
emalloc(leakbytes);
}
+/* Reallocate a block of old_size bytes to new_size bytes and return its block
+ * size, along with the block size of a fresh allocation of new_size bytes. */
+static ZEND_FUNCTION(zend_test_erealloc_block_size)
+{
+ zend_long old_size, new_size;
+
+ ZEND_PARSE_PARAMETERS_START(2, 2)
+ Z_PARAM_LONG(old_size)
+ Z_PARAM_LONG(new_size)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (old_size < 1) {
+ zend_argument_value_error(1, "must be greater than 0");
+ RETURN_THROWS();
+ }
+ if (new_size < 1) {
+ zend_argument_value_error(2, "must be greater than 0");
+ RETURN_THROWS();
+ }
+
+ void *ptr = erealloc(emalloc(old_size), new_size);
+ void *fresh = emalloc(new_size);
+
+ array_init(return_value);
+ add_next_index_long(return_value, zend_mem_block_size(ptr));
+ add_next_index_long(return_value, zend_mem_block_size(fresh));
+
+ efree(fresh);
+ efree(ptr);
+}
+
/* Leak a refcounted variable */
static ZEND_FUNCTION(zend_leak_variable)
{
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index eb4a6fac8795..1e693707e3f7 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -264,6 +264,8 @@ function zend_leak_bytes(int $bytes = 3): void {}
function zend_delref(mixed $variable): void {}
+ function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
+
function zend_string_or_object(object|string $param): object|string {}
function zend_string_or_object_or_null(object|string|null $param): object|string|null {}
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index d605cff30ef7..f4bd4c9ee7de 100644
--- a/ext/zend_test/test_arginfo.h
+++ b/ext/zend_test/test_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: b9cc27fee1c2d75a13d489f4589956eef7bfedf4 */
+ * Stub hash: 4990c2206809c0737f7c13eb5ea11459437bd144 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0)
ZEND_END_ARG_INFO()
@@ -52,6 +52,11 @@ ZEND_END_ARG_INFO()
#define arginfo_zend_delref arginfo_zend_leak_variable
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_erealloc_block_size, 0, 2, IS_ARRAY, 0)
+ ZEND_ARG_TYPE_INFO(0, old_size, IS_LONG, 0)
+ ZEND_ARG_TYPE_INFO(0, new_size, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_zend_string_or_object, 0, 1,
MAY_BE_OBJECT|MAY_BE_STRING)
ZEND_ARG_TYPE_MASK(0, param, MAY_BE_OBJECT|MAY_BE_STRING, NULL)
ZEND_END_ARG_INFO()
@@ -300,6 +305,7 @@ static ZEND_FUNCTION(zend_terminate_string);
static ZEND_FUNCTION(zend_leak_variable);
static ZEND_FUNCTION(zend_leak_bytes);
static ZEND_FUNCTION(zend_delref);
+static ZEND_FUNCTION(zend_test_erealloc_block_size);
static ZEND_FUNCTION(zend_string_or_object);
static ZEND_FUNCTION(zend_string_or_object_or_null);
static ZEND_FUNCTION(zend_string_or_stdclass);
@@ -432,6 +438,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_leak_variable, arginfo_zend_leak_variable)
ZEND_FE(zend_leak_bytes, arginfo_zend_leak_bytes)
ZEND_FE(zend_delref, arginfo_zend_delref)
+ ZEND_FE(zend_test_erealloc_block_size, arginfo_zend_test_erealloc_block_size)
ZEND_FE(zend_string_or_object, arginfo_zend_string_or_object)
ZEND_FE(zend_string_or_object_or_null, arginfo_zend_string_or_object_or_null)
ZEND_FE(zend_string_or_stdclass, arginfo_zend_string_or_stdclass)