[php-src] master: Merge branch 'PHP-8.5' into PHP-8.6
| From: | ndossche | Date: | Thu, 08 Oct 2026 18:42:02 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.5' into PHP-8.6 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142105@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-08T20:41:43+02:00
Commit: https://github.com/php/php-src/commit/eefe4db40ed2b6da4352db6510d920d2abcaead7
Raw diff: https://github.com/php/php-src/commit/eefe4db40ed2b6da4352db6510d920d2abcaead7.diff
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5:
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
M ext/zend_test/test_decl.h
M ext/zend_test/test_legacy_arginfo.h
Diff:
diff --git a/NEWS b/NEWS
index fcfee542f6b6..618ecace1ae2 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 4fc8926c0fcc..dcee8e9b2b3f 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -1786,8 +1786,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 4853e1f61e70..fa9b43b68c59 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -197,6 +197,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 afeae903c668..1fbacdae1093 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -299,6 +299,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_bool(bool $param): bool {}
function zend_bool_or_null(bool|null $param): bool|null {}
function zend_bool_slow_zpp(bool $param): bool {}
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index d01e3d036c7f..40f06222783f 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 test.stub.php instead.
- * Stub hash: 2b25ec2985ed2c14165625a0603ad2a122a255c7
+ * Stub hash: 0d233e99eed49cad1bb8e52c28d14ba2a6054b03
* Has decl header: yes */
#include "zend_attributes.h"
@@ -59,6 +59,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_INFO_EX(arginfo_zend_bool, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, param, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
@@ -441,6 +446,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_bool);
static ZEND_FUNCTION(zend_bool_or_null);
static ZEND_FUNCTION(zend_bool_slow_zpp);
@@ -618,6 +624,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_bool, arginfo_zend_bool)
ZEND_FE(zend_bool_or_null, arginfo_zend_bool_or_null)
ZEND_FE(zend_bool_slow_zpp, arginfo_zend_bool_slow_zpp)
diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h
index d2bc04df8f4e..96ebfbea6fc4 100644
--- a/ext/zend_test/test_decl.h
+++ b/ext/zend_test/test_decl.h
@@ -1,8 +1,8 @@
/* This is a generated file, edit test.stub.php instead.
- * Stub hash: 2b25ec2985ed2c14165625a0603ad2a122a255c7 */
+ * Stub hash: 0d233e99eed49cad1bb8e52c28d14ba2a6054b03 */
-#ifndef ZEND_TEST_DECL_2b25ec2985ed2c14165625a0603ad2a122a255c7_H
-#define ZEND_TEST_DECL_2b25ec2985ed2c14165625a0603ad2a122a255c7_H
+#ifndef ZEND_TEST_DECL_0d233e99eed49cad1bb8e52c28d14ba2a6054b03_H
+#define ZEND_TEST_DECL_0d233e99eed49cad1bb8e52c28d14ba2a6054b03_H
typedef enum zend_enum_ZendTestUnitEnum {
ZEND_ENUM_ZendTestUnitEnum_Foo = 1,
@@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface {
ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2,
} zend_enum_ZendTestEnumWithInterface;
-#endif /* ZEND_TEST_DECL_2b25ec2985ed2c14165625a0603ad2a122a255c7_H */
+#endif /* ZEND_TEST_DECL_0d233e99eed49cad1bb8e52c28d14ba2a6054b03_H */
diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h
index e12e72eb6d28..4ee3d3d083eb 100644
--- a/ext/zend_test/test_legacy_arginfo.h
+++ b/ext/zend_test/test_legacy_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit test.stub.php instead.
- * Stub hash: 2b25ec2985ed2c14165625a0603ad2a122a255c7
+ * Stub hash: 0d233e99eed49cad1bb8e52c28d14ba2a6054b03
* Has decl header: yes */
#include "zend_constants.h"
@@ -54,6 +54,11 @@ ZEND_END_ARG_INFO()
#define arginfo_zend_delref arginfo_zend_leak_variable
+ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_erealloc_block_size, 0, 0, 2)
+ ZEND_ARG_INFO(0, old_size)
+ ZEND_ARG_INFO(0, new_size)
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_bool, 0, 0, 1)
ZEND_ARG_INFO(0, param)
ZEND_END_ARG_INFO()
@@ -365,6 +370,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_bool);
static ZEND_FUNCTION(zend_bool_or_null);
static ZEND_FUNCTION(zend_bool_slow_zpp);
@@ -514,6 +520,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_bool, arginfo_zend_bool)
ZEND_FE(zend_bool_or_null, arginfo_zend_bool_or_null)
ZEND_FE(zend_bool_slow_zpp, arginfo_zend_bool_slow_zpp)