[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

From: 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)

« previous php.cvs (#142103) next »